{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
--------------------------------------------------------------------------------
-- |
-- Module      :  Data.Geometry.Ipe.Types
-- Copyright   :  (C) Frank Staals
-- License     :  see the LICENSE file
-- Maintainer  :  Frank Staals
--
-- Data type modeling the various elements in Ipe files.
--
--------------------------------------------------------------------------------
module Data.Geometry.Ipe.Types(
    LayerName(LayerName), layerName
  , Image(Image), imageData, rect
  , TextLabel(..)
  , MiniPage(..), width

  , IpeSymbol(Symbol), symbolPoint, symbolName

  , Path(Path), pathSegments
  , PathSegment(..)

  , Group(Group), groupItems


  , IpeObject(..), _IpeGroup, _IpeImage, _IpeTextLabel, _IpeMiniPage, _IpeUse, _IpePath
  , IpeObject'
  , ipeObject'
  , ToObject(..)

  , IpeAttributes
  , Attributes', AttributesOf, AttrMap, AttrMapSym1
  , attributes, traverseIpeAttrs
  , commonAttributes

  , flattenGroups


  , View(View), layerNames, activeLayer

  , IpeStyle(IpeStyle), styleName, styleData
  , basicIpeStyle


  , IpePreamble(IpePreamble), encoding, preambleData

  , IpeBitmap


  , IpePage(IpePage), layers, views, content
  , emptyPage, fromContent
  , onLayer, contentInView
  , withDefaults

  , IpeFile(IpeFile), preamble, styles, pages
  , ipeFile, singlePageFile, singlePageFromContent
  ) where


import           Control.Lens hiding (views)
import           Data.Geometry.Ipe.Attributes hiding (Matrix)
import           Data.Geometry.Ipe.Content
import           Data.Geometry.Ipe.Layer
import           Data.Geometry.Ipe.Literal
import qualified Data.List.NonEmpty as NE
import           Data.Maybe (mapMaybe)
import           Data.Semigroup (Endo)
import qualified Data.Set as Set
import           Data.Text (Text)
import           Text.XML.Expat.Tree (Node)

--------------------------------------------------------------------------------


-- | The definition of a view
-- make active layer into an index ?
data View = View { _layerNames      :: [LayerName]
                 , _activeLayer     :: LayerName
                 }
          deriving (Eq, Ord, Show)
makeLenses ''View

-- instance Default


-- | for now we pretty much ignore these
data IpeStyle = IpeStyle { _styleName :: Maybe Text
                         , _styleData :: Node Text Text
                         }
              deriving (Eq,Show)
makeLenses ''IpeStyle


basicIpeStyle :: IpeStyle
basicIpeStyle = IpeStyle (Just "basic") (xmlLiteral [litFile|resources/basic.isy|])


-- | The maybe string is the encoding
data IpePreamble  = IpePreamble { _encoding     :: Maybe Text
                                , _preambleData :: Text
                                }
                  deriving (Eq,Read,Show,Ord)
makeLenses ''IpePreamble

type IpeBitmap = Text



--------------------------------------------------------------------------------
-- Ipe Pages

-- | An IpePage is essentially a Group, together with a list of layers and a
-- list of views.
data IpePage r = IpePage { _layers  :: [LayerName]
                         , _views   :: [View]
                         , _content :: [IpeObject r]
                         }
              deriving (Eq,Show)
makeLenses ''IpePage

-- | Creates an empty page with one layer and view.
emptyPage :: IpePage r
emptyPage = fromContent []

-- | Creates a simple page with a single view.
fromContent     :: [IpeObject r] -> IpePage r
fromContent obs = IpePage layers' [View layers' a] obs
  where
    layers' = Set.toList . Set.fromList $ a : mapMaybe (^.commonAttributes.ixAttr SLayer) obs
    a       = "alpha"

-- | Makes sure that the page has at least one layer and at least one
-- view, essentially matching the behaviour of ipe. In particular,
--
-- - if the page does not have any layers, it creates a layer named "alpha", and
-- - if the page does not have any views, it creates a view in which all layers are visible.
--
withDefaults :: IpePage r -> IpePage r
withDefaults = addView . addLayer
  where
    whenNull ys = \case
                    [] -> ys
                    xs -> xs
    addLayer p = p&layers %~ whenNull ["alpha"]
    addView  p = p&views  %~ whenNull [View (p^.layers) (head $ p^.layers)]
                 -- note that the head is save, since we just made sure
                 -- with 'addLayer' that there is at least one layer

-- | This allows you to filter the objects on some layer.
--
-- >>> let page = IpePage [] [] []
-- >>> page^..content.onLayer "myLayer"
-- []
onLayer   :: LayerName -> Getting (Endo [IpeObject r]) [IpeObject r] (IpeObject r)
onLayer n = folded.filtered (\o -> o^?commonAttributes._Attr SLayer == Just n)

-- | Gets all objects that are visible in the given view.
--
-- Note that views are indexed starting from 0. If the page does not
-- have any explicit view definitions, this function returns an empty
-- list.
--
-- >>> let page = IpePage [] [] []
-- >>> page^.contentInView 0
-- []
contentInView                     :: Word -> Getter (IpePage r) [IpeObject r]
contentInView (fromIntegral -> i) = to inView'
  where
    inView' p = let lrs = Set.fromList . concatMap (^.layerNames) $ p^..views.ix i
                in p^..content.folded.filtered (inVisibleLayer lrs)

    inVisibleLayer lrs o = maybe False (`Set.member` lrs) $ o^?commonAttributes._Attr SLayer

--------------------------------------------------------------------------------

-- | A complete ipe file
data IpeFile r = IpeFile { _preamble :: Maybe IpePreamble
                         , _styles   :: [IpeStyle]
                         , _pages    :: NE.NonEmpty (IpePage r)
                         }
               deriving (Eq,Show)
makeLenses ''IpeFile


-- | Convenience constructor for creating an ipe file without preamble
-- and with the default stylesheet.
ipeFile :: NE.NonEmpty (IpePage r) -> IpeFile r
ipeFile = IpeFile Nothing [basicIpeStyle]

-- | Convenience function to construct an ipe file consisting of a single page.
singlePageFile :: IpePage r -> IpeFile r
singlePageFile = ipeFile . (NE.:| [])

-- | Create a single page ipe file from a list of IpeObjects
singlePageFromContent :: [IpeObject r] -> IpeFile r
singlePageFromContent = singlePageFile . fromContent