{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
module Codec.Xlsx.Types.AutoFilter where

import GHC.Generics (Generic)

import Control.Lens (makeLenses)
import Data.Default
import Data.Map (Map)
import Data.Maybe (catMaybes)
import qualified Data.Map as M
import Data.Text (Text)
import Text.XML
import Text.XML.Cursor

import Codec.Xlsx.Parser.Internal
import Codec.Xlsx.Types.Common
import Codec.Xlsx.Writer.Internal

-- | The filterColumn collection identifies a particular column in the
-- AutoFilter range and specifies filter information that has been
-- applied to this column. If a column in the AutoFilter range has no
-- criteria specified, then there is no corresponding filterColumn
-- collection expressed for that column.
--
-- Section 18.3.2.7 "filterColumn (AutoFilter Column)" (p. 1717)
data FilterColumn
  = Filters { _fltValues :: [Text]}
  | ACustomFilter CustomFilter
  | CustomFiltersOr CustomFilter
                    CustomFilter
  | CustomFiltersAnd CustomFilter
                     CustomFilter
  deriving (Eq, Show, Generic)

data CustomFilter = CustomFilter
  { cfltOperator :: CustomFilterOperator
  , cfltValue :: Text
  } deriving (Eq, Show, Generic)

data CustomFilterOperator
  = FltrEqual
    -- ^ Show results which are equal to criteria.
  | FltrGreaterThan
    -- ^ Show results which are greater than criteria.
  | FltrGreaterThanOrEqual
    -- ^ Show results which are greater than or equal to criteria.
  | FltrLessThan
    -- ^ Show results which are less than criteria.
  | FltrLessThanOrEqual
    -- ^ Show results which are less than or equal to criteria.
  | FltrNotEqual
    -- ^ Show results which are not equal to criteria.
  deriving (Eq, Show, Generic)

-- | AutoFilter temporarily hides rows based on a filter criteria,
-- which is applied column by column to a table of datain the
-- worksheet.
--
-- TODO: sortState, extList
--
-- Section 18.3.1.2 "autoFilter (AutoFilter Settings)" (p. 1596)
data AutoFilter = AutoFilter
  { _afRef :: Maybe CellRef
  , _afFilterColumns :: Map Int FilterColumn
  } deriving (Eq, Show, Generic)

makeLenses ''AutoFilter


{-------------------------------------------------------------------------------
  Default instances
-------------------------------------------------------------------------------}

instance Default AutoFilter where
    def = AutoFilter Nothing M.empty

{-------------------------------------------------------------------------------
  Parsing
-------------------------------------------------------------------------------}

instance FromCursor AutoFilter where
  fromCursor cur = do
    _afRef <- maybeAttribute "ref" cur
    let _afFilterColumns = M.fromList $ cur $/ element (n_ "filterColumn") >=> \c -> do
          colId <- fromAttribute "colId" c
          fcol <- c $/ anyElement >=> fltColFromNode . node
          return (colId, fcol)
    return AutoFilter {..}

fltColFromNode :: Node -> [FilterColumn]
fltColFromNode n | n `nodeElNameIs` (n_ "filters") = do
                     let _fltValues = cur $/ element (n_ "filter") >=> fromAttribute "val"
                     return Filters{..}
                 | n `nodeElNameIs` (n_ "customFilters") = do
                     isAnd <- fromAttributeDef "and" False cur
                     let cFilters = cur $/ element (n_ "customFilter") >=> \c -> do
                           op <- fromAttributeDef "operator" FltrEqual c
                           val <- fromAttribute "val" c
                           return $ CustomFilter op val
                     case cFilters of
                       [f] ->
                         return $ ACustomFilter f
                       [f1, f2] ->
                         if isAnd
                           then return $ CustomFiltersAnd f1 f2
                           else return $ CustomFiltersOr f1 f2
                       _ ->
                         fail "bad custom filter"
                 | otherwise = fail "no matching nodes"
  where
    cur = fromNode n

instance FromAttrVal CustomFilterOperator where
  fromAttrVal "equal" = readSuccess FltrEqual
  fromAttrVal "greaterThan" = readSuccess FltrGreaterThan
  fromAttrVal "greaterThanOrEqual" = readSuccess FltrGreaterThanOrEqual
  fromAttrVal "lessThan" = readSuccess FltrLessThan
  fromAttrVal "lessThanOrEqual" = readSuccess FltrLessThanOrEqual
  fromAttrVal "notEqual" = readSuccess FltrNotEqual
  fromAttrVal t = invalidText "CustomFilterOperator" t

{-------------------------------------------------------------------------------
  Rendering
-------------------------------------------------------------------------------}

instance ToElement AutoFilter where
  toElement nm AutoFilter {..} =
    elementList
      nm
      (catMaybes ["ref" .=? _afRef])
      [ elementList
        (n_ "filterColumn")
        ["colId" .= colId]
        [fltColToElement fCol]
      | (colId, fCol) <- M.toList _afFilterColumns
      ]

fltColToElement :: FilterColumn -> Element
fltColToElement Filters {..} =
  elementListSimple
    (n_ "filters")
    [leafElement (n_ "filter") ["val" .= v] | v <- _fltValues]
fltColToElement (ACustomFilter f) =
  elementListSimple (n_ "customFilters") [toElement (n_ "customFilter") f]
fltColToElement (CustomFiltersOr f1 f2) =
  elementListSimple
    (n_ "customFilters")
    [toElement (n_ "customFilter") f | f <- [f1, f2]]
fltColToElement (CustomFiltersAnd f1 f2) =
  elementList
    (n_ "customFilters")
    ["and" .= True]
    [toElement (n_ "customFilter") f | f <- [f1, f2]]

instance ToElement CustomFilter where
  toElement nm CustomFilter {..} =
    leafElement nm ["operator" .= cfltOperator, "val" .= cfltValue]

instance ToAttrVal CustomFilterOperator where
  toAttrVal FltrEqual = "equal"
  toAttrVal FltrGreaterThan = "greaterThan"
  toAttrVal FltrGreaterThanOrEqual = "greaterThanOrEqual"
  toAttrVal FltrLessThan = "lessThan"
  toAttrVal FltrLessThanOrEqual = "lessThanOrEqual"
  toAttrVal FltrNotEqual = "notEqual"