content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RecordWildCards #-}
  {-# LANGUAGE TemplateHaskell #-}
  {-# LANGUAGE DeriveGeneric #-}
  module Codec.Xlsx.Types.Drawing.Chart where
  
  import GHC.Generics (Generic)
  
  import Codec.Xlsx.LensCompat (makeLenses)
  import Control.DeepSeq (NFData)
  import Data.Default
  import Data.Maybe (catMaybes, listToMaybe, maybeToList)
  import Data.Text (Text)
  import Text.XML
  import Text.XML.Cursor
  
  import Codec.Xlsx.Parser.Internal
  import Codec.Xlsx.Types.Common
  import Codec.Xlsx.Types.Drawing.Common
  import Codec.Xlsx.Writer.Internal
  
  -- | Main Chart holder, combines
  -- TODO: title, autoTitleDeleted, pivotFmts
  --  view3D, floor, sideWall, backWall, showDLblsOverMax, extLst
  data ChartSpace = ChartSpace
    { _chspTitle :: Maybe ChartTitle
    , _chspCharts :: [Chart]
    , _chspLegend :: Maybe Legend
    , _chspPlotVisOnly :: Maybe Bool
    , _chspDispBlanksAs :: Maybe DispBlanksAs
    } deriving (Eq, Show, Generic)
  instance NFData ChartSpace
  
  -- | Chart title
  --
  -- TODO: layout, overlay, spPr, txPr, extLst
  newtype ChartTitle =
    ChartTitle (Maybe TextBody)
    deriving (Eq, Show, Generic)
  instance NFData ChartTitle
  
  -- | This simple type specifies the possible ways to display blanks.
  --
  -- See 21.2.3.10 "ST_DispBlanksAs (Display Blanks As)" (p. 3444)
  data DispBlanksAs
    = DispBlanksAsGap
      -- ^ Specifies that blank values shall be left as a gap.
    | DispBlanksAsSpan
      -- ^ Specifies that blank values shall be spanned with a line.
    | DispBlanksAsZero
      -- ^ Specifies that blank values shall be treated as zero.
    deriving (Eq, Show, Generic)
  instance NFData DispBlanksAs
  
  -- TODO: legendEntry, layout, overlay, spPr, txPr, extLst
  data Legend = Legend
      { _legendPos     :: Maybe LegendPos
      , _legendOverlay :: Maybe Bool
      } deriving (Eq, Show, Generic)
  instance NFData Legend
  
  -- See 21.2.3.24 "ST_LegendPos (Legend Position)" (p. 3449)
  data LegendPos
    = LegendBottom
      -- ^ b (Bottom) Specifies that the legend shall be drawn at the
      -- bottom of the chart.
    | LegendLeft
      -- ^ l (Left) Specifies that the legend shall be drawn at the left
      -- of the chart.
    | LegendRight
      -- ^ r (Right) Specifies that the legend shall be drawn at the
      -- right of the chart.
    | LegendTop
      -- ^ t (Top) Specifies that the legend shall be drawn at the top
      -- of the chart.
    | LegendTopRight
      -- ^ tr (Top Right) Specifies that the legend shall be drawn at
      -- the top right of the chart.
    deriving (Eq, Show, Generic)
  instance NFData LegendPos
  
  -- | Specific Chart
  -- TODO:
  --   area3DChart, line3DChart, stockChart, radarChart,
  --   pie3DChart, doughnutChart, bar3DChart, ofPieChart,
  --   surfaceChart, surface3DChart, bubbleChart
  data Chart
    = LineChart { _lnchGrouping :: ChartGrouping
                , _lnchSeries :: [LineSeries]
                , _lnchMarker :: Maybe Bool
                  -- ^ specifies that the marker shall be shown
                , _lnchSmooth :: Maybe Bool
                  -- ^ specifies the line connecting the points on the chart shall be
                  -- smoothed using Catmull-Rom splines
                }
    | AreaChart { _archGrouping :: Maybe ChartGrouping
                , _archSeries :: [AreaSeries]
                }
    | BarChart { _brchDirection :: BarDirection
               , _brchGrouping :: Maybe BarChartGrouping
               , _brchSeries :: [BarSeries]
               }
    | PieChart { _pichSeries :: [PieSeries]
               }
    | ScatterChart { _scchStyle :: ScatterStyle
                   , _scchSeries :: [ScatterSeries]
                   }
    deriving (Eq, Show, Generic)
  instance NFData Chart
  
  -- | Possible groupings for a chart
  --
  -- See 21.2.3.17 "ST_Grouping (Grouping)" (p. 3446)
  data ChartGrouping
    = PercentStackedGrouping
      -- ^ (100% Stacked) Specifies that the chart series are drawn next to each
      -- other along the value axis and scaled to total 100%.
    | StackedGrouping
      -- ^ (Stacked) Specifies that the chart series are drawn next to each
      -- other on the value axis.
    | StandardGrouping
      -- ^(Standard) Specifies that the chart series are drawn on the value
      -- axis.
    deriving (Eq, Show, Generic)
  instance NFData ChartGrouping
  
  -- | Possible groupings for a bar chart
  --
  -- See 21.2.3.4 "ST_BarGrouping (Bar Grouping)" (p. 3441)
  data BarChartGrouping
    = BarClusteredGrouping
      -- ^ Specifies that the chart series are drawn next to each other
      -- along the category axis.
    | BarPercentStackedGrouping
      -- ^ (100% Stacked) Specifies that the chart series are drawn next to each
      -- other along the value axis and scaled to total 100%.
    | BarStackedGrouping
      -- ^ (Stacked) Specifies that the chart series are drawn next to each
      -- other on the value axis.
    | BarStandardGrouping
      -- ^(Standard) Specifies that the chart series are drawn on the value
      -- axis.
    deriving (Eq, Show, Generic)
  instance NFData BarChartGrouping
  
  -- | Possible directions for a bar chart
  --
  -- See 21.2.3.3 "ST_BarDir (Bar Direction)" (p. 3441)
  data BarDirection
    = DirectionBar
    | DirectionColumn
    deriving (Eq, Show, Generic)
  instance NFData BarDirection
  
  -- | Possible styles of scatter chart
  --
  -- /Note:/ It appears that even for 'ScatterMarker' style Exel draws a
  -- line between chart points if otline fill for '_scserShared' isn't
  -- set to so it's not quite clear how could Excel use this property
  --
  -- See 21.2.3.40 "ST_ScatterStyle (Scatter Style)" (p. 3455)
  data ScatterStyle
    = ScatterNone
    | ScatterLine
    | ScatterLineMarker
    | ScatterMarker
    | ScatterSmooth
    | ScatterSmoothMarker
    deriving (Eq, Show, Generic)
  instance NFData ScatterStyle
  
  -- | Single data point options
  --
  -- TODO:  invertIfNegative,  bubble3D, explosion, pictureOptions, extLst
  --
  -- See 21.2.2.52 "dPt (Data Point)" (p. 3384)
  data DataPoint = DataPoint
    { _dpMarker :: Maybe DataMarker
    , _dpShapeProperties :: Maybe ShapeProperties
    } deriving (Eq, Show, Generic)
  instance NFData DataPoint
  
  -- | Specifies common series options
  -- TODO: spPr
  --
  -- See @EG_SerShared@ (p. 4063)
  data Series = Series
    { _serTx :: Maybe Formula
      -- ^ specifies text for a series name, without rich text formatting
      -- currently only reference formula is supported
    , _serShapeProperties :: Maybe ShapeProperties
    } deriving (Eq, Show, Generic)
  instance NFData Series
  
  -- | A series on a line chart
  --
  -- TODO: dPt, trendline, errBars, cat, extLst
  --
  -- See @CT_LineSer@ (p. 4064)
  data LineSeries = LineSeries
    { _lnserShared :: Series
    , _lnserMarker :: Maybe DataMarker
    , _lnserDataLblProps :: Maybe DataLblProps
    , _lnserVal :: Maybe Formula
      -- ^ currently only reference formula is supported
    , _lnserSmooth :: Maybe Bool
    } deriving (Eq, Show, Generic)
  instance NFData LineSeries
  
  -- | A series on an area chart
  --
  -- TODO: pictureOptions, dPt, trendline, errBars, cat, extLst
  --
  -- See @CT_AreaSer@ (p. 4065)
  data AreaSeries = AreaSeries
    { _arserShared :: Series
    , _arserDataLblProps :: Maybe DataLblProps
    , _arserVal :: Maybe Formula
    } deriving (Eq, Show, Generic)
  instance NFData AreaSeries
  
  -- | A series on a bar chart
  --
  -- TODO: invertIfNegative, pictureOptions, dPt, trendline, errBars,
  -- cat, shape, extLst
  --
  -- See @CT_BarSer@ (p. 4064)
  data BarSeries = BarSeries
    { _brserShared :: Series
    , _brserDataLblProps :: Maybe DataLblProps
    , _brserVal :: Maybe Formula
    } deriving (Eq, Show, Generic)
  instance NFData BarSeries
  
  -- | A series on a pie chart
  --
  -- TODO: explosion, cat, extLst
  --
  -- See @CT_PieSer@ (p. 4065)
  data PieSeries = PieSeries
    { _piserShared :: Series
    , _piserDataPoints :: [DataPoint]
    -- ^ normally you should set fill for chart datapoints to make them
    -- properly colored
    , _piserDataLblProps :: Maybe DataLblProps
    , _piserVal :: Maybe Formula
    } deriving (Eq, Show, Generic)
  instance NFData PieSeries
  
  -- | A series on a scatter chart
  --
  -- TODO: dPt, trendline, errBars, smooth, extLst
  --
  -- See @CT_ScatterSer@ (p. 4064)
  data ScatterSeries = ScatterSeries
    { _scserShared :: Series
    , _scserMarker :: Maybe DataMarker
    , _scserDataLblProps :: Maybe DataLblProps
    , _scserXVal :: Maybe Formula
    , _scserYVal :: Maybe Formula
    , _scserSmooth :: Maybe Bool
    } deriving (Eq, Show, Generic)
  instance NFData ScatterSeries
  
  -- See @CT_Marker@ (p. 4061)
  data DataMarker = DataMarker
    { _dmrkSymbol :: Maybe DataMarkerSymbol
    , _dmrkSize :: Maybe Int
      -- ^ integer between 2 and 72, specifying a size in points
    } deriving (Eq, Show, Generic)
  instance NFData DataMarker
  
  data DataMarkerSymbol
    = DataMarkerCircle
    | DataMarkerDash
    | DataMarkerDiamond
    | DataMarkerDot
    | DataMarkerNone
    | DataMarkerPicture
    | DataMarkerPlus
    | DataMarkerSquare
    | DataMarkerStar
    | DataMarkerTriangle
    | DataMarkerX
    | DataMarkerAuto
    deriving (Eq, Show, Generic)
  instance NFData DataMarkerSymbol
  
  -- | Settings for the data labels for an entire series or the
  -- entire chart
  --
  -- TODO: numFmt, spPr, txPr, dLblPos, showBubbleSize,
  -- separator, showLeaderLines, leaderLines
  -- See 21.2.2.49 "dLbls (Data Labels)" (p. 3384)
  data DataLblProps = DataLblProps
    { _dlblShowLegendKey :: Maybe Bool
    , _dlblShowVal :: Maybe Bool
    , _dlblShowCatName :: Maybe Bool
    , _dlblShowSerName :: Maybe Bool
    , _dlblShowPercent :: Maybe Bool
    } deriving (Eq, Show, Generic)
  instance NFData DataLblProps
  
  -- | Specifies the possible positions for tick marks.
  
  -- See 21.2.3.48 "ST_TickMark (Tick Mark)" (p. 3467)
  data TickMark
    = TickMarkCross
      -- ^ (Cross) Specifies the tick marks shall cross the axis.
    | TickMarkIn
      -- ^ (Inside) Specifies the tick marks shall be inside the plot area.
    | TickMarkNone
      -- ^ (None) Specifies there shall be no tick marks.
    | TickMarkOut
      -- ^ (Outside) Specifies the tick marks shall be outside the plot area.
    deriving (Eq, Show, Generic)
  instance NFData TickMark
  
  makeLenses ''DataPoint
  
  {-------------------------------------------------------------------------------
    Default instances
  -------------------------------------------------------------------------------}
  
  instance Default DataPoint where
      def = DataPoint Nothing Nothing
  
  {-------------------------------------------------------------------------------
    Parsing
  -------------------------------------------------------------------------------}
  
  instance FromCursor ChartSpace where
    fromCursor cur = do
      cur' <- cur $/ element (c_ "chart")
      _chspTitle <- maybeFromElement (c_ "title") cur'
      let _chspCharts =
            cur' $/ element (c_ "plotArea") &/ anyElement >=> chartFromNode . node
      _chspLegend <- maybeFromElement (c_ "legend") cur'
      _chspPlotVisOnly <- maybeBoolElementValue (c_ "plotVisOnly") cur'
      _chspDispBlanksAs <- maybeElementValue (c_ "dispBlanksAs") cur'
      return ChartSpace {..}
  
  chartFromNode :: Node -> [Chart]
  chartFromNode n
    | n `nodeElNameIs` (c_ "lineChart") = do
      _lnchGrouping <- fromElementValue (c_ "grouping") cur
      let _lnchSeries = cur $/ element (c_ "ser") >=> fromCursor
      _lnchMarker <- maybeBoolElementValue (c_ "marker") cur
      _lnchSmooth <- maybeBoolElementValue (c_ "smooth") cur
      return LineChart {..}
    | n `nodeElNameIs` (c_ "areaChart") = do
      _archGrouping <- maybeElementValue (c_ "grouping") cur
      let _archSeries = cur $/ element (c_ "ser") >=> fromCursor
      return AreaChart {..}
    | n `nodeElNameIs` (c_ "barChart") = do
      _brchDirection <- fromElementValue (c_ "barDir") cur
      _brchGrouping <-
        maybeElementValueDef (c_ "grouping") BarClusteredGrouping cur
      let _brchSeries = cur $/ element (c_ "ser") >=> fromCursor
      return BarChart {..}
    | n `nodeElNameIs` (c_ "pieChart") = do
      let _pichSeries = cur $/ element (c_ "ser") >=> fromCursor
      return PieChart {..}
    | n `nodeElNameIs` (c_ "scatterChart") = do
      _scchStyle <- fromElementValue (c_ "scatterStyle") cur
      let _scchSeries = cur $/ element (c_ "ser") >=> fromCursor
      return ScatterChart {..}
    | otherwise = fail "no matching chart node"
    where
      cur = fromNode n
  
  instance FromCursor LineSeries where
    fromCursor cur = do
      _lnserShared <- fromCursor cur
      _lnserMarker <- maybeFromElement (c_ "marker") cur
      _lnserDataLblProps <- maybeFromElement (c_ "dLbls") cur
      _lnserVal <-
        cur $/ element (c_ "val") &/ element (c_ "numRef") >=>
        maybeFromElement (c_ "f")
      _lnserSmooth <- maybeElementValueDef (c_ "smooth") True cur
      return LineSeries {..}
  
  instance FromCursor AreaSeries where
    fromCursor cur = do
      _arserShared <- fromCursor cur
      _arserDataLblProps <- maybeFromElement (c_ "dLbls") cur
      _arserVal <-
        cur $/ element (c_ "val") &/ element (c_ "numRef") >=>
        maybeFromElement (c_ "f")
      return AreaSeries {..}
  
  instance FromCursor BarSeries where
    fromCursor cur = do
      _brserShared <- fromCursor cur
      _brserDataLblProps <- maybeFromElement (c_ "dLbls") cur
      _brserVal <-
        cur $/ element (c_ "val") &/ element (c_ "numRef") >=>
        maybeFromElement (c_ "f")
      return BarSeries {..}
  
  instance FromCursor PieSeries where
    fromCursor cur = do
      _piserShared <- fromCursor cur
      let _piserDataPoints = cur $/ element (c_ "dPt") >=> fromCursor
      _piserDataLblProps <- maybeFromElement (c_ "dLbls") cur
      _piserVal <-
        cur $/ element (c_ "val") &/ element (c_ "numRef") >=>
        maybeFromElement (c_ "f")
      return PieSeries {..}
  
  instance FromCursor ScatterSeries where
    fromCursor cur = do
      _scserShared <- fromCursor cur
      _scserMarker <- maybeFromElement (c_ "marker") cur
      _scserDataLblProps <- maybeFromElement (c_ "dLbls") cur
      _scserXVal <-
        cur $/ element (c_ "xVal") &/ element (c_ "numRef") >=>
        maybeFromElement (c_ "f")
      _scserYVal <-
        cur $/ element (c_ "yVal") &/ element (c_ "numRef") >=>
        maybeFromElement (c_ "f")
      _scserSmooth <- maybeElementValueDef (c_ "smooth") True cur
      return ScatterSeries {..}
  
  -- should we respect idx and order?
  instance FromCursor Series where
    fromCursor cur = do
      _serTx <-
        cur $/ element (c_ "tx") &/ element (c_ "strRef") >=>
        maybeFromElement (c_ "f")
      _serShapeProperties <- maybeFromElement (c_ "spPr") cur
      return Series {..}
  
  instance FromCursor DataMarker where
    fromCursor cur = do
      _dmrkSymbol <- maybeElementValue (c_ "symbol") cur
      _dmrkSize <- maybeElementValue (c_ "size") cur
      return DataMarker {..}
  
  instance FromCursor DataPoint where
    fromCursor cur = do
      _dpMarker <- maybeFromElement (c_ "marker") cur
      _dpShapeProperties <- maybeFromElement (c_ "spPr") cur
      return DataPoint {..}
  
  instance FromAttrVal DataMarkerSymbol where
    fromAttrVal "circle" = readSuccess DataMarkerCircle
    fromAttrVal "dash" = readSuccess DataMarkerDash
    fromAttrVal "diamond" = readSuccess DataMarkerDiamond
    fromAttrVal "dot" = readSuccess DataMarkerDot
    fromAttrVal "none" = readSuccess DataMarkerNone
    fromAttrVal "picture" = readSuccess DataMarkerPicture
    fromAttrVal "plus" = readSuccess DataMarkerPlus
    fromAttrVal "square" = readSuccess DataMarkerSquare
    fromAttrVal "star" = readSuccess DataMarkerStar
    fromAttrVal "triangle" = readSuccess DataMarkerTriangle
    fromAttrVal "x" = readSuccess DataMarkerX
    fromAttrVal "auto" = readSuccess DataMarkerAuto
    fromAttrVal t = invalidText "DataMarkerSymbol" t
  
  instance FromAttrVal BarDirection where
    fromAttrVal "bar" = readSuccess DirectionBar
    fromAttrVal "col" = readSuccess DirectionColumn
    fromAttrVal t = invalidText "BarDirection" t
  
  instance FromAttrVal ScatterStyle where
    fromAttrVal "none" = readSuccess ScatterNone
    fromAttrVal "line" = readSuccess ScatterLine
    fromAttrVal "lineMarker" = readSuccess ScatterLineMarker
    fromAttrVal "marker" = readSuccess ScatterMarker
    fromAttrVal "smooth" = readSuccess ScatterSmooth
    fromAttrVal "smoothMarker" = readSuccess ScatterSmoothMarker
    fromAttrVal t = invalidText "ScatterStyle" t
  
  instance FromCursor DataLblProps where
    fromCursor cur = do
      _dlblShowLegendKey <- maybeBoolElementValue (c_ "showLegendKey") cur
      _dlblShowVal <- maybeBoolElementValue (c_ "showVal") cur
      _dlblShowCatName <- maybeBoolElementValue (c_ "showCatName") cur
      _dlblShowSerName <- maybeBoolElementValue (c_ "showSerName") cur
      _dlblShowPercent <- maybeBoolElementValue (c_ "showPercent") cur
      return DataLblProps {..}
  
  instance FromAttrVal ChartGrouping where
    fromAttrVal "percentStacked" = readSuccess PercentStackedGrouping
    fromAttrVal "standard" = readSuccess StandardGrouping
    fromAttrVal "stacked" = readSuccess StackedGrouping
    fromAttrVal t = invalidText "ChartGrouping" t
  
  instance FromAttrVal BarChartGrouping where
    fromAttrVal "clustered" = readSuccess BarClusteredGrouping
    fromAttrVal "percentStacked" = readSuccess BarPercentStackedGrouping
    fromAttrVal "standard" = readSuccess BarStandardGrouping
    fromAttrVal "stacked" = readSuccess BarStackedGrouping
    fromAttrVal t = invalidText "BarChartGrouping" t
  
  instance FromCursor ChartTitle where
    fromCursor cur = do
      let mTitle = listToMaybe $
            cur $/ element (c_ "tx") &/ element (c_ "rich") >=> fromCursor
      return $ ChartTitle mTitle
  
  instance FromCursor Legend where
    fromCursor cur = do
      _legendPos <- maybeElementValue (c_ "legendPos") cur
      _legendOverlay <- maybeElementValueDef (c_ "overlay") True cur
      return Legend {..}
  
  instance FromAttrVal LegendPos where
    fromAttrVal "b" = readSuccess LegendBottom
    fromAttrVal "l" = readSuccess LegendLeft
    fromAttrVal "r" = readSuccess LegendRight
    fromAttrVal "t" = readSuccess LegendTop
    fromAttrVal "tr" = readSuccess LegendTopRight
    fromAttrVal t = invalidText "LegendPos" t
  
  instance FromAttrVal DispBlanksAs where
    fromAttrVal "gap" = readSuccess DispBlanksAsGap
    fromAttrVal "span" = readSuccess DispBlanksAsSpan
    fromAttrVal "zero" = readSuccess DispBlanksAsZero
    fromAttrVal t = invalidText "DispBlanksAs" t
  
  {-------------------------------------------------------------------------------
    Default instances
  -------------------------------------------------------------------------------}
  
  instance Default Legend where
    def = Legend {_legendPos = Just LegendBottom, _legendOverlay = Just False}
  
  {-------------------------------------------------------------------------------
    Rendering
  -------------------------------------------------------------------------------}
  
  instance ToDocument ChartSpace where
    toDocument =
      documentFromNsPrefElement "Charts generated by xlsx" chartNs (Just "c") .
      toElement "chartSpace"
  
  instance ToElement ChartSpace where
    toElement nm ChartSpace {..} =
      elementListSimple nm [nonRounded, chartEl, chSpPr]
      where
        -- no such element gives a chart space with rounded corners
        nonRounded = elementValue "roundedCorners" False
        chSpPr = toElement "spPr" $ def {_spFill = Just $ solidRgb "ffffff"}
        chartEl = elementListSimple "chart" elements
        elements =
          catMaybes
            [ toElement "title" <$> _chspTitle
            -- LO?
            , Just $ elementValue "autoTitleDeleted" False
            , Just $ elementListSimple "plotArea" areaEls
            , toElement "legend" <$> _chspLegend
            , elementValue "plotVisOnly" <$> _chspPlotVisOnly
            , elementValue "dispBlanksAs" <$> _chspDispBlanksAs
            ]
        areaEls = charts ++ axes
        (_, charts, axes) = foldr addChart (1, [], []) _chspCharts
        addChart ch (i, cs, as) =
          let (c, as') = chartToElements ch i
          in (i + length as', c : cs, as' ++ as)
  
  chartToElements :: Chart -> Int -> (Element, [Element])
  chartToElements chart axId =
    case chart of
      LineChart {..} ->
        chartElement
          "lineChart"
          stdAxes
          (Just _lnchGrouping)
          _lnchSeries
          []
          (catMaybes
             [ elementValue "marker" <$> _lnchMarker
             , elementValue "smooth" <$> _lnchSmooth
             ])
      AreaChart {..} ->
        chartElement "areaChart" stdAxes _archGrouping _archSeries [] []
      BarChart {..} ->
        chartElement
          "barChart"
          stdAxes
          _brchGrouping
          _brchSeries
          [elementValue "barDir" _brchDirection]
          []
      PieChart {..} -> chartElement "pieChart" [] noGrouping _pichSeries [] []
      ScatterChart {..} ->
        chartElement
          "scatterChart"
          xyAxes
          noGrouping
          _scchSeries
          [elementValue "scatterStyle" _scchStyle]
          []
    where
      noGrouping :: Maybe ChartGrouping
      noGrouping = Nothing
      chartElement
        :: (ToElement s, ToAttrVal gr)
        => Name
        -> [Element]
        -> Maybe gr
        -> [s]
        -> [Element]
        -> [Element]
        -> (Element, [Element])
      chartElement nm axes mGrouping series prepended appended =
        ( elementListSimple nm $
          prepended ++
          (maybeToList $ elementValue "grouping" <$> mGrouping) ++
          (varyColors : seriesEls series) ++
          appended ++ zipWith (\n _ -> elementValue "axId" n) [axId ..] axes
        , axes)
      -- no element seems to be equal to varyColors=true in Excel Online
      varyColors = elementValue "varyColors" False
      seriesEls series = [indexedSeriesEl i s | (i, s) <- zip [0 ..] series]
      indexedSeriesEl
        :: ToElement a
        => Int -> a -> Element
      indexedSeriesEl i s = prependI i $ toElement "ser" s
      prependI i e@Element {..} = e {elementNodes = iNodes i ++ elementNodes}
      iNodes i = map NodeElement [elementValue n i | n <- ["idx", "order"]]
  
      stdAxes = [catAx axId (axId + 1), valAx "l" (axId + 1) axId]
      xyAxes = [valAx "b" axId (axId + 1), valAx "l" (axId + 1) axId]
      catAx :: Int -> Int -> Element
      catAx i cr =
        elementListSimple "catAx" $
        [ elementValue "axId" i
        , emptyElement "scaling"
        , elementValue "delete" False
        , elementValue "axPos" ("b" :: Text)
        , elementValue "majorTickMark" TickMarkNone
        , elementValue "minorTickMark" TickMarkNone
        , toElement "spPr" grayLines
        , elementValue "crossAx" cr
        , elementValue "auto" True
        ]
      valAx :: Text -> Int -> Int -> Element
      valAx pos i cr =
        elementListSimple "valAx" $
        [ elementValue "axId" i
        , emptyElement "scaling"
        , elementValue "delete" False
        , elementValue "axPos" pos
        , gridLinesEl
        , elementValue "majorTickMark" TickMarkNone
        , elementValue "minorTickMark" TickMarkNone
        , toElement "spPr" grayLines
        , elementValue "crossAx" cr
        ]
      grayLines = def {_spOutline = Just def {_lnFill = Just $ solidRgb "b3b3b3"}}
      gridLinesEl =
        elementListSimple "majorGridlines" [toElement "spPr" grayLines]
  
  instance ToAttrVal ChartGrouping where
    toAttrVal PercentStackedGrouping = "percentStacked"
    toAttrVal StandardGrouping = "standard"
    toAttrVal StackedGrouping = "stacked"
  
  instance ToAttrVal BarChartGrouping where
    toAttrVal BarClusteredGrouping = "clustered"
    toAttrVal BarPercentStackedGrouping = "percentStacked"
    toAttrVal BarStandardGrouping = "standard"
    toAttrVal BarStackedGrouping = "stacked"
  
  instance ToAttrVal BarDirection where
    toAttrVal DirectionBar = "bar"
    toAttrVal DirectionColumn = "col"
  
  instance ToAttrVal ScatterStyle where
    toAttrVal ScatterNone = "none"
    toAttrVal ScatterLine = "line"
    toAttrVal ScatterLineMarker = "lineMarker"
    toAttrVal ScatterMarker = "marker"
    toAttrVal ScatterSmooth = "smooth"
    toAttrVal ScatterSmoothMarker = "smoothMarker"
  
  instance ToElement LineSeries where
    toElement nm LineSeries {..} = simpleSeries nm _lnserShared _lnserVal pr ap
      where
        pr =
          catMaybes
            [ toElement "marker" <$> _lnserMarker
            , toElement "dLbls" <$> _lnserDataLblProps
            ]
        ap = maybeToList $ elementValue "smooth" <$> _lnserSmooth
  
  simpleSeries :: Name
               -> Series
               -> Maybe Formula
               -> [Element]
               -> [Element]
               -> Element
  simpleSeries nm shared val prepended appended =
    serEl {elementNodes = elementNodes serEl ++ map NodeElement elements}
    where
      serEl = toElement nm shared
      elements = prepended ++ (valEl val : appended)
      valEl v =
        elementListSimple
          "val"
          [elementListSimple "numRef" $ maybeToList (toElement "f" <$> v)]
  
  instance ToElement DataMarker where
    toElement nm DataMarker {..} = elementListSimple nm elements
      where
        elements =
          catMaybes
            [ elementValue "symbol" <$> _dmrkSymbol
            , elementValue "size" <$> _dmrkSize
            ]
  
  instance ToAttrVal DataMarkerSymbol where
    toAttrVal DataMarkerCircle = "circle"
    toAttrVal DataMarkerDash = "dash"
    toAttrVal DataMarkerDiamond = "diamond"
    toAttrVal DataMarkerDot = "dot"
    toAttrVal DataMarkerNone = "none"
    toAttrVal DataMarkerPicture = "picture"
    toAttrVal DataMarkerPlus = "plus"
    toAttrVal DataMarkerSquare = "square"
    toAttrVal DataMarkerStar = "star"
    toAttrVal DataMarkerTriangle = "triangle"
    toAttrVal DataMarkerX = "x"
    toAttrVal DataMarkerAuto = "auto"
  
  instance ToElement DataLblProps where
    toElement nm DataLblProps {..} = elementListSimple nm elements
      where
        elements =
          catMaybes
            [ elementValue "showLegendKey" <$> _dlblShowLegendKey
            , elementValue "showVal" <$> _dlblShowVal
            , elementValue "showCatName" <$> _dlblShowCatName
            , elementValue "showSerName" <$> _dlblShowSerName
            , elementValue "showPercent" <$> _dlblShowPercent
            ]
  
  instance ToElement AreaSeries where
    toElement nm AreaSeries {..} = simpleSeries nm _arserShared _arserVal pr []
      where
        pr = maybeToList $ fmap (toElement "dLbls") _arserDataLblProps
  
  instance ToElement BarSeries where
    toElement nm BarSeries {..} = simpleSeries nm _brserShared _brserVal pr []
      where
        pr = maybeToList $ fmap (toElement "dLbls") _brserDataLblProps
  
  instance ToElement PieSeries where
    toElement nm PieSeries {..} = simpleSeries nm _piserShared _piserVal pr []
      where
        pr = dPts ++ maybeToList (fmap (toElement "dLbls") _piserDataLblProps)
        dPts = zipWith dPtEl [(0 :: Int) ..] _piserDataPoints
        dPtEl i DataPoint {..} =
          elementListSimple
            "dPt"
            (elementValue "idx" i :
             catMaybes
               [ toElement "marker" <$> _dpMarker
               , toElement "spPr" <$> _dpShapeProperties
               ])
  
  instance ToElement ScatterSeries where
    toElement nm ScatterSeries {..} =
      serEl {elementNodes = elementNodes serEl ++ map NodeElement elements}
      where
        serEl = toElement nm _scserShared
        elements =
          catMaybes
            [ toElement "marker" <$> _scserMarker
            , toElement "dLbls" <$> _scserDataLblProps
            ] ++
          [valEl "xVal" _scserXVal, valEl "yVal" _scserYVal] ++
          (maybeToList $ fmap (elementValue "smooth") _scserSmooth)
        valEl vnm v =
          elementListSimple
            vnm
            [elementListSimple "numRef" $ maybeToList (toElement "f" <$> v)]
  
  -- should we respect idx and order?
  instance ToElement Series where
    toElement nm Series {..} =
      elementListSimple nm $
      [ elementListSimple
          "tx"
          [elementListSimple "strRef" $ maybeToList (toElement "f" <$> _serTx)]
      ] ++
      maybeToList (toElement "spPr" <$> _serShapeProperties)
  
  instance ToElement ChartTitle where
    toElement nm (ChartTitle body) =
      elementListSimple nm [txEl, elementValue "overlay" False]
      where
        txEl = elementListSimple "tx" $ catMaybes [toElement (c_ "rich") <$> body]
  
  instance ToElement Legend where
    toElement nm Legend{..} = elementListSimple nm elements
      where
         elements = catMaybes [ elementValue "legendPos" <$> _legendPos
                              , elementValue "overlay" <$>_legendOverlay]
  
  instance ToAttrVal LegendPos where
    toAttrVal LegendBottom   = "b"
    toAttrVal LegendLeft     = "l"
    toAttrVal LegendRight    = "r"
    toAttrVal LegendTop      = "t"
    toAttrVal LegendTopRight = "tr"
  
  instance ToAttrVal DispBlanksAs where
    toAttrVal DispBlanksAsGap  = "gap"
    toAttrVal DispBlanksAsSpan = "span"
    toAttrVal DispBlanksAsZero = "zero"
  
  instance ToAttrVal TickMark where
    toAttrVal TickMarkCross = "cross"
    toAttrVal TickMarkIn = "in"
    toAttrVal TickMarkNone = "none"
    toAttrVal TickMarkOut = "out"
  
  -- | Add chart namespace to name
  c_ :: Text -> Name
  c_ x =
    Name {nameLocalName = x, nameNamespace = Just chartNs, namePrefix = Just "c"}
  
  chartNs :: Text
  chartNs = "http://schemas.openxmlformats.org/drawingml/2006/chart"
  