-- | Defines functions for reading values from a file corresponding to a DataDesc
module Language.Drasil.Code.Imperative.ReadInput (
  sampleInputDD, readWithDataDesc
) where

import Language.Drasil hiding (Data, Matrix, CodeVarChunk)
import Language.Drasil.Code.DataDesc (DataDesc'(..), Data'(..), DataItem'(..),
  Delimiter, dataDesc, junk, list, singleton')
import Language.Drasil.Chunk.Code (CodeVarChunk)
import Language.Drasil.Expr.Development (Expr(Matrix))

import Control.Lens ((^.))
import Data.List (intersperse, isPrefixOf, transpose)
import Data.List.Split (splitOn)
import Data.List.NonEmpty (NonEmpty(..), toList)

-- | Reads data from a file and converts the values to 'Expr's. The file must be
-- formatted according to the 'DataDesc'' passed as a parameter.
readWithDataDesc :: FilePath -> DataDesc' -> IO [Expr]
readWithDataDesc :: FilePath -> DataDesc' -> IO [Expr]
readWithDataDesc FilePath
fp DataDesc'
ddsc = do
  FilePath
ins <- FilePath -> IO FilePath
readFile FilePath
fp
  let readDD :: DataDesc' -> String -> [Expr]
      readDD :: DataDesc' -> FilePath -> [Expr]
readDD (DD Data'
ds FilePath
dlm DataDesc'
dd) FilePath
s = let (FilePath
dat,FilePath
rest) = FilePath -> FilePath -> (FilePath, FilePath)
splitAtFirst FilePath
s FilePath
dlm in
        Data' -> FilePath -> [Expr]
readData Data'
ds FilePath
dat forall a. [a] -> [a] -> [a]
++ DataDesc' -> FilePath -> [Expr]
readDD DataDesc'
dd FilePath
rest
      readDD (End Data'
d) FilePath
s = Data' -> FilePath -> [Expr]
readData Data'
d FilePath
s
      readData :: Data' -> String -> [Expr]
      readData :: Data' -> FilePath -> [Expr]
readData Data'
Junk FilePath
_ = []
      readData (Datum DataItem'
d) FilePath
s = [DataItem' -> FilePath -> Expr
readDataItem DataItem'
d FilePath
s]
      readData (Data NonEmpty DataItem'
dis Integer
0 FilePath
d) FilePath
s = forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith DataItem' -> FilePath -> Expr
readDataItem (forall a. NonEmpty a -> [a]
toList NonEmpty DataItem'
dis) (forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
d FilePath
s)
      readData (Data ((DI CodeVarChunk
c [FilePath
dlm1]):|[DataItem']
_) Integer
1 FilePath
dlm2) FilePath
s = forall a b. (a -> b) -> [a] -> [b]
map (([[Expr]] -> Expr
Matrix forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall a. a -> [a] -> [a]
:[])) forall b c a. (b -> c) -> (a -> b) -> a -> c
.
        forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr (Space -> Space
getInnerSpace forall a b. (a -> b) -> a -> b
$ CodeVarChunk
c forall s a. s -> Getting a s a -> a
^. forall c. HasSpace c => Getter c Space
typ))) forall a b. (a -> b) -> a -> b
$ forall a. [[a]] -> [[a]]
transpose forall a b. (a -> b) -> a -> b
$
        forall a b. (a -> b) -> [a] -> [b]
map (forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm2) forall a b. (a -> b) -> a -> b
$ forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm1 FilePath
s
      readData (Data ((DI CodeVarChunk
c [FilePath
dlm1, FilePath
dlm3]):|[DataItem']
_) Integer
1 FilePath
dlm2) FilePath
s = forall a b. (a -> b) -> [a] -> [b]
map ([[Expr]] -> Expr
Matrix forall b c a. (b -> c) -> (a -> b) -> a -> c
.
        forall a b. (a -> b) -> [a] -> [b]
map (forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr (Space -> Space
getInnerSpace forall a b. (a -> b) -> a -> b
$ CodeVarChunk
c forall s a. s -> Getting a s a -> a
^. forall c. HasSpace c => Getter c Space
typ)))) forall a b. (a -> b) -> a -> b
$ forall a. [[a]] -> [[a]]
transpose forall a b. (a -> b) -> a -> b
$
        forall a b. (a -> b) -> [a] -> [b]
map (forall a b. (a -> b) -> [a] -> [b]
map (forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm3) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm2) forall a b. (a -> b) -> a -> b
$ forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm1 FilePath
s
      readData (Data ((DI CodeVarChunk
c [FilePath
dlm1, FilePath
dlm2]):|[DataItem']
_) Integer
2 FilePath
dlm3) FilePath
s = forall a b. (a -> b) -> [a] -> [b]
map ([[Expr]] -> Expr
Matrix forall b c a. (b -> c) -> (a -> b) -> a -> c
.
        forall a b. (a -> b) -> [a] -> [b]
map (forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr (Space -> Space
getInnerSpace forall a b. (a -> b) -> a -> b
$ CodeVarChunk
c forall s a. s -> Getting a s a -> a
^. forall c. HasSpace c => Getter c Space
typ))) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. [[a]] -> [[a]]
transpose) forall a b. (a -> b) -> a -> b
$
        forall a. [[a]] -> [[a]]
transpose forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (forall a b. (a -> b) -> [a] -> [b]
map (forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm3) forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm2) forall a b. (a -> b) -> a -> b
$ forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm1 FilePath
s
      readData Data'
_ FilePath
_ = forall a. HasCallStack => FilePath -> a
error FilePath
"Invalid degree of intermixing in DataDesc or list with more than 2 dimensions (not yet supported)"
      -- Below match is an attempt at a generic match for Data, but it doesn't
      -- work because the following are needed:
      --   - 1-D Vect Expr constructor
      --   - A map function on Expr Vects (exprVectMap)
      --   - A transpose function on Expr Vects (exprVectTranspose)
      -- readData (Data ((DI c dlms):dis) i dlm2) s = let (ls,rs) = splitAt i
      --   dlms in transposeData i $ data (ls ++ [dlm] ++ rs) (getInnerType $ c ^. typ) s
      readDataItem :: DataItem' -> String -> Expr
      readDataItem :: DataItem' -> FilePath -> Expr
readDataItem (DI CodeVarChunk
c []) FilePath
s = Space -> FilePath -> Expr
strAsExpr (CodeVarChunk
c forall s a. s -> Getting a s a -> a
^. forall c. HasSpace c => Getter c Space
typ) FilePath
s
      readDataItem (DI CodeVarChunk
c [FilePath
dlm]) FilePath
s = Space -> [FilePath] -> Expr
strListAsExpr (CodeVarChunk
c forall s a. s -> Getting a s a -> a
^. forall c. HasSpace c => Getter c Space
typ) (forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm FilePath
s)
      readDataItem (DI CodeVarChunk
c [FilePath
dlm1, FilePath
dlm2]) FilePath
s = Space -> [[FilePath]] -> Expr
strList2DAsExpr (CodeVarChunk
c forall s a. s -> Getting a s a -> a
^. forall c. HasSpace c => Getter c Space
typ)
        (forall a b. (a -> b) -> [a] -> [b]
map (forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm2) forall a b. (a -> b) -> a -> b
$ forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm1 FilePath
s)
      -- FIXME: Since the representation for vectors in Expr is Matrix, and that constructor accepts a 2-D list, building a 3-D or higher matrix is not straightforward. This would be easier if Expr had a constructor for 1-D vectors, which could be nested to achieve n-dimensional structures.
      readDataItem (DI CodeVarChunk
_ [FilePath]
_) FilePath
_ = forall a. HasCallStack => FilePath -> a
error FilePath
"readWithDataDesc does not yet support lists with 3 or more dimensions"
  forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ DataDesc' -> FilePath -> [Expr]
readDD DataDesc'
ddsc FilePath
ins

-- data :: [Delimiter] -> Space -> String -> Expr
-- data [] sp s = strAsExpr sp s
-- data (d:ds) = Vect $ map (data ds) (splitOn d s)

-- transposeData :: Integer -> (Expr -> Expr)
-- transposeData 1 = exprVectTranspose
-- transposeData n = exprVectMap exprVectTranspose . transposeData (n-1)

-- | Defines the DataDesc for the file containing a sample data set, which a
-- user must supply if they want to generate a sample input file.
sampleInputDD :: [CodeVarChunk] -> DataDesc'
sampleInputDD :: [CodeVarChunk] -> DataDesc'
sampleInputDD [CodeVarChunk]
ds = [Data'] -> FilePath -> DataDesc'
dataDesc (Data'
junk forall a. a -> [a] -> [a]
: forall a. a -> [a] -> [a]
intersperse Data'
junk (forall a b. (a -> b) -> [a] -> [b]
map CodeVarChunk -> Data'
toData [CodeVarChunk]
ds)) FilePath
"\n"
  where toData :: CodeVarChunk -> Data'
toData CodeVarChunk
d = Space -> CodeVarChunk -> Data'
toData' (CodeVarChunk
d forall s a. s -> Getting a s a -> a
^. forall c. HasSpace c => Getter c Space
typ) CodeVarChunk
d
        toData' :: Space -> CodeVarChunk -> Data'
toData' t :: Space
t@(Vect Space
_) CodeVarChunk
d = CodeVarChunk -> [FilePath] -> Data'
list CodeVarChunk
d
          (forall a. Int -> [a] -> [a]
take (Space -> Int
getDimension Space
t) ([FilePath
", ", FilePath
"; "] forall a. [a] -> [a] -> [a]
++ forall a. (a -> a) -> a -> [a]
iterate (Char
':'forall a. a -> [a] -> [a]
:) FilePath
":"))
        toData' Space
_ CodeVarChunk
d = CodeVarChunk -> Data'
singleton' CodeVarChunk
d

-- helpers

-- | Converts a 'String' to an 'Expr' of a given 'Space'.
strAsExpr :: Space -> String -> Expr
strAsExpr :: Space -> FilePath -> Expr
strAsExpr Space
Integer  FilePath
s = forall r. LiteralC r => Integer -> r
int (forall a. Read a => FilePath -> a
read FilePath
s :: Integer)
strAsExpr Space
Natural  FilePath
s = forall r. LiteralC r => Integer -> r
int (forall a. Read a => FilePath -> a
read FilePath
s :: Integer)
strAsExpr Space
Real     FilePath
s = forall r. LiteralC r => Double -> r
dbl (forall a. Read a => FilePath -> a
read FilePath
s :: Double)
strAsExpr Space
Rational FilePath
s = forall r. LiteralC r => Double -> r
dbl (forall a. Read a => FilePath -> a
read FilePath
s :: Double)
strAsExpr Space
String   FilePath
s = forall r. LiteralC r => FilePath -> r
str FilePath
s
strAsExpr Space
_        FilePath
_ = forall a. HasCallStack => FilePath -> a
error FilePath
"strAsExpr should only be numeric space or string"

-- | Gets the dimension of a 'Space'.
getDimension :: Space -> Int
getDimension :: Space -> Int
getDimension (Vect Space
t) = Int
1 forall a. Num a => a -> a -> a
+ Space -> Int
getDimension Space
t
getDimension Space
_ = Int
0

-- | Splits a string at the first (and only the first) occurrence of a delimiter.
-- The delimiter is dropped from the result.
splitAtFirst :: String -> Delimiter -> (String, String)
splitAtFirst :: FilePath -> FilePath -> (FilePath, FilePath)
splitAtFirst = forall {a}. Eq a => [a] -> [a] -> [a] -> ([a], [a])
splitAtFirst' []
  where splitAtFirst' :: [a] -> [a] -> [a] -> ([a], [a])
splitAtFirst' [a]
acc [] [a]
_ = ([a]
acc, [])
        splitAtFirst' [a]
acc s :: [a]
s@(a
h:[a]
t) [a]
d = if [a]
d forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [a]
s then
          ([a]
acc, forall {a}. Eq a => [a] -> [a] -> [a]
dropDelim [a]
d [a]
s) else [a] -> [a] -> [a] -> ([a], [a])
splitAtFirst' ([a]
accforall a. [a] -> [a] -> [a]
++[a
h]) [a]
t [a]
d
        dropDelim :: [a] -> [a] -> [a]
dropDelim (a
d:[a]
ds) (a
s:[a]
ss) = if a
d forall a. Eq a => a -> a -> Bool
== a
s then [a] -> [a] -> [a]
dropDelim [a]
ds [a]
ss
          else forall a. HasCallStack => FilePath -> a
error FilePath
"impossible"
        dropDelim [] [a]
s = [a]
s
        dropDelim [a]
_ [] = forall a. HasCallStack => FilePath -> a
error FilePath
"impossible"

-- | Converts a list of 'String's to a Matrix 'Expr' of a given 'Space'.
strListAsExpr :: Space -> [String] -> Expr
strListAsExpr :: Space -> [FilePath] -> Expr
strListAsExpr (Vect Space
t) [FilePath]
ss = [[Expr]] -> Expr
Matrix [forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr Space
t) [FilePath]
ss]
strListAsExpr Space
_ [FilePath]
_ = forall a. HasCallStack => FilePath -> a
error FilePath
"strListsAsExpr called on non-vector space"

-- | Converts a 2D list of 'String's to a Matrix 'Expr' of a given 'Space'.
strList2DAsExpr :: Space -> [[String]] -> Expr
strList2DAsExpr :: Space -> [[FilePath]] -> Expr
strList2DAsExpr (Vect (Vect Space
t)) [[FilePath]]
sss = [[Expr]] -> Expr
Matrix forall a b. (a -> b) -> a -> b
$ forall a b. (a -> b) -> [a] -> [b]
map (forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr Space
t)) [[FilePath]]
sss
strList2DAsExpr Space
_ [[FilePath]]
_ = forall a. HasCallStack => FilePath -> a
error FilePath
"strLists2DAsExprs called on non-2D-vector space"