content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RecordWildCards #-}
  {-# LANGUAGE TemplateHaskell #-}
  {-# LANGUAGE DeriveGeneric #-}
  module Codec.Xlsx.Types.Cell
    ( CellFormula(..)
    , FormulaExpression(..)
    , simpleCellFormula
    , sharedFormulaByIndex
    , SharedFormulaIndex(..)
    , SharedFormulaOptions(..)
    , formulaDataFromCursor
    , applySharedFormulaOpts
    , Cell(..)
    , cellStyle
    , cellValue
    , cellComment
    , cellFormula
    , CellMap
    ) where
  
  import Control.Arrow (first)
  import Codec.Xlsx.LensCompat (makeLenses)
  import Control.DeepSeq (NFData)
  import Data.Default
  import Data.Map (Map)
  import qualified Data.Map as M
  import Data.Maybe (catMaybes, listToMaybe)
  import Data.Monoid ((<>))
  import Data.Text (Text)
  import GHC.Generics (Generic)
  import Text.XML
  import Text.XML.Cursor
  
  import Codec.Xlsx.Parser.Internal
  import Codec.Xlsx.Types.Comment
  import Codec.Xlsx.Types.Common
  import Codec.Xlsx.Writer.Internal
  
  -- | Formula for the cell.
  --
  -- TODO: array, dataTable formula types support
  --
  -- See 18.3.1.40 "f (Formula)" (p. 1636)
  data CellFormula = CellFormula
    { _cellfExpression :: FormulaExpression
    , _cellfAssignsToName :: Bool
        -- ^ Specifies that this formula assigns a value to a name.
    , _cellfCalculate :: Bool
        -- ^ Indicates that this formula needs to be recalculated
        -- the next time calculation is performed.
        -- [/Example/: This is always set on volatile functions,
        -- like =RAND(), and circular references. /end example/]
    } deriving (Eq, Show, Generic)
  instance NFData CellFormula
  
  -- | formula type with type-specific options
  data FormulaExpression
    = NormalFormula Formula
    | SharedFormula SharedFormulaIndex
    deriving (Eq, Show, Generic)
  instance NFData FormulaExpression
  
  defaultFormulaType :: Text
  defaultFormulaType = "normal"
  
  -- | index of shared formula in worksheet's 'wsSharedFormulas'
  -- property
  newtype SharedFormulaIndex = SharedFormulaIndex Int
    deriving (Eq, Ord, Show, Generic)
  instance NFData SharedFormulaIndex
  
  data SharedFormulaOptions = SharedFormulaOptions
    { _sfoRef :: CellRef
    , _sfoExpression :: Formula
    }
    deriving (Eq, Show, Generic)
  instance NFData SharedFormulaOptions
  
  simpleCellFormula :: Text -> CellFormula
  simpleCellFormula expr = CellFormula
      { _cellfExpression    = NormalFormula $ Formula expr
      , _cellfAssignsToName = False
      , _cellfCalculate     = False
      }
  
  sharedFormulaByIndex :: SharedFormulaIndex -> CellFormula
  sharedFormulaByIndex si =
    CellFormula
    { _cellfExpression = SharedFormula si
    , _cellfAssignsToName = False
    , _cellfCalculate = False
    }
  
  -- | Currently cell details include cell values, style ids and cell
  -- formulas (inline strings from @\<is\>@ subelements are ignored)
  data Cell = Cell
      { _cellStyle   :: Maybe Int
      , _cellValue   :: Maybe CellValue
      , _cellComment :: Maybe Comment
      , _cellFormula :: Maybe CellFormula
      } deriving (Eq, Show, Generic)
  instance NFData Cell
  
  instance Default Cell where
      def = Cell Nothing Nothing Nothing Nothing
  
  makeLenses ''Cell
  
  -- | Map containing cell values which are indexed by row and column
  -- if you need to use more traditional (x,y) indexing please you could
  -- use corresponding accessors from ''Codec.Xlsx.Lens''
  type CellMap = Map (RowIndex, ColumnIndex) Cell
  
  {-------------------------------------------------------------------------------
    Parsing
  -------------------------------------------------------------------------------}
  
  formulaDataFromCursor ::
       Cursor -> [(CellFormula, Maybe (SharedFormulaIndex, SharedFormulaOptions))]
  formulaDataFromCursor cur = do
    _cellfAssignsToName <- fromAttributeDef "bx" False cur
    _cellfCalculate <- fromAttributeDef "ca" False cur
    t <- fromAttributeDef "t" defaultFormulaType cur
    (_cellfExpression, shared) <-
      case t of
        d| d == defaultFormulaType -> do
          formula <- fromCursor cur
          return (NormalFormula formula, Nothing)
        "shared" -> do
          let expr = listToMaybe $ fromCursor cur
          ref <- maybeAttribute "ref" cur
          si <- fromAttribute "si" cur
          return (SharedFormula si, (,) <$> pure si <*>
                   (SharedFormulaOptions <$> ref <*> expr))
        _ ->
          fail $ "Unexpected formula type" ++ show t
    return (CellFormula {..}, shared)
  
  instance FromAttrVal SharedFormulaIndex where
    fromAttrVal = fmap (first SharedFormulaIndex) . fromAttrVal
  
  instance FromAttrBs SharedFormulaIndex where
    fromAttrBs = fmap SharedFormulaIndex . fromAttrBs
  
  {-------------------------------------------------------------------------------
    Rendering
  -------------------------------------------------------------------------------}
  
  instance ToElement CellFormula where
    toElement nm CellFormula {..} =
      formulaEl {elementAttributes = elementAttributes formulaEl <> commonAttrs}
      where
        commonAttrs =
          M.fromList $
          catMaybes
            [ "bx" .=? justTrue _cellfAssignsToName
            , "ca" .=? justTrue _cellfCalculate
            , "t" .=? justNonDef defaultFormulaType fType
            ]
        (formulaEl, fType) =
          case _cellfExpression of
            NormalFormula f -> (toElement nm f, defaultFormulaType)
            SharedFormula si -> (leafElement nm ["si" .= si], "shared")
  
  instance ToAttrVal SharedFormulaIndex where
    toAttrVal (SharedFormulaIndex si) = toAttrVal si
  
  applySharedFormulaOpts :: SharedFormulaOptions -> Element -> Element
  applySharedFormulaOpts SharedFormulaOptions {..} el =
    el
    { elementAttributes = elementAttributes el <> M.fromList ["ref" .= _sfoRef]
    , elementNodes = NodeContent (unFormula _sfoExpression) : elementNodes el
    }
  