{-| Scan clusters via RAPI and write instance/node data files.

-}

module Main (main) where

import Data.List
import Data.Function
import Data.Maybe(fromJust)
import Monad
import System
import System.IO
import System.FilePath
import System.Console.GetOpt
import qualified System

import Text.Printf (printf)

import qualified Ganeti.HTools.Container as Container
import qualified Ganeti.HTools.Cluster as Cluster
import qualified Ganeti.HTools.Node as Node
import qualified Ganeti.HTools.Instance as Instance
import qualified Ganeti.HTools.CLI as CLI
import Ganeti.HTools.Rapi
import Ganeti.HTools.Utils

-- | Command line options structure.
data Options = Options
    { optShowNodes :: Bool     -- ^ Whether to show node status
    , optOutPath   :: FilePath -- ^ Path to the output directory
    , optVerbose   :: Int      -- ^ Verbosity level
    , optNoHeader  :: Bool     -- ^ Do not show a header line
    , optShowVer   :: Bool     -- ^ Just show the program version
    , optShowHelp  :: Bool     -- ^ Just show the help
    } deriving Show

-- | Default values for the command line options.
defaultOptions :: Options
defaultOptions  = Options
 { optShowNodes = False
 , optOutPath   = "."
 , optVerbose   = 0
 , optNoHeader  = False
 , optShowVer   = False
 , optShowHelp  = False
 }

-- | Options list and functions
options :: [OptDescr (Options -> Options)]
options =
    [ Option ['p']     ["print-nodes"]
      (NoArg (\ opts -> opts { optShowNodes = True }))
      "print the final node list"
    , Option ['d']     ["output-dir"]
      (ReqArg (\ d opts -> opts { optOutPath = d }) "PATH")
      "directory in which to write output files"
    , Option ['v']     ["verbose"]
      (NoArg (\ opts -> opts { optVerbose = (optVerbose opts) + 1 }))
      "increase the verbosity level"
    , Option []        ["no-headers"]
      (NoArg (\ opts -> opts { optNoHeader = True }))
      "do not show a header line"
    , Option ['V']     ["version"]
      (NoArg (\ opts -> opts { optShowVer = True}))
      "show the version of the program"
    , Option ['h']     ["help"]
      (NoArg (\ opts -> opts { optShowHelp = True}))
      "show help"
    ]

-- | Generate node file data from node objects
serializeNodes :: Cluster.NodeList -> String -> Cluster.NameList -> String
serializeNodes nl csf ktn =
    let etn = map (\(idx, name) -> (idx, name ++ csf)) ktn
        nodes = Container.elems nl
        nlines = map
                 (\node ->
                      let name = (fromJust $ lookup (Node.idx node) etn)
                          t_mem = (truncate $ Node.t_mem node)::Int
                          t_dsk = (truncate $ Node.t_dsk node)::Int
                      in
                        printf "%s|%d|%d|%d|%d|%d" name
                                   t_mem (Node.n_mem node) (Node.f_mem node)
                                   t_dsk (Node.f_dsk node))
                 nodes
    in unlines nlines

-- | Generate instance file data from instance objects
serializeInstances :: Cluster.InstanceList -> String
                   -> Cluster.NameList -> Cluster.NameList -> String
serializeInstances il csf ktn kti =
    let etn = map (\(idx, name) -> (idx, name ++ csf)) ktn
        eti = map (\(idx, name) -> (idx, name ++ csf)) kti
        instances = Container.elems il
        nlines = map
                 (\inst ->
                      let
                          iname = fromJust $ lookup (Instance.idx inst) eti
                          pnode = fromJust $ lookup (Instance.pnode inst) etn
                          snode = fromJust $ lookup (Instance.snode inst) etn
                      in
                        printf "%s|%d|%d|%s|%s|%s"
                               iname (Instance.mem inst) (Instance.dsk inst)
                               (Instance.run_st inst)
                               pnode snode
                 )
                 instances
    in unlines nlines

-- | Return a one-line summary of cluster state
printCluster :: Cluster.NodeList -> Cluster.InstanceList
             -> Cluster.NameList -> Cluster.NameList
             -> String
printCluster nl il ktn kti =
    let (bad_nodes, bad_instances) = Cluster.computeBadItems nl il
        ccv = Cluster.compCV nl
        nodes = Container.elems nl
        t_ram = truncate . sum . map Node.t_mem $ nodes
        t_dsk = truncate . sum . map Node.t_dsk $ nodes
        f_ram = sum . map Node.f_mem $ nodes
        f_dsk = sum . map Node.f_dsk $ nodes
    in
      printf "%5d %5d %5d %5d %6d %6d %6d %6d %.8f"
                 (length ktn) (length kti)
                 (length bad_nodes) (length bad_instances)
                 (t_ram::Integer) f_ram
                 ((t_dsk::Integer) `div` 1024) (f_dsk `div` 1024)
                 ccv


-- | Main function.
main :: IO ()
main = do
  cmd_args <- System.getArgs
  (opts, clusters) <- CLI.parseOpts cmd_args "hscan" options
                      defaultOptions optShowHelp

  when (optShowVer opts) $ do
         putStr $ CLI.showVersion "hscan"
         exitWith ExitSuccess

  let odir = optOutPath opts
      nlen = maximum . map length $ clusters

  unless (optNoHeader opts) $
         printf "%-*s %5s %5s %5s %5s %6s %6s %6s %6s %10s\n" nlen
                "Name" "Nodes" "Inst" "BNode" "BInst" "t_mem" "f_mem"
                "t_disk" "f_disk" "Score"

  mapM (\ name ->
            do
              printf "%-*s " nlen name
              hFlush stdout
              node_data <- getNodes name
              inst_data <- getInstances name
              (if isLeft(node_data)
               then putStrLn $ fromLeft node_data
               else if isLeft(inst_data)
                    then putStrLn $ fromLeft inst_data
                    else do
                      let ndata = fromRight node_data
                          idata = fromRight inst_data
                          (nl, il, csf, ktn, kti) =
                              Cluster.loadData ndata idata
                          (_, fix_nl) = Cluster.checkData nl il ktn kti
                      putStrLn $ printCluster fix_nl il ktn kti
                      when (optShowNodes opts) $ do
                           putStr $ Cluster.printNodes ktn fix_nl
                      let ndata = serializeNodes nl csf ktn
                          idata = serializeInstances il csf ktn kti
                          oname = odir </> name
                      writeFile (oname <.> "nodes") ndata
                      writeFile (oname <.> "instances") idata)
       ) clusters
  exitWith ExitSuccess