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

#ifdef USE_MICROLENS
import Lens.Micro.TH (makeLenses)
#else
import Control.Lens (makeLenses)
#endif
import Control.DeepSeq (NFData)
import Data.Maybe (catMaybes, maybeToList)
import Data.Text (Text)
import GHC.Generics (Generic)
import Text.XML
import Text.XML.Cursor

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

-- | Tables are ranges of data in the worksheet that have special
-- behavior applied which allow users to better sort, analyze, format,
-- manage, add, and delete data. Tables and table columns can also be
-- referenced through formulas by the spreadsheet application using
-- friendly names, making formula calculations that use tables much
-- easier to understand and maintain. Tables provide a natural way for
-- working with large sets of tabular data.
--
-- NOTE: as @headerRowCount@ property isn't yet supported it's
-- supposed that it's library user liability to guarantee that the 1st
-- row of 'tblRef' range contains cells with names specified in
-- `tblColumns`
--
-- Section 18.5 \"Tables\" (p. 1728)
-- Section 18.5.1 \"Tables\" (p. 1729)
-- Section 18.5.1.2 "table (Table)" (p. 1730)
data Table = Table
  { tblDisplayName :: Text
    -- ^ A string representing the name of the table. This is the name
    -- that shall be used in formula references, and displayed in the UI
    -- to the spreadsheet user.  This name shall not have any spaces in
    -- it, and it shall be unique amongst all other displayNames and
    -- definedNames in the workbook. The character lengths and
    -- restrictions are the same as for definedNames .
  , tblName :: Maybe Text
    -- ^ A string representing the name of the table that is used to
    -- reference the table programmatically through the spreadsheet
    -- applications object model. This string shall be unique per table
    -- per sheet. It has the same length and character restrictions as
    -- for displayName.  By default this should be the same as the
    -- table's 'tblDisplayName' . This name should also be kept in synch with
    -- the displayName when the displayName is updated in the UI by the
    -- spreadsheet user.
  , tblRef :: CellRef
    -- ^ The range on the relevant sheet that the table occupies
    -- expressed using A1 style referencing.
  , tblColumns :: [TableColumn]
    -- ^ columns of this table, specification requires any table to
    -- include at least 1 column
  , tblAutoFilter :: Maybe AutoFilter
  } deriving (Eq, Show, Generic)
instance NFData Table

-- | Single table column
--
-- TODO: styling information
--
-- Section 18.5.1.3 "tableColumn (Table Column)" (p. 1735)
data TableColumn = TableColumn
  { tblcName :: Text
  -- ^ A string representing the unique caption of the table
  -- column. This is what shall be displayed in the header row in the
  -- UI, and is referenced through functions. This name shall be
  -- unique per table.
  } deriving (Eq, Show, Generic)
instance NFData TableColumn

makeLenses ''Table

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

instance FromCursor Table where
  fromCursor c = do
    tblDisplayName <- fromAttribute "displayName" c
    tblName <- maybeAttribute "name" c
    tblRef <- fromAttribute "ref" c
    tblAutoFilter <- maybeFromElement (n_ "autoFilter") c
    let tblColumns =
          c $/ element (n_ "tableColumns") &/ element (n_ "tableColumn") >=>
          fmap TableColumn . fromAttribute "name"
    return Table {..}

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

tableToDocument :: Table -> Int -> Document
tableToDocument tbl i =
  documentFromElement "Table generated by xlsx" $
  tableToElement "table" tbl i

tableToElement :: Name -> Table -> Int -> Element
tableToElement nm Table {..} i = elementList nm attrs subElements
  where
    attrs =
      [ "id" .= i
      , "displayName" .= tblDisplayName
      , "ref" .= tblRef
      ] ++
      catMaybes
      [ "name" .=? tblName
      ]
    subElements =
      maybeToList (toElement "autoFilter" <$> tblAutoFilter) ++
      maybeToList (nonEmptyCountedElementList
                    "tableColumns"
                    [ leafElement "tableColumn" ["id" .= i', "name" .= tblcName c]
                    | (i', c) <- zip [(1 :: Int) ..] tblColumns
                    ]
                  )
