module Data.CSV.Table.Types (

  -- * Representation
    Table (..)
  , Row (..)
  , Col (..)
  , RowInfo
  , TField (..)
  , Order (..)

  -- * Accessors
  , getCols
  , getRows
  , lookupCol

  -- * Parsing
  , fromFile
  , fromString

  -- * Saving
  , toFile

  ) where

import           Text.Printf
import           Text.CSV
import           System.FilePath
import           Control.Applicative ((<$>))
import           Data.Maybe
import           Data.List (sort, elemIndex)
import qualified Data.Map.Strict as M

newtype Col  = C Field   deriving (Eq, Ord, Show)
newtype Row  = R [Field] deriving (Eq, Ord, Show)
type RowInfo = [(Col, Field)]

-----------------------------------------------------------------------------------
-- | Types
-----------------------------------------------------------------------------------

data Table  = T { dim :: Int, cols :: [Col], body :: [Row]}

{-@ measure width :: Row -> Int
    width (R xs) = (len xs)                                                     @-}

{-@ type ColsN N = {v:[Col] | (len v)   = N}                                    @-}
{-@ type RowN  N = {r:Row   | (width r) = N}                                    @-}
{-@ data Table   = T (dim :: Nat) (cols :: (ColsN dim)) (body :: [(RowN dim)])  @-}

{-@ getCols   :: t:Table -> ListN Field {(dim t)} @-}
getCols t = [c | C c <- cols t]

{-@ getRows   :: t:Table -> ListN Field {(dim t)} @-}
getRows t = [r | R r <- body t]

lookupCol :: Col -> RowInfo -> Field
lookupCol c cxs = fromMaybe err $ lookup c cxs
  where
    err         = printf "lookupCol: cannot find %s in %s" (show c) (show cxs)

--------------------------------------------------------------------------------
-- | Field Sorts
--------------------------------------------------------------------------------

data TField = FStr | FInt | FDbl
            deriving (Eq, Ord, Show)

data Order  = Asc | Dsc
            deriving (Eq, Ord, Show)
----------------------------------------------------------------------
-- | Converting to CSV
----------------------------------------------------------------------

fromCSV        :: CSV -> Table
fromCSV []     = error "fromCSV: Empty CSV with no rows!"
fromCSV (r:rs) = T n cs b
  where
    n          = length r
    cs         = [C x | x <- r]
    b          = mapMaybe (makeRow n) $ zip [0..] rs

makeRow :: Int -> (Int, Record) -> Maybe Row
makeRow n (i, xs)
  | length xs == n = Just $ R xs
  | empty xs       = Nothing
  | otherwise      = error $ printf "Row %d does not have %d columns:\n%s" i n (show xs)

empty :: Record -> Bool
empty = null . unwords

toCSV   :: Table -> CSV
toCSV t = [c | C c <- cols t] : [xs | R xs <- body t]

--------------------------------------------------------------------------------
-- | Parsing
--------------------------------------------------------------------------------

toFile   :: FilePath -> Table -> IO ()
toFile f = writeFile f . show

fromFile    :: FilePath -> IO Table
fromFile  f = fromString f <$> readFile f

fromString      :: FilePath -> String -> Table
fromString fp s = fromCSV $ parseCSV' fp s

parseCSV' fp s = case parseCSV fp s of
                   Right c -> c
                   Left e  -> error $ printf "parseCSV': %s" (show e)

--------------------------------------------------------------------------------
-- | Printing
--------------------------------------------------------------------------------

instance Show Table where
  show = printCSV . toCSV