module Reanimate.Svg.Constructors
  ( 
    mkCircle
  , mkEllipse
  , mkRect
  , mkLine
  , mkPath
  , mkPathString
  , mkPathText
  , mkLinePath
  , mkLinePathClosed
  , mkClipPath
  , mkText
  
  , mkGroup
  , mkDefinitions
  , mkUse
  
  , withId
  , withStrokeColor
  , withStrokeColorPixel
  , withStrokeDashArray
  , withStrokeLineJoin
  , withFillColor
  , withFillColorPixel
  , withFillOpacity
  , withGroupOpacity
  , withStrokeWidth
  , withClipPathRef
  
  , center
  , centerX
  , centerY
  , centerUsing
  , translate
  , rotate
  , rotateAroundCenter
  , rotateAround
  , scale
  , scaleToSize
  , scaleToWidth
  , scaleToHeight
  , scaleXY
  , flipXAxis
  , flipYAxis
  , aroundCenter
  , aroundCenterX
  , aroundCenterY
  , withTransformations
  , withViewBox
  
  , mkColor
  , mkBackground
  , mkBackgroundPixel
  , gridLayout
  ) where
import           Codec.Picture                (PixelRGBA8 (..))
import           Control.Lens                 ((&), (.~), (?~))
import           Data.Attoparsec.Text         (parseOnly)
import qualified Data.Map                     as Map
import qualified Data.Text                    as T
import           Graphics.SvgTree             hiding (height, line, path, use,
                                               width)
import           Graphics.SvgTree.NamedColors
import           Graphics.SvgTree.PathParser
import           Linear.V2                    hiding (angle)
import           Reanimate.Constants
import           Reanimate.Svg.BoundingBox
withTransformations :: [Transformation] -> Tree -> Tree
withTransformations transformations t =
  mkGroup [t] & transform ?~ transformations
translate :: Double -> Double -> Tree -> Tree
translate x y = withTransformations [Translate x y]
rotate :: Double -> Tree -> Tree
rotate a = withTransformations [Rotate a Nothing]
rotateAround :: Double -> RPoint -> Tree -> Tree
rotateAround a (V2 x y) = withTransformations [Rotate a (Just (x,y))]
rotateAroundCenter :: Double -> Tree -> Tree
rotateAroundCenter a t =
    rotateAround a (V2 (x+w/2) (y+h/2)) t
  where
    (x,y,w,h) = boundingBox t
aroundCenter :: (Tree -> Tree) -> Tree -> Tree
aroundCenter fn t =
    translate (-offsetX) (-offsetY) $ fn $ translate offsetX offsetY t
  where
    offsetX = -x-w/2
    offsetY = -y-h/2
    (x,y,w,h) = boundingBox t
aroundCenterY :: (Tree -> Tree) -> Tree -> Tree
aroundCenterY fn t =
    translate 0 (-offsetY) $ fn $ translate 0 offsetY t
  where
    offsetY = -y-h/2
    (_x,y,_w,h) = boundingBox t
aroundCenterX :: (Tree -> Tree) -> Tree -> Tree
aroundCenterX fn t =
    translate (-offsetX) 0 $ fn $ translate offsetX 0 t
  where
    offsetX = -x-w/2
    (x,_y,w,_h) = boundingBox t
scale :: Double -> Tree -> Tree
scale a = withTransformations [Scale a Nothing]
scaleToSize :: Double -> Double -> Tree -> Tree
scaleToSize w h t =
    scaleXY (w/w') (h/h') t
  where
    (_x, _y, w', h') = boundingBox t
scaleToWidth :: Double -> Tree -> Tree
scaleToWidth w t =
    scale (w/w') t
  where
    (_x, _y, w', _h') = boundingBox t
scaleToHeight :: Double -> Tree -> Tree
scaleToHeight h t =
    scale (h/h') t
  where
    (_x, _y, _w', h') = boundingBox t
scaleXY :: Double -> Double -> Tree -> Tree
scaleXY x y = withTransformations [Scale x (Just y)]
flipXAxis :: Tree -> Tree
flipXAxis = scaleXY (-1) 1
flipYAxis :: Tree -> Tree
flipYAxis = scaleXY 1 (-1)
center :: Tree -> Tree
center t = centerUsing t t
centerX :: Tree -> Tree
centerX t = translate (-x-w/2) 0 t
  where
    (x, _y, w, _h) = boundingBox t
centerY :: Tree -> Tree
centerY t = translate 0 (-y-h/2) t
  where
    (_x, y, _w, h) = boundingBox t
centerUsing :: Tree -> Tree -> Tree
centerUsing a = translate (-x-w/2) (-y-h/2)
  where
    (x, y, w, h) = boundingBox a
mkColor :: String -> Texture
mkColor name =
  case Map.lookup (T.pack name) svgNamedColors of
    Nothing -> ColorRef (PixelRGBA8 240 248 255 255)
    Just c  -> ColorRef c
withStrokeColor :: String -> Tree -> Tree
withStrokeColor color = strokeColor .~ pure (mkColor color)
withStrokeColorPixel :: PixelRGBA8 -> Tree -> Tree
withStrokeColorPixel color = strokeColor .~ pure (ColorRef color)
withStrokeDashArray :: [Double] -> Tree -> Tree
withStrokeDashArray arr = strokeDashArray .~ pure (map Num arr)
withStrokeLineJoin :: LineJoin -> Tree -> Tree
withStrokeLineJoin ljoin = strokeLineJoin .~ pure ljoin
withFillColor :: String -> Tree -> Tree
withFillColor color = fillColor .~ pure (mkColor color)
withFillColorPixel :: PixelRGBA8 -> Tree -> Tree
withFillColorPixel color = fillColor .~ pure (ColorRef color)
withFillOpacity :: Double -> Tree -> Tree
withFillOpacity opacity = fillOpacity ?~ realToFrac opacity
withGroupOpacity :: Double -> Tree -> Tree
withGroupOpacity opacity = groupOpacity ?~ realToFrac opacity
withStrokeWidth :: Double -> Tree -> Tree
withStrokeWidth width = strokeWidth .~ pure (Num width)
withClipPathRef :: ElementRef 
                -> Tree 
                -> Tree
withClipPathRef ref sub = mkGroup [sub] & clipPathRef .~ pure ref
withId :: String -> Tree -> Tree
withId idTag = attrId ?~ idTag
mkRect :: Double -> Double -> Tree
mkRect width height = translate (-width/2) (-height/2) $ RectangleTree $ defaultSvg
  & rectUpperLeftCorner .~ (Num 0, Num 0)
  & rectWidth ?~ Num width
  & rectHeight ?~ Num height
mkCircle :: Double -> Tree
mkCircle radius = CircleTree $ defaultSvg
  & circleCenter .~ (Num 0, Num 0)
  & circleRadius .~ Num radius
mkEllipse :: Double -> Double -> Tree
mkEllipse rx ry = EllipseTree $ defaultSvg
  & ellipseCenter .~ (Num 0, Num 0)
  & ellipseXRadius .~ Num rx
  & ellipseYRadius .~ Num ry
mkLine :: (Double,Double) -> (Double, Double) -> Tree
mkLine (x1,y1) (x2,y2) = LineTree $ defaultSvg
  & linePoint1 .~ (Num x1, Num y1)
  & linePoint2 .~ (Num x2, Num y2)
mkGroup :: [Tree] -> Tree
mkGroup forest = GroupTree $ defaultSvg
  & groupChildren .~ forest
mkDefinitions :: [Tree] -> Tree
mkDefinitions forest = DefinitionTree $ defaultSvg
  & groupChildren .~ forest
mkUse :: String -> Tree
mkUse name = UseTree (defaultSvg & useName .~ name) Nothing
mkClipPath :: String  
                      
           -> [Tree] 
           -> Tree
mkClipPath idTag forest = withId idTag $ ClipPathTree $ defaultSvg
  & clipPathContent .~ forest
mkPath :: [PathCommand] -> Tree
mkPath cmds = PathTree $ defaultSvg & pathDefinition .~ cmds
mkPathString :: String -> Tree
mkPathString = mkPathText . T.pack
mkPathText :: T.Text -> Tree
mkPathText str =
  case parseOnly pathParser str of
    Left err   -> error err
    Right cmds -> mkPath cmds
mkLinePath :: [(Double, Double)] -> Tree
mkLinePath [] = mkGroup []
mkLinePath ((startX, startY):rest) =
    PathTree $ defaultSvg & pathDefinition .~ cmds
  where
    cmds = [ MoveTo OriginAbsolute [V2 startX startY]
           , LineTo OriginAbsolute [ V2 x y | (x, y) <- rest ] ]
mkLinePathClosed :: [(Double, Double)] -> Tree
mkLinePathClosed [] = mkGroup []
mkLinePathClosed ((startX, startY):rest) =
    PathTree $ defaultSvg & pathDefinition .~ cmds
  where
    cmds = [ MoveTo OriginAbsolute [V2 startX startY]
           , LineTo OriginAbsolute [ V2 x y | (x, y) <- rest ]
           , EndPath ]
mkBackground :: String -> Tree
mkBackground color = withFillOpacity 1 $  withStrokeWidth 0 $
  withFillColor color $ mkRect screenWidth screenHeight
mkBackgroundPixel :: PixelRGBA8 -> Tree
mkBackgroundPixel pixel =
    withFillOpacity 1 $ withStrokeWidth 0 $
    withFillColorPixel pixel $ mkRect screenWidth screenHeight
gridLayout :: [[Tree]] -> Tree
gridLayout rows = mkGroup
    [ translate (-screenWidth/2+colSep*nCol + colSep*0.5)
                (screenHeight/2-rowSep*nRow - rowSep*0.5)
      elt
    | (nRow, row) <- zip [0..] rows
    , let nCols = length row
          colSep = screenWidth / fromIntegral nCols
    , (nCol, elt) <- zip [0..] row ]
  where
    rowSep = screenHeight / fromIntegral nRows
    nRows = length rows
mkText :: T.Text -> Tree
mkText str =
  flipYAxis
  (TextTree Nothing $ defaultSvg
    & textRoot .~ span_
    & fontSize .~ pure (Num 2))
    & textAnchor .~ pure TextAnchorMiddle
    
    
  where
    span_ = defaultSvg & spanContent .~ [SpanText str]
withViewBox :: (Double, Double, Double, Double) -> Tree -> Tree
withViewBox vbox child = translate (-screenWidth/2) (-screenHeight/2) $
  SvgTree $ Document
  { _viewBox = Just vbox
  , _width = Just (Num screenWidth)
  , _height = Just (Num screenHeight)
  , _elements = [child]
  , _description = ""
  , _documentLocation = ""
  , _documentAspectRatio = PreserveAspectRatio False AlignNone Nothing
  }