module Language.Drasil.Code.DataDesc where

import Language.Drasil.Chunk.Code (CodeVarChunk)

import Data.List (nub)
import Data.List.NonEmpty (NonEmpty(..), fromList)

-- | A 'DataItem' is just a 'CodeVarChunk' (a piece of data).
type DataItem = CodeVarChunk

-- New DataDesc
-- | A data description either has data connected to other pieces of data, or a single piece of data.
data DataDesc' = DD Data' Delimiter DataDesc' | End Data'

-- Data can either contain a single 'DataItem'', 'Data', or 'Junk'. See @source@ for more details on each constructor.
data Data' = Datum DataItem' -- ^ Single data item.
  -- | To be used in cases where multiple list-type data have their
  -- elements intermixed, and thus need to be described together.
  | Data
    (NonEmpty DataItem') -- The DataItems being simultaneously described.
                -- The intra-list delimiters for any shared dimensions should
                -- be the same between the DataItems. For example, if mixing 2
                -- lists of same dimension, the intra-list delimiters must be
                -- the same between the two lists. If mixing a 1-D list with a
                -- 2-D list, the first delimiter must be the same, but the 2nd
                -- delimiter for the 2-D list is not constrained because there
                -- is no corresponding delimiter for the 1-D list.
    Integer -- Degree of intermixing
            -- 0 <= degree of intermixing <= minimum dimension of the DataItems
            -- Ex. 2 2-D lists with 1 degree of intermixing:
              -- x11 x12, y11 y12; x21 x22, y21 y22
            -- Ex. 2 2-D lists with 2 degrees of intermixing:
              -- x11, y11 x12, y12; x21, y21 x22, y22
    Delimiter -- Delimiter between elements from different lists
  | Junk -- ^ Data that can be ignored/skipped over.

-- | A piece of data that contains the datum described and delimeters between elements.
-- The size of the list of delimiters should be equal to the dimension of datum.
data DataItem' = DI
  CodeVarChunk -- The datum being described
  [Delimiter] -- Delimiters between list elements.
              -- Size of list should equal dimension of datum
              -- Ex. a 1-D list needs 1 delimiter, a 2-D list needs 2 delimiters
              -- Outermost delimiter first, innermost last

-- | Delimiter between elements of data.
type Delimiter = String

-- New constructors

-- | Organize a list of data with a given 'Delimiter' into a 'DataDesc''.
dataDesc :: [Data'] -> Delimiter -> DataDesc'
dataDesc :: [Data'] -> Delimiter -> DataDesc'
dataDesc [Data'
d] Delimiter
_ = Data' -> DataDesc'
End Data'
d
dataDesc (Data'
d:[Data']
ds) Delimiter
dlm = Data' -> Delimiter -> DataDesc' -> DataDesc'
DD Data'
d Delimiter
dlm ([Data'] -> Delimiter -> DataDesc'
dataDesc [Data']
ds Delimiter
dlm)
dataDesc [] Delimiter
_ = forall a. HasCallStack => Delimiter -> a
error Delimiter
"DataDesc must have at least one data item"

-- | Constructor for creating a single datum.
singleton' :: CodeVarChunk -> Data'
singleton' :: DataItem -> Data'
singleton' DataItem
d = DataItem' -> Data'
Datum forall a b. (a -> b) -> a -> b
$ DataItem -> [Delimiter] -> DataItem'
DI DataItem
d []

-- | Constructor for creating data from a description of the data and a list of delimiters with a size equal to the dimension.
list :: CodeVarChunk -> [Delimiter] -> Data'
list :: DataItem -> [Delimiter] -> Data'
list DataItem
d [Delimiter]
dlms = DataItem' -> Data'
Datum forall a b. (a -> b) -> a -> b
$ DataItem -> [Delimiter] -> DataItem'
DI DataItem
d [Delimiter]
dlms

-- | Weaves elements of data together given a list of data items, a degree of intermixing, and a delimiter for the data.
interwovenLists :: [DataItem'] -> Integer -> Delimiter -> Data'
interwovenLists :: [DataItem'] -> Integer -> Delimiter -> Data'
interwovenLists [] Integer
_ Delimiter
_ = forall a. HasCallStack => Delimiter -> a
error Delimiter
"interwovenLists must be passed a non-empty list"
interwovenLists [DataItem']
ds Integer
i Delimiter
dl = NonEmpty DataItem' -> Integer -> Delimiter -> Data'
Data (forall a. [a] -> NonEmpty a
fromList [DataItem']
ds) Integer
i Delimiter
dl

-- | Constructor for data that should be skipped.
junk :: Data'
junk :: Data'
junk = Data'
Junk

-- Old DataDesc
-- | Older version of 'DataDesc''. Holds a list of 'Data'.
type DataDesc = [Data]

-- | Older version of 'Delimiter'. Holds a 'Char'.
type Delim = Char  -- delimiter

-- | Older version of 'Data''.
data Data = Singleton DataItem                      -- ^ Single datum.
          | JunkData                                -- ^ Junk data (can be skipped).
          | Line LinePattern Delim                  -- ^ Single-line pattern of data with a delimiter.
          | Lines LinePattern (Maybe Integer) Delim -- ^ Multi-line data.
                                                    -- @Maybe Int@ determines the number of lines.
                                                    -- If it is Nothing, then it is unknown so go to end of file.
-- | Determines the pattern of data.
data LinePattern = Straight [DataItem] -- ^ Line of data with no pattern.
                 | Repeat [DataItem]   -- ^ Line of data with repeated pattern.

-- Old constructors/helpers

-- | Constructor for a single datum.
singleton :: DataItem -> Data
singleton :: DataItem -> Data
singleton = DataItem -> Data
Singleton

-- | Constructor for junk data.
junkLine :: Data
junkLine :: Data
junkLine = Data
JunkData

-- | Constructor for a single line of data.
singleLine :: LinePattern -> Delim -> Data
singleLine :: LinePattern -> Delim -> Data
singleLine = LinePattern -> Delim -> Data
Line

-- | Constructor for an unknown amount of lines of data.
multiLine :: LinePattern -> Delim -> Data
multiLine :: LinePattern -> Delim -> Data
multiLine LinePattern
l = LinePattern -> Maybe Integer -> Delim -> Data
Lines LinePattern
l forall a. Maybe a
Nothing

-- | Constructor for multi-line data with a specified number of lines.
multiLine' :: LinePattern -> Integer -> Delim -> Data
multiLine' :: LinePattern -> Integer -> Delim -> Data
multiLine' LinePattern
l Integer
i = LinePattern -> Maybe Integer -> Delim -> Data
Lines LinePattern
l (forall a. a -> Maybe a
Just Integer
i)

-- | Constructor for data with no pattern.
straight :: [DataItem] -> LinePattern
straight :: [DataItem] -> LinePattern
straight = [DataItem] -> LinePattern
Straight

-- | Constructor for data with a repeated pattern.
repeated :: [DataItem] -> LinePattern
repeated :: [DataItem] -> LinePattern
repeated = [DataItem] -> LinePattern
Repeat

-- | Checks if a piece of 'Data' is 'JunkData'.
isJunk :: Data -> Bool
isJunk :: Data -> Bool
isJunk Data
JunkData = Bool
True
isJunk Data
_ = Bool
False

-- | Checks if a piece of 'Data' is only a single line.
isLine :: Data -> Bool
isLine :: Data -> Bool
isLine Line{} = Bool
True
isLine Data
_ = Bool
False

-- | Checks if a piece of 'Data' is multi-line.
isLines :: Data -> Bool
isLines :: Data -> Bool
isLines Lines{} = Bool
True
isLines Data
_ = Bool
False

-- | Gets the data inputs from a 'DataDesc'.
getInputs :: DataDesc -> [DataItem]
getInputs :: DataDesc -> [DataItem]
getInputs DataDesc
d = forall a. Eq a => [a] -> [a]
nub forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Data -> [DataItem]
getDataInputs DataDesc
d

-- | Helper that gets data inputs from 'Data'.
getDataInputs :: Data -> [DataItem]
getDataInputs :: Data -> [DataItem]
getDataInputs (Singleton DataItem
v) = [DataItem
v]
getDataInputs (Line LinePattern
lp Delim
_) = LinePattern -> [DataItem]
getPatternInputs LinePattern
lp
getDataInputs (Lines LinePattern
lp Maybe Integer
_ Delim
_) = LinePattern -> [DataItem]
getPatternInputs LinePattern
lp
getDataInputs Data
JunkData = []

-- | Helper that gets the data inputs from single-line or multi-line data.
getPatternInputs :: LinePattern -> [DataItem]
getPatternInputs :: LinePattern -> [DataItem]
getPatternInputs (Straight [DataItem]
vs) = [DataItem]
vs
getPatternInputs (Repeat [DataItem]
vs) = [DataItem]
vs