{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings  #-}
{-# LANGUAGE RecordWildCards    #-}
{-# LANGUAGE TemplateHaskell    #-}
{-# LANGUAGE DeriveGeneric #-}
module Codec.Xlsx.Types.RichText (
    -- * Main types
    RichTextRun(..)
  , RunProperties(..)
  , applyRunProperties
    -- * Lenses
    -- ** RichTextRun
  , richTextRunProperties
  , richTextRunText
    -- ** RunProperties
  , runPropertiesBold
  , runPropertiesCharset
  , runPropertiesColor
  , runPropertiesCondense
  , runPropertiesExtend
  , runPropertiesFontFamily
  , runPropertiesItalic
  , runPropertiesOutline
  , runPropertiesFont
  , runPropertiesScheme
  , runPropertiesShadow
  , runPropertiesStrikeThrough
  , runPropertiesSize
  , runPropertiesUnderline
  , runPropertiesVertAlign
  ) where

import GHC.Generics (Generic)

import Control.Lens hiding (element)
import Control.Monad
import Control.DeepSeq (NFData)
import Data.Default
import Data.Maybe (catMaybes)
import Data.Text (Text)
import Text.XML
import Text.XML.Cursor
import qualified Data.Map as Map

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

-- | Rich Text Run
--
-- This element represents a run of rich text. A rich text run is a region of
-- text that share a common set of properties, such as formatting properties.
--
-- Section 18.4.4, "r (Rich Text Run)" (p. 1724)
data RichTextRun = RichTextRun {
    -- | This element represents a set of properties to apply to the contents of
    -- this rich text run.
    _richTextRunProperties :: Maybe RunProperties

    -- | This element represents the text content shown as part of a string.
    --
    -- NOTE: 'RichTextRun' elements with an empty text field will result in
    -- an error when opening the file in Excel.
    --
    -- Section 18.4.12, "t (Text)" (p. 1727)
  , _richTextRunText :: Text
  }
  deriving (Eq, Ord, Show, Generic)

instance NFData RichTextRun

-- | Run properties
--
-- Section 18.4.7, "rPr (Run Properties)" (p. 1725)
data RunProperties = RunProperties {
    -- | Displays characters in bold face font style.
    --
    -- Section 18.8.2, "b (Bold)" (p. 1757)
    _runPropertiesBold :: Maybe Bool

    -- | This element defines the font character set of this font.
    --
    -- Section 18.4.1, "charset (Character Set)" (p. 1721)
  , _runPropertiesCharset :: Maybe Int

    -- | One of the colors associated with the data bar or color scale.
    --
    -- Section 18.3.1.15, "color (Data Bar Color)" (p. 1608)
  , _runPropertiesColor :: Maybe Color

    -- | Macintosh compatibility setting. Represents special word/character
    -- rendering on Macintosh, when this flag is set. The effect is to condense
    -- the text (squeeze it together).
    --
    -- Section 18.8.12, "condense (Condense)" (p. 1764)
  , _runPropertiesCondense :: Maybe Bool

    -- | This element specifies a compatibility setting used for previous
    -- spreadsheet applications, resulting in special word/character rendering
    -- on those legacy applications, when this flag is set. The effect extends
    -- or stretches out the text.
    --
    -- Section 18.8.17, "extend (Extend)" (p. 1766)
  , _runPropertiesExtend :: Maybe Bool

    -- | The font family this font belongs to. A font family is a set of fonts
    -- having common stroke width and serif characteristics. This is system
    -- level font information. The font name overrides when there are
    -- conflicting values.
    --
    -- Section 18.8.18, "family (Font Family)" (p. 1766)
  , _runPropertiesFontFamily :: Maybe FontFamily

    -- | Displays characters in italic font style. The italic style is defined
    -- by the font at a system level and is not specified by ECMA-376.
    --
    -- Section 18.8.26, "i (Italic)" (p. 1773)
  , _runPropertiesItalic :: Maybe Bool

    -- | This element displays only the inner and outer borders of each
    -- character. This is very similar to Bold in behavior.
    --
    -- Section 18.4.2, "outline (Outline)" (p. 1722)
  , _runPropertiesOutline :: Maybe Bool

    -- | This element is a string representing the name of the font assigned to
    -- display this run.
    --
    -- Section 18.4.5, "rFont (Font)" (p. 1724)
  , _runPropertiesFont :: Maybe Text

    -- | Defines the font scheme, if any, to which this font belongs. When a
    -- font definition is part of a theme definition, then the font is
    -- categorized as either a major or minor font scheme component. When a new
    -- theme is chosen, every font that is part of a theme definition is updated
    -- to use the new major or minor font definition for that theme. Usually
    -- major fonts are used for styles like headings, and minor fonts are used
    -- for body and paragraph text.
    --
    -- Section 18.8.35, "scheme (Scheme)" (p. 1794)
  , _runPropertiesScheme :: Maybe FontScheme

    -- | Macintosh compatibility setting. Represents special word/character
    -- rendering on Macintosh, when this flag is set. The effect is to render a
    -- shadow behind, beneath and to the right of the text.
    --
    -- Section 18.8.36, "shadow (Shadow)" (p. 1795)
  , _runPropertiesShadow :: Maybe Bool

    -- | This element draws a strikethrough line through the horizontal middle
    -- of the text.
    --
    -- Section 18.4.10, "strike (Strike Through)" (p. 1726)
  , _runPropertiesStrikeThrough :: Maybe Bool

    -- | This element represents the point size (1/72 of an inch) of the Latin
    -- and East Asian text.
    --
    -- Section 18.4.11, "sz (Font Size)" (p. 1727)
  , _runPropertiesSize :: Maybe Double

    -- | This element represents the underline formatting style.
    --
    -- Section 18.4.13, "u (Underline)" (p. 1728)
  , _runPropertiesUnderline :: Maybe FontUnderline

    -- | This element adjusts the vertical position of the text relative to the
    -- text's default appearance for this run. It is used to get 'superscript'
    -- or 'subscript' texts, and shall reduce the font size (if a smaller size
    -- is available) accordingly.
    --
    -- Section 18.4.14, "vertAlign (Vertical Alignment)" (p. 1728)
  , _runPropertiesVertAlign :: Maybe FontVerticalAlignment
  }
  deriving (Eq, Ord, Show, Generic)

instance NFData RunProperties

{-------------------------------------------------------------------------------
  Lenses
-------------------------------------------------------------------------------}

makeLenses ''RichTextRun
makeLenses ''RunProperties

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

instance Default RichTextRun where
  def = RichTextRun {
      _richTextRunProperties = Nothing
    , _richTextRunText       = ""
    }

instance Default RunProperties where
   def = RunProperties {
      _runPropertiesBold          = Nothing
    , _runPropertiesCharset       = Nothing
    , _runPropertiesColor         = Nothing
    , _runPropertiesCondense      = Nothing
    , _runPropertiesExtend        = Nothing
    , _runPropertiesFontFamily    = Nothing
    , _runPropertiesItalic        = Nothing
    , _runPropertiesOutline       = Nothing
    , _runPropertiesFont          = Nothing
    , _runPropertiesScheme        = Nothing
    , _runPropertiesShadow        = Nothing
    , _runPropertiesStrikeThrough = Nothing
    , _runPropertiesSize          = Nothing
    , _runPropertiesUnderline     = Nothing
    , _runPropertiesVertAlign     = Nothing
    }

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

-- | See @CT_RElt@, p. 3903
instance ToElement RichTextRun where
  toElement nm RichTextRun{..} = Element {
      elementName       = nm
    , elementAttributes = Map.empty
    , elementNodes      = map NodeElement . catMaybes $ [
          toElement "rPr" <$> _richTextRunProperties
        , Just $ elementContentPreserved "t" _richTextRunText
        ]
    }

-- | See @CT_RPrElt@, p. 3903
instance ToElement RunProperties where
  toElement nm RunProperties{..} = Element {
      elementName       = nm
    , elementAttributes = Map.empty
    , elementNodes      = map NodeElement . catMaybes $ [
          elementValue "rFont"     <$> _runPropertiesFont
        , elementValue "charset"   <$> _runPropertiesCharset
        , elementValue "family"    <$> _runPropertiesFontFamily
        , elementValue "b"         <$> _runPropertiesBold
        , elementValue "i"         <$> _runPropertiesItalic
        , elementValue "strike"    <$> _runPropertiesStrikeThrough
        , elementValue "outline"   <$> _runPropertiesOutline
        , elementValue "shadow"    <$> _runPropertiesShadow
        , elementValue "condense"  <$> _runPropertiesCondense
        , elementValue "extend"    <$> _runPropertiesExtend
        , toElement    "color"     <$> _runPropertiesColor
        , elementValue "sz"        <$> _runPropertiesSize
        , elementValueDef "u" FontUnderlineSingle
                                   <$> _runPropertiesUnderline
        , elementValue "vertAlign" <$> _runPropertiesVertAlign
        , elementValue "scheme"    <$> _runPropertiesScheme
        ]
    }

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

-- | See @CT_RElt@, p. 3903
instance FromCursor RichTextRun where
  fromCursor cur = do
    _richTextRunText <- cur $/ element (n_ "t") &/ content
    _richTextRunProperties <- maybeFromElement (n_ "rPr") cur
    return RichTextRun{..}

instance FromXenoNode RichTextRun where
  fromXenoNode root = do
    (prNode, tNode) <- collectChildren root $ (,) <$> maybeChild "rPr" <*> requireChild "t"
    _richTextRunProperties <- mapM fromXenoNode prNode
    _richTextRunText <- contentX tNode
    return RichTextRun {..}

-- | See @CT_RPrElt@, p. 3903
instance FromCursor RunProperties where
  fromCursor cur = do
    _runPropertiesFont          <- maybeElementValue (n_ "rFont") cur
    _runPropertiesCharset       <- maybeElementValue (n_ "charset") cur
    _runPropertiesFontFamily    <- maybeElementValue (n_ "family") cur
    _runPropertiesBold          <- maybeBoolElementValue (n_ "b") cur
    _runPropertiesItalic        <- maybeBoolElementValue (n_ "i") cur
    _runPropertiesStrikeThrough <- maybeBoolElementValue (n_ "strike") cur
    _runPropertiesOutline       <- maybeBoolElementValue (n_ "outline") cur
    _runPropertiesShadow        <- maybeBoolElementValue (n_ "shadow") cur
    _runPropertiesCondense      <- maybeBoolElementValue (n_ "condense") cur
    _runPropertiesExtend        <- maybeBoolElementValue (n_ "extend") cur
    _runPropertiesColor         <- maybeFromElement  (n_ "color") cur
    _runPropertiesSize          <- maybeElementValue (n_ "sz") cur
    _runPropertiesUnderline     <- maybeElementValueDef (n_ "u") FontUnderlineSingle cur
    _runPropertiesVertAlign     <- maybeElementValue (n_ "vertAlign") cur
    _runPropertiesScheme        <- maybeElementValue (n_ "scheme") cur
    return RunProperties{..}

instance FromXenoNode RunProperties where
  fromXenoNode root = collectChildren root $ do
    _runPropertiesFont          <- maybeElementVal "rFont"
    _runPropertiesCharset       <- maybeElementVal "charset"
    _runPropertiesFontFamily    <- maybeElementVal "family"
    _runPropertiesBold          <- maybeElementVal "b"
    _runPropertiesItalic        <- maybeElementVal "i"
    _runPropertiesStrikeThrough <- maybeElementVal "strike"
    _runPropertiesOutline       <- maybeElementVal "outline"
    _runPropertiesShadow        <- maybeElementVal "shadow"
    _runPropertiesCondense      <- maybeElementVal "condense"
    _runPropertiesExtend        <- maybeElementVal "extend"
    _runPropertiesColor         <- maybeFromChild "color"
    _runPropertiesSize          <- maybeElementVal "sz"
    _runPropertiesUnderline     <- maybeElementVal "u"
    _runPropertiesVertAlign     <- maybeElementVal "vertAlign"
    _runPropertiesScheme        <- maybeElementVal "scheme"
    return RunProperties{..}

{-------------------------------------------------------------------------------
  Applying formatting
-------------------------------------------------------------------------------}

#if (MIN_VERSION_base(4,11,0))
instance Semigroup RunProperties where
  a <> b = RunProperties {
      _runPropertiesBold          = override _runPropertiesBold
    , _runPropertiesCharset       = override _runPropertiesCharset
    , _runPropertiesColor         = override _runPropertiesColor
    , _runPropertiesCondense      = override _runPropertiesCondense
    , _runPropertiesExtend        = override _runPropertiesExtend
    , _runPropertiesFontFamily    = override _runPropertiesFontFamily
    , _runPropertiesItalic        = override _runPropertiesItalic
    , _runPropertiesOutline       = override _runPropertiesOutline
    , _runPropertiesFont          = override _runPropertiesFont
    , _runPropertiesScheme        = override _runPropertiesScheme
    , _runPropertiesShadow        = override _runPropertiesShadow
    , _runPropertiesStrikeThrough = override _runPropertiesStrikeThrough
    , _runPropertiesSize          = override _runPropertiesSize
    , _runPropertiesUnderline     = override _runPropertiesUnderline
    , _runPropertiesVertAlign     = override _runPropertiesVertAlign
    }
    where
      override :: (RunProperties -> Maybe x) -> Maybe x
      override f = f b `mplus` f a

#endif

-- | The 'Monoid' instance for 'RunProperties' is biased: later properties
-- override earlier ones.
instance Monoid RunProperties where
  mempty = def
  a `mappend` b = RunProperties {
      _runPropertiesBold          = override _runPropertiesBold
    , _runPropertiesCharset       = override _runPropertiesCharset
    , _runPropertiesColor         = override _runPropertiesColor
    , _runPropertiesCondense      = override _runPropertiesCondense
    , _runPropertiesExtend        = override _runPropertiesExtend
    , _runPropertiesFontFamily    = override _runPropertiesFontFamily
    , _runPropertiesItalic        = override _runPropertiesItalic
    , _runPropertiesOutline       = override _runPropertiesOutline
    , _runPropertiesFont          = override _runPropertiesFont
    , _runPropertiesScheme        = override _runPropertiesScheme
    , _runPropertiesShadow        = override _runPropertiesShadow
    , _runPropertiesStrikeThrough = override _runPropertiesStrikeThrough
    , _runPropertiesSize          = override _runPropertiesSize
    , _runPropertiesUnderline     = override _runPropertiesUnderline
    , _runPropertiesVertAlign     = override _runPropertiesVertAlign
    }
    where
      override :: (RunProperties -> Maybe x) -> Maybe x
      override f = f b `mplus` f a

-- | Apply properties to a 'RichTextRun'
--
-- If the 'RichTextRun' specifies its own properties, then these overrule the
-- properties specified here. For example, adding @bold@ to a 'RichTextRun'
-- which is already @italic@ will make the 'RichTextRun' both @bold and @italic@
-- but adding it to one that that is explicitly _not_ bold will leave the
-- 'RichTextRun' unchanged.
applyRunProperties :: RunProperties -> RichTextRun -> RichTextRun
applyRunProperties p (RichTextRun Nothing   t) = RichTextRun (Just p) t
applyRunProperties p (RichTextRun (Just p') t) = RichTextRun (Just (p `mappend` p')) t