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

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

import Language.Drasil hiding (Data, Matrix)
import Language.Drasil.Expr.Development (Expr(Matrix))

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

-- | 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 [Expr] -> [Expr] -> [Expr]
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 = (DataItem' -> FilePath -> Expr)
-> [DataItem'] -> [FilePath] -> [Expr]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith DataItem' -> FilePath -> Expr
readDataItem (NonEmpty DataItem' -> [DataItem']
forall a. NonEmpty a -> [a]
toList NonEmpty DataItem'
dis) (FilePath -> FilePath -> [FilePath]
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 = ([FilePath] -> Expr) -> [[FilePath]] -> [Expr]
forall a b. (a -> b) -> [a] -> [b]
map (([[Expr]] -> Expr
Matrix ([[Expr]] -> Expr) -> ([Expr] -> [[Expr]]) -> [Expr] -> Expr
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ([Expr] -> [[Expr]] -> [[Expr]]
forall a. a -> [a] -> [a]
:[])) ([Expr] -> Expr) -> ([FilePath] -> [Expr]) -> [FilePath] -> Expr
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
        (FilePath -> Expr) -> [FilePath] -> [Expr]
forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr (Space -> Space
getInnerSpace (Space -> Space) -> Space -> Space
forall a b. (a -> b) -> a -> b
$ CodeVarChunk
c CodeVarChunk -> Getting Space CodeVarChunk Space -> Space
forall s a. s -> Getting a s a -> a
^. Getting Space CodeVarChunk Space
forall c. HasSpace c => Getter c Space
Getter CodeVarChunk Space
typ))) ([[FilePath]] -> [Expr]) -> [[FilePath]] -> [Expr]
forall a b. (a -> b) -> a -> b
$ [[FilePath]] -> [[FilePath]]
forall a. [[a]] -> [[a]]
transpose ([[FilePath]] -> [[FilePath]]) -> [[FilePath]] -> [[FilePath]]
forall a b. (a -> b) -> a -> b
$
        (FilePath -> [FilePath]) -> [FilePath] -> [[FilePath]]
forall a b. (a -> b) -> [a] -> [b]
map (FilePath -> FilePath -> [FilePath]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm2) ([FilePath] -> [[FilePath]]) -> [FilePath] -> [[FilePath]]
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> [FilePath]
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 = ([[FilePath]] -> Expr) -> [[[FilePath]]] -> [Expr]
forall a b. (a -> b) -> [a] -> [b]
map ([[Expr]] -> Expr
Matrix ([[Expr]] -> Expr)
-> ([[FilePath]] -> [[Expr]]) -> [[FilePath]] -> Expr
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
        ([FilePath] -> [Expr]) -> [[FilePath]] -> [[Expr]]
forall a b. (a -> b) -> [a] -> [b]
map ((FilePath -> Expr) -> [FilePath] -> [Expr]
forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr (Space -> Space
getInnerSpace (Space -> Space) -> Space -> Space
forall a b. (a -> b) -> a -> b
$ CodeVarChunk
c CodeVarChunk -> Getting Space CodeVarChunk Space -> Space
forall s a. s -> Getting a s a -> a
^. Getting Space CodeVarChunk Space
forall c. HasSpace c => Getter c Space
Getter CodeVarChunk Space
typ)))) ([[[FilePath]]] -> [Expr]) -> [[[FilePath]]] -> [Expr]
forall a b. (a -> b) -> a -> b
$ [[[FilePath]]] -> [[[FilePath]]]
forall a. [[a]] -> [[a]]
transpose ([[[FilePath]]] -> [[[FilePath]]])
-> [[[FilePath]]] -> [[[FilePath]]]
forall a b. (a -> b) -> a -> b
$
        (FilePath -> [[FilePath]]) -> [FilePath] -> [[[FilePath]]]
forall a b. (a -> b) -> [a] -> [b]
map ((FilePath -> [FilePath]) -> [FilePath] -> [[FilePath]]
forall a b. (a -> b) -> [a] -> [b]
map (FilePath -> FilePath -> [FilePath]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm3) ([FilePath] -> [[FilePath]])
-> (FilePath -> [FilePath]) -> FilePath -> [[FilePath]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath -> [FilePath]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm2) ([FilePath] -> [[[FilePath]]]) -> [FilePath] -> [[[FilePath]]]
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> [FilePath]
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 = ([[FilePath]] -> Expr) -> [[[FilePath]]] -> [Expr]
forall a b. (a -> b) -> [a] -> [b]
map ([[Expr]] -> Expr
Matrix ([[Expr]] -> Expr)
-> ([[FilePath]] -> [[Expr]]) -> [[FilePath]] -> Expr
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
        ([FilePath] -> [Expr]) -> [[FilePath]] -> [[Expr]]
forall a b. (a -> b) -> [a] -> [b]
map ((FilePath -> Expr) -> [FilePath] -> [Expr]
forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr (Space -> Space
getInnerSpace (Space -> Space) -> Space -> Space
forall a b. (a -> b) -> a -> b
$ CodeVarChunk
c CodeVarChunk -> Getting Space CodeVarChunk Space -> Space
forall s a. s -> Getting a s a -> a
^. Getting Space CodeVarChunk Space
forall c. HasSpace c => Getter c Space
Getter CodeVarChunk Space
typ))) ([[FilePath]] -> [[Expr]])
-> ([[FilePath]] -> [[FilePath]]) -> [[FilePath]] -> [[Expr]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. [[FilePath]] -> [[FilePath]]
forall a. [[a]] -> [[a]]
transpose) ([[[FilePath]]] -> [Expr]) -> [[[FilePath]]] -> [Expr]
forall a b. (a -> b) -> a -> b
$
        [[[FilePath]]] -> [[[FilePath]]]
forall a. [[a]] -> [[a]]
transpose ([[[FilePath]]] -> [[[FilePath]]])
-> [[[FilePath]]] -> [[[FilePath]]]
forall a b. (a -> b) -> a -> b
$ (FilePath -> [[FilePath]]) -> [FilePath] -> [[[FilePath]]]
forall a b. (a -> b) -> [a] -> [b]
map ((FilePath -> [FilePath]) -> [FilePath] -> [[FilePath]]
forall a b. (a -> b) -> [a] -> [b]
map (FilePath -> FilePath -> [FilePath]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm3) ([FilePath] -> [[FilePath]])
-> (FilePath -> [FilePath]) -> FilePath -> [[FilePath]]
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FilePath -> FilePath -> [FilePath]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm2) ([FilePath] -> [[[FilePath]]]) -> [FilePath] -> [[[FilePath]]]
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> [FilePath]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm1 FilePath
s
      readData Data'
_ FilePath
_ = FilePath -> [Expr]
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 CodeVarChunk -> Getting Space CodeVarChunk Space -> Space
forall s a. s -> Getting a s a -> a
^. Getting Space CodeVarChunk Space
forall c. HasSpace c => Getter c Space
Getter CodeVarChunk Space
typ) FilePath
s
      readDataItem (DI CodeVarChunk
c [FilePath
dlm]) FilePath
s = Space -> [FilePath] -> Expr
strListAsExpr (CodeVarChunk
c CodeVarChunk -> Getting Space CodeVarChunk Space -> Space
forall s a. s -> Getting a s a -> a
^. Getting Space CodeVarChunk Space
forall c. HasSpace c => Getter c Space
Getter CodeVarChunk Space
typ) (FilePath -> FilePath -> [FilePath]
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 CodeVarChunk -> Getting Space CodeVarChunk Space -> Space
forall s a. s -> Getting a s a -> a
^. Getting Space CodeVarChunk Space
forall c. HasSpace c => Getter c Space
Getter CodeVarChunk Space
typ)
        ((FilePath -> [FilePath]) -> [FilePath] -> [[FilePath]]
forall a b. (a -> b) -> [a] -> [b]
map (FilePath -> FilePath -> [FilePath]
forall a. Eq a => [a] -> [a] -> [[a]]
splitOn FilePath
dlm2) ([FilePath] -> [[FilePath]]) -> [FilePath] -> [[FilePath]]
forall a b. (a -> b) -> a -> b
$ FilePath -> FilePath -> [FilePath]
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
_ = FilePath -> Expr
forall a. HasCallStack => FilePath -> a
error FilePath
"readWithDataDesc does not yet support lists with 3 or more dimensions"
  [Expr] -> IO [Expr]
forall a. a -> IO a
forall (m :: * -> *) a. Monad m => a -> m a
return ([Expr] -> IO [Expr]) -> [Expr] -> IO [Expr]
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 Data' -> [Data'] -> [Data']
forall a. a -> [a] -> [a]
: Data' -> [Data'] -> [Data']
forall a. a -> [a] -> [a]
intersperse Data'
junk ((CodeVarChunk -> Data') -> [CodeVarChunk] -> [Data']
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 CodeVarChunk -> Getting Space CodeVarChunk Space -> Space
forall s a. s -> Getting a s a -> a
^. Getting Space CodeVarChunk Space
forall c. HasSpace c => Getter c Space
Getter CodeVarChunk Space
typ) CodeVarChunk
d
        toData' :: Space -> CodeVarChunk -> Data'
toData' t :: Space
t@(Vect Space
_) CodeVarChunk
d = CodeVarChunk -> [FilePath] -> Data'
list CodeVarChunk
d
          (Int -> [FilePath] -> [FilePath]
forall a. Int -> [a] -> [a]
take (Space -> Int
getDimension Space
t) ([FilePath
", ", FilePath
"; "] [FilePath] -> [FilePath] -> [FilePath]
forall a. [a] -> [a] -> [a]
++ (FilePath -> FilePath) -> FilePath -> [FilePath]
forall a. (a -> a) -> a -> [a]
iterate (Char
':'Char -> FilePath -> FilePath
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 = Integer -> Expr
forall r. LiteralC r => Integer -> r
int (FilePath -> Integer
forall a. Read a => FilePath -> a
read FilePath
s :: Integer)
strAsExpr Space
Natural  FilePath
s = Integer -> Expr
forall r. LiteralC r => Integer -> r
int (FilePath -> Integer
forall a. Read a => FilePath -> a
read FilePath
s :: Integer)
strAsExpr Space
Real     FilePath
s = Double -> Expr
forall r. LiteralC r => Double -> r
dbl (FilePath -> Double
forall a. Read a => FilePath -> a
read FilePath
s :: Double)
strAsExpr Space
Rational FilePath
s = Double -> Expr
forall r. LiteralC r => Double -> r
dbl (FilePath -> Double
forall a. Read a => FilePath -> a
read FilePath
s :: Double)
strAsExpr Space
String   FilePath
s = FilePath -> Expr
forall r. LiteralC r => FilePath -> r
str FilePath
s
strAsExpr Space
_        FilePath
_ = FilePath -> Expr
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 Int -> Int -> Int
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 = FilePath -> FilePath -> FilePath -> (FilePath, FilePath)
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 [a] -> [a] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` [a]
s then
          ([a]
acc, [a] -> [a] -> [a]
forall {a}. Eq a => [a] -> [a] -> [a]
dropDelim [a]
d [a]
s) else [a] -> [a] -> [a] -> ([a], [a])
splitAtFirst' ([a]
acc[a] -> [a] -> [a]
forall 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 a -> a -> Bool
forall a. Eq a => a -> a -> Bool
== a
s then [a] -> [a] -> [a]
dropDelim [a]
ds [a]
ss
          else FilePath -> [a]
forall a. HasCallStack => FilePath -> a
error FilePath
"impossible"
        dropDelim [] [a]
s = [a]
s
        dropDelim [a]
_ [] = FilePath -> [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 [(FilePath -> Expr) -> [FilePath] -> [Expr]
forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr Space
t) [FilePath]
ss]
strListAsExpr Space
_ [FilePath]
_ = FilePath -> Expr
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 ([[Expr]] -> Expr) -> [[Expr]] -> Expr
forall a b. (a -> b) -> a -> b
$ ([FilePath] -> [Expr]) -> [[FilePath]] -> [[Expr]]
forall a b. (a -> b) -> [a] -> [b]
map ((FilePath -> Expr) -> [FilePath] -> [Expr]
forall a b. (a -> b) -> [a] -> [b]
map (Space -> FilePath -> Expr
strAsExpr Space
t)) [[FilePath]]
sss
strList2DAsExpr Space
_ [[FilePath]]
_ = FilePath -> Expr
forall a. HasCallStack => FilePath -> a
error FilePath
"strLists2DAsExprs called on non-2D-vector space"