--Cookbook.Project.Scribe
--Scribe is a tool for communicating with SCRIBE databases.
module Cookbook.Project.Scribe.Scribe(
  Frame(..),Column,Row,Table,Database,
  isRow,isColumn,isFrame,isTable,
  makeName,
  parseFrame,parseColumn,parseRow,parseTable,
  readColumn,readRow,readTable,readDatabase,
  getTable,getRow,getColumn,getFrame,
  qGetRow,qGetColumn,qGetFrame) where

import qualified Cookbook.Essential.Common             as Cm
import qualified Cookbook.Essential.Continuous         as Ct
import qualified Cookbook.Ingredients.Lists.Modify     as Md
import qualified Cookbook.Ingredients.Lists.Access     as Ac
import qualified Cookbook.Ingredients.Tupples.Look     as Lk
import qualified Cookbook.Ingredients.Functional.Break as Br
import qualified Cookbook.Recipes.Sanitize             as St

--Data
data Frame = Frame {keyval :: String, entry :: String} | Promise {keyval :: String, link :: String}  deriving (Eq,Show)

--Type synonyms
type Column = (String,[Frame])
type Row = (String,[Column])
type Table = (String,[Row])
type Database = [Table]

--Standard information
isRow     x = and [x `Ac.contains` "row",    length x > 4]
isColumn  x = and [x `Ac.contains` "column", length x > 4]
isFrame   x = and $ map ((flip elem) x) "<>"
isTable   x = and [x `Ac.contains` "table",length x > 4]

makeName :: String -> String -> String
makeName a b = St.blacklist (Ct.after a b) "{ "

--Parsing
parseFrame :: String -> Frame
parseFrame x | not $ isFrame x = error (x ++ " was passed to parseFrame, but does not conform to standard.")
parseFrame x
  | ('-','-') `Ac.surrounds` inner = Promise {keyval = Md.between inner ('-','-'), link = Ct.after x '>'}
  | otherwise = Frame {keyval = inner, entry = Ct.after x '>'} -- Surrounded in <-->? It's a link.
  where inner = Md.between x ('<','>')

parseColumn :: (String,[String]) -> Column -- Expects the column declaration, and the relevant lines.
parseColumn (a,b) = (makeName a "column ",map parseFrame b)

parseRow :: (String,[(String,[String])]) -> Row
parseRow (a,b) = (makeName a "row ",map parseColumn b)

parseTable :: (String,[(String,[(String,[String])])]) -> Table
parseTable (a,b) = (makeName a "table ",map parseRow b)

--Reading
readColumn :: [String] -> (String,[String])
readColumn [] = error "Column either does not exist or does not conform to standards."
readColumn (x:xs) | not  $ isColumn x = readColumn xs
readColumn (x:xs) = (x,Md.linesBetween (x:xs) ("{","}"))

readRow :: [String] -> (String,[(String,[String])])
readRow [] = error "Row either does not exist or does not conform to standards."
readRow (x:xs) | not $ isRow x = readRow xs
readRow (x:xs) = (x, map readColumn splitup)
  where splitup = Br.splitBool (\c -> not $ c `Ac.contains` "}") rowScope
        rowScope = Md.linesBetween (x:xs) ("{","}}")

readTable :: [String] -> (String,[(String,[(String,[String])])])
readTable [] = error "Table either does not exist or does not conform to standards."
readTable (x:xs) | not $ isTable x = readTable xs
readTable (x:xs) = (x,map readRow splitup)
  where splitup = Br.splitBool (\c -> not $ c `Ac.contains` "}}") tableScope;
        tableScope = Md.linesBetween (x:xs) ("{","}}}")

--Final functions
readDatabase :: [String] -> Database
readDatabase (x:xs)
  | isTable x = parseTable (readTable (x:xs)) : readDatabase (Ct.after xs "}}}")
  | otherwise = readDatabase xs

--Accessors
getTable :: String -> Database -> Table
getTable a b = (a,(head $ Lk.lookList b a))

getRow :: (String,String) -> Database -> Row
getRow (a,b) c = (b,head $ Lk.lookList (snd (getTable a c)) b)

getColumn :: (String,String,String) -> Database -> Column
getColumn (a,b,c) d = (c,head $ Lk.lookList (snd (getRow (a,b) d)) c)

getFrame :: (String,String,String,String) -> Database -> Frame
getFrame (a,b,c,d) e = let frames = (snd $ getColumn (a,b,c) e) in head $ Lk.lookList (zip (map keyval frames) frames) d

--Shorthand accessors
qGetRow :: String -> Database -> Row
qGetRow x z= getRow (a,b) z
  where (a,b) = let (f:g:[]) = Md.splitOn x '.' in (f,g)

qGetColumn :: String -> Database -> Column
qGetColumn x z = getColumn (a,b,c) z
  where (a,b,c) = let (f:g:h:[]) = Md.splitOn x '.' in (f,g,h)

qGetFrame :: String -> Database -> Frame
qGetFrame x z = getFrame (a,b,c,d) z
  where (a,b,c,d) = let (f:g:h:j:[]) = Md.splitOn x '.' in (f,g,h,j)