packages feed

lhae-0.0.2: src/Model/Grid.hs

module Model.Grid (Grid,new,getCell,setCell,rewriteCell
                  ,updateCell,updateCellIO,resizeTo
                  ,addRow,addColumn,deleteColumns,deleteRows
                  ,updateRowLabel,updateColumnLabel
                  ,columnIndex,rowIndex
                  ,setFilePath,getFilePath
                  ,getColumnValues)
    where

import Control.Monad (forM,forM_)
import Control.Applicative ((<$>))
import Data.IORef
import Util.DynArray (DynArray)
import qualified Util.DynArray as Dyn
import Model.Cell (Cell)
import qualified Model.Cell as Cell
import Model.CellContent (CellExpr(..),CellValue(..),Reference(..)
                         ,NamedReference(..),CompileReason(..))
import qualified Model.CellContent as CellContent
import Model.CellExpression.Parser (parse)
import Model.GridHeader (Header)
import qualified Model.GridHeader as Header
import CellCoordinate (CellCoord,coordRange)
import qualified View

data Grid = Grid { cells :: DynArray CellCoord Cell
                 , rowHeader :: Header
                 , columnHeader :: Header
                 , filePath :: IORef (Maybe FilePath)
                 , numViewGridRows :: IO Int
                 , numViewGridCols :: IO Int
                 }

new :: View.Info -> IO Grid
new viewInfo = do
  cells <- Dyn.newMatrix (1,1) Cell.empty
  rowHeader <- Header.new
  columnHeader <- Header.new
  filePath <- newIORef Nothing
  return $ Grid cells rowHeader columnHeader filePath
         (View.numDataRows viewInfo) (View.numDataColumns viewInfo)

getCell :: CellCoord -> Grid -> IO Cell
getCell coord = Dyn.read coord . cells

getCellValue :: CellCoord -> Grid -> IO CellValue
getCellValue coord grid = Cell.value <$> getCell coord grid

setCell :: CellCoord -> String -> Grid -> IO [CellCoord]
setCell coord string grid = do
  cell <- getCell coord grid
  deleteDependenciesOn cell
  cell' <- do expr <- compile (parse string,coord) grid
              return $ Cell.setExpression string expr cell
  addDependenciesOn cell'
  Dyn.write coord cell' $ cells grid
  evaluate coord grid
    where
      onCoords coords f = forM_ coords $ \c -> updateCell c f grid

      deleteDependenciesOn cell = 
          let onRef ref = 
               case ref of
                 Column a -> Header.deleteDependent coord a $ columnHeader grid
                 Row a -> Header.deleteDependent coord a $ rowHeader grid
                 _ -> do cs <- referencedCoords ref grid
                         onCoords cs $ Cell.deleteDependent coord
          in forM_ (Cell.dependencies cell) onRef

      addDependenciesOn cell = 
          let onRef ref = 
               case ref of
                 Column a -> Header.addDependent coord a $ columnHeader grid
                 Row a -> Header.addDependent coord a $ rowHeader grid
                 _ -> do cs <- referencedCoords ref grid
                         onCoords cs $ Cell.addDependent coord
          in forM_ (Cell.dependencies cell) onRef

rewriteCell :: CellCoord -> Grid -> IO [CellCoord]
rewriteCell coord grid = do
  input <- Cell.input <$> getCell coord grid
  setCell coord input grid

evaluate :: CellCoord -> Grid -> IO [CellCoord]
evaluate evalCoord grid = 
    let evaluateCell = Cell.evaluate $ flip referencedCellValue grid
        evalRec (coord@(row,col)) = do
          cell <- getCell coord grid
          updateCellIO coord evaluateCell grid
          changed1 <- forM (Cell.dependent cell) evalRec
          changed2 <- evaluateHeaderDeps row rowHeader grid
          changed3 <- evaluateHeaderDeps col columnHeader grid
          return $ coord : ((concat changed1) ++ changed2 ++ changed3)
    in
      evalRec evalCoord

evaluateHeaderDeps :: Int -> (Grid -> Header) -> Grid -> IO [CellCoord]
evaluateHeaderDeps i getHeader grid = do
  dependents <- Header.dependents i $ getHeader grid
  concat <$> (forM dependents $ \d -> evaluate d grid)

evaluateAllHeaderDeps :: (Grid -> Header) -> Grid -> IO [CellCoord]
evaluateAllHeaderDeps getHeader grid = do
  dependents <- Header.allDependents $ getHeader grid
  concat <$> (forM dependents $ \d -> evaluate d grid)  

referencedCoords :: Reference -> Grid -> IO [CellCoord]
referencedCoords ref grid =
    case ref of
      Cell coord -> return [coord]
      Range (from,to) -> return $ coordRange (from,to)
      Column c -> do rows <- numViewGridRows grid
                     return $ coordRange ((0,c),(rows-1,c))
      Row r -> do cols <- numViewGridCols grid
                  return $ coordRange ((r,0),(r,cols-1))

referencedCellValue :: Reference -> Grid -> IO CellValue
referencedCellValue ref grid = do
  coord <- referencedCoords ref grid
  case ref of
    (Cell _) -> getCellValue (head coord) grid
    _ -> ListValue <$> (forM coord $ flip getCellValue grid)


updateCellIO :: CellCoord -> (Cell -> IO Cell) -> Grid -> IO ()
updateCellIO coord f = Dyn.updateIO coord f . cells

updateCell :: CellCoord -> (Cell -> Cell) -> Grid -> IO ()
updateCell coord f = Dyn.update coord f . cells

resizeTo :: CellCoord -> Grid -> IO ()
resizeTo (r,c) = Dyn.resizeTo ((0,0),(r-1,c-1)) . cells

addRow,addColumn :: String -> Grid -> IO [CellCoord]
addRow name grid = do
  i <- numViewGridRows grid
  Header.setLabel name (i-1) $ rowHeader grid
  evaluateAllHeaderDeps columnHeader grid

addColumn name grid = do
  i <- numViewGridCols grid
  Header.setLabel name (i-1) $ columnHeader grid
  evaluateAllHeaderDeps rowHeader grid

updateRowLabel,updateColumnLabel :: Int -> String -> Grid -> IO [CellCoord]
updateRowLabel i to grid = do
  Header.setLabel to i $ rowHeader grid
  changed1 <- do 
    cols <- numViewGridCols grid
    rowDeps <- forM [0..cols] $ \c -> Cell.dependent <$> (getCell (i,c) grid)
    forM (concat rowDeps) $ flip rewriteCell grid
  changed2 <- do
    headerDeps <- Header.dependents i $ rowHeader grid
    forM headerDeps $ flip rewriteCell grid  
  return $ (concat changed1) ++ (concat changed2)

updateColumnLabel i to grid = do
  Header.setLabel to i $ columnHeader grid
  changed1 <- do 
    rows <- numViewGridRows grid
    colDeps <- forM [0..rows] $ \r -> Cell.dependent <$> (getCell (r,i) grid)
    forM (concat colDeps) $ flip rewriteCell grid
  changed2 <- do
    headerDeps <- Header.dependents i $ columnHeader grid
    forM headerDeps $ flip rewriteCell grid  
  return $ (concat changed1) ++ (concat changed2)

rowIndex,columnIndex :: String -> Grid -> IO (Maybe Int)
rowIndex name grid = Header.index name $ rowHeader grid
columnIndex name grid = Header.index name $ columnHeader grid

labelsToCoord :: (String,String) -> Grid -> IO (Maybe CellCoord)
labelsToCoord (rowName,columnName) grid = do
  row <- rowIndex rowName grid
  col <- columnIndex columnName grid
  return $ case (row,col) of
             (Just r,Just c) -> Just (r,c)
             _ -> Nothing

getFilePath :: Grid -> IO (Maybe FilePath)
getFilePath = readIORef . filePath

setFilePath :: FilePath -> Grid -> IO ()
setFilePath path grid = writeIORef (filePath grid) $ Just path

deleteColumns :: [Int] -> Grid -> IO ()
deleteColumns cols grid = do
  Header.delete cols $ columnHeader grid
  Dyn.deleteColumnsInMatrix cols $ cells grid

deleteRows :: [Int] -> Grid -> IO ()
deleteRows rows grid = do
  Header.delete rows $ rowHeader grid
  Dyn.deleteRowsInMatrix rows $ cells grid

getColumnValues :: Int -> Grid -> IO [CellValue]
getColumnValues i grid = do
  rows <- numViewGridRows grid
  forM [0..rows-1] $ \row -> getCellValue (row,i) grid

compile :: (CellExpr,CellCoord) -> Grid -> IO CellExpr
compile (expr,coord) grid = do
  expr' <- referenceConversion expr grid
  referenceLoop <- checkReferenceLoop (expr',coord) grid
  if referenceLoop then return $ CompileErrorExpr ReferenceLoop
   else return expr'

checkReferenceLoop :: (CellExpr,CellCoord) -> Grid -> IO Bool
checkReferenceLoop (expr,at) grid =
    let searchOnDependencies deps = do
          refCoords <- do cs <- forM deps $ referencedCoords `flip` grid
                          return $ concat cs
          or <$> forM refCoords depthFirstSearch

        depthFirstSearch coords | coords == at = return True
        depthFirstSearch coords = do
          deps <- Cell.dependencies <$> getCell coords grid
          searchOnDependencies deps      
    in
      searchOnDependencies $ CellContent.dependencies expr

referenceConversion :: CellExpr -> Grid -> IO CellExpr
referenceConversion expr grid =
    let convert (NamedCell a) = fmap Cell <$> labelsToCoord a grid
        convert (NamedRange (a,b)) = 
            do a' <- labelsToCoord a grid
               b' <- labelsToCoord b grid
               case (a',b') of
                 (Just x,Just y) -> return $ Just $ Range (x,y) 
                 _ -> return Nothing
        convert (NamedColumn a) = fmap Column <$> columnIndex a grid
        convert (NamedRow a) = fmap Row <$> rowIndex a grid
        refConversion = flip referenceConversion grid

    in case expr of
         NamedReference ref -> 
             do ref' <- convert ref
                case ref' of
                  Just r -> return $ Reference r
                  Nothing -> return $ CompileErrorExpr $ RefConversionError ref
         ListExpr a -> ListExpr <$> forM a refConversion
         UnaryOp s a -> UnaryOp s <$> refConversion a
         BinaryOp s a b -> do a' <- refConversion a
                              b' <- refConversion b
                              return $ BinaryOp s a' b'
         Call s a -> Call s <$> refConversion a
         Sub a -> Sub <$> refConversion a
         _ -> return expr