{-| Parsing data from text-files

This module holds the code for loading the cluster state from text
files, as produced by gnt-node and gnt-instance list.

-}

module Ganeti.HTools.Text
    where

import Control.Monad

import Ganeti.HTools.Utils
import Ganeti.HTools.Loader
import Ganeti.HTools.Types
import qualified Ganeti.HTools.Node as Node
import qualified Ganeti.HTools.Instance as Instance

-- | Safe 'read' function returning data encapsulated in a Result
tryRead :: (Monad m, Read a) => String -> String -> m a
tryRead name s =
    let sols = readsPrec 0 s
    in case sols of
         (v, ""):[] -> return v
         (_, e):[] -> fail $ name ++ ": leftover characters when parsing '"
                      ++ s ++ "': '" ++ e ++ "'"
         _ -> fail $ name ++ ": cannot parse string '" ++ s ++ "'"

-- | Load a node from a field list
loadNode :: (Monad m) => [String] -> m (String, Node.Node)
loadNode (name:tm:nm:fm:td:fd:fo:[]) = do
  new_node <-
      if any (== "?") [tm,nm,fm,td,fd] || fo == "Y" then
          return $ Node.create name 0 0 0 0 0 True
      else do
        vtm <- tryRead name tm
        vnm <- tryRead name nm
        vfm <- tryRead name fm
        vtd <- tryRead name td
        vfd <- tryRead name fd
        return $ Node.create name vtm vnm vfm vtd vfd False
  return (name, new_node)
loadNode s = fail $ "Invalid/incomplete node data: '" ++ (show s) ++ "'"

-- | Load an instance from a field list
loadInst :: (Monad m) =>
            [(String, Int)] -> [String] -> m (String, Instance.Instance)
loadInst ktn (name:mem:dsk:status:pnode:snode:[]) = do
  pidx <- lookupNode ktn name pnode
  sidx <- (if null snode then return Node.noSecondary
           else lookupNode ktn name snode)
  vmem <- tryRead name mem
  vdsk <- tryRead name dsk
  when (sidx == pidx) $ fail $ "Instance " ++ name ++
           " has same primary and secondary node - " ++ pnode
  let newinst = Instance.create name vmem vdsk status pidx sidx
  return (name, newinst)
loadInst _ s = fail $ "Invalid/incomplete instance data: '" ++ (show s) ++ "'"

{- | Convert newline and delimiter-separated text.

This function converts a text in tabular format as generated by
@gnt-instance list@ and @gnt-node list@ to a list of objects using a
supplied conversion function.

-}
loadTabular :: (Monad m, Element a) =>
               String -> ([String] -> m (String, a))
            -> m ([(String, Int)], [(Int, a)])
loadTabular text_data convert_fn = do
  let lines_data = lines text_data
      rows = map (sepSplit '|') lines_data
  kerows <- mapM convert_fn rows
  return $ assignIndices kerows

loadData :: String -- ^ Node data in string format
         -> String -- ^ Instance data in string format
         -> IO (Result (Node.AssocList, Instance.AssocList))
loadData nfile ifile = do -- IO monad
  ndata <- readFile nfile
  idata <- readFile ifile
  return $ do
    {- node file: name t_mem n_mem f_mem t_disk f_disk -}
    (ktn, nl) <- loadTabular ndata loadNode
    {- instance file: name mem disk status pnode snode -}
    (_, il) <- loadTabular idata (loadInst ktn)
    return (nl, il)