-- |
-- Module      : Presentation.Yeamer.Internal.Grid
-- Copyright   : (c) Justus Sagemüller 2017
-- License     : GPL v3
-- 
-- Maintainer  : (@) jsag $ hvl.no
-- Stability   : experimental
-- Portability : portable
-- 
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE DeriveFunctor         #-}
{-# LANGUAGE DeriveFoldable        #-}
{-# LANGUAGE DeriveTraversable     #-}
{-# LANGUAGE DeriveGeneric         #-}
{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TemplateHaskell       #-}

module Presentation.Yeamer.Internal.Grid where

import Data.Semigroup.Numbered

import GHC.Generics

import Data.Aeson (FromJSON, ToJSON)
import Flat (Flat)

import Data.Ratio ((%))

import Data.List (sortBy)
import Data.Ord (comparing)

import Control.Applicative (liftA2)
import Control.Monad.Trans.State
import Control.Arrow (second)

import Lens.Micro
import Lens.Micro.TH

data Gridded a = GridRegion a
               | GridDivisions [[Gridded a]]
  deriving (Generic, Functor, Eq, Show, Foldable, Traversable)
instance FromJSON a => FromJSON (Gridded a)
instance ToJSON a => ToJSON (Gridded a)
instance Flat a => Flat (Gridded a)

instance Applicative Gridded where
  pure = GridRegion
  fs <*> GridRegion x = ($ x) <$> fs
  GridRegion f <*> xs = f <$> xs
  GridDivisions fs <*> GridDivisions xs = GridDivisions $ liftA2 (<*>) <$> fs <*> xs
instance Monad Gridded where
  return = GridRegion
  GridRegion x >>= f = f x
  GridDivisions xs >>= f = GridDivisions $ map (>>=f) <$> xs

instance SemigroupNo 0 (Gridded a) where
  sappendN _ (GridDivisions g) (GridDivisions h) | length g == length h
        = GridDivisions $ zipWith (++) g h
  sappendN _ e (GridDivisions [r]) = GridDivisions [e:r]
  sappendN _ (GridDivisions [r]) e = GridDivisions [r++[e]]
  sappendN p a b = GridDivisions [[a,b]]

instance SemigroupNo 1 (Gridded a) where
  sappendN _ (GridDivisions g@(l:_)) (GridDivisions h@(m:_)) | length l == length m
        = GridDivisions $ g++h
  sappendN _ e (GridDivisions c@([r]:_)) = GridDivisions $ [e]:c
  sappendN _ (GridDivisions c@([r]:_)) e = GridDivisions $ c++[[e]]
  sappendN p a b = GridDivisions [[a],[b]]


data GridRange = GridRange {
      _xBegin, _xEnd, _yBegin, _yEnd :: Int }
   deriving (Eq, Show, Generic)
makeLenses ''GridRange
data GridLayout a = GridLayout {
      _gridWidth, _gridHeight :: Int
    , _gridContents :: [(GridRange, a)]
    } deriving (Functor, Generic, Eq, Show)
makeLenses ''GridLayout

layoutGrid :: Gridded a -> GridLayout a
layoutGrid = fmap snd . fst . layoutGridP

type GridRegionId = Int

layoutGridP :: Gridded a -> ( GridLayout (GridRegionId, a)
                            , [(GridRegionId, b)] -> (Gridded b, [(GridRegionId, b)]) )
layoutGridP = (`evalState`0) . go
 where go (GridRegion a) = do
         i <- get
         put $ i+1
         return ( GridLayout 1 1 [(GridRange 0 1 0 1, (i, a))]
                , \((_, b):lgrs) -> (GridRegion b, lgrs) )
       go (GridDivisions [])
        = return ( GridLayout 0 0 []
                 , \lgrs -> (GridDivisions [], lgrs) )
       go (GridDivisions [row]) = do
         layouts <- mapM go row
         return ( alignLayoutDirectional gridWidth xBegin xEnd
                             gridHeight yBegin yEnd
                             (fst<$>layouts)
                , let procLgrs [] acc lgrs = (GridDivisions [acc []], lgrs)
                      procLgrs (srow:srows) acc lgrs
                         = let (srowRes, lgrs') = srow lgrs
                           in procLgrs srows (acc . (srowRes:)) lgrs'
                  in procLgrs (snd<$>layouts) id )
       go (GridDivisions rows) = do
         rLayouts <- mapM (go . GridDivisions . pure) rows
         return ( alignLayoutDirectional gridHeight yBegin yEnd
                             gridWidth xBegin xEnd
                        (fst<$>rLayouts)
                , let procLgrs [] acc lgrs = (GridDivisions $ acc [], lgrs)
                      procLgrs (srow:srows) acc lgrs
                         = let (GridDivisions [srowRes], lgrs') = srow lgrs
                           in procLgrs srows (acc . (srowRes:)) lgrs'
                  in procLgrs (snd<$>rLayouts) id  )

alignLayoutDirectional
    :: Lens' (GridLayout a) Int -> Lens' GridRange Int -> Lens' GridRange Int
    -> Lens' (GridLayout a) Int -> Lens' GridRange Int -> Lens' GridRange Int
    -> [GridLayout a] -> GridLayout a
alignLayoutDirectional gridLength sBegin sEnd
                       gridThickness zBegin zEnd
                           = align . map (\(ζ, h') -> ((0,h'), (h',(ζ,0))))
                                                  . xcat 0
 where align state = case sortBy (comparing $ snd . fst) state of
           (headSnail@((_,ySnail), _) : others)
             | ySnail < 1
               -> case break ((>ySnail) . snd . fst) others of
                    (snails, hares)
                      -> align $
                            [ ((ySnail, ySnail+h'), (h', (ζ,i+1)))
                            | (_, (h', (ζ,i))) <- headSnail : snails ]
                         ++ [ ((ySnail,yHare), (h', shiftup cH))
                            | ((_,yHare), (h', cH)) <- hares ]
           _   -> gather $ fst . snd . snd <$> state
       shiftup (ζ, i)
          = ( ζ & gridThickness %~ (+1)
                & gridContents . mapped
                         %~ \(range, a) -> (range & zBegin%~shift
                                                  & zEnd%~shift  , a)
            , i+1 )
        where shift j | j>i        = j+1
                      | otherwise  = j
       xcat _ [] = []
       xcat ix (ζ : cells)
          = ( ζ & gridContents . mapped . _1 %~ (sBegin %~(+ix))
                                              . (sEnd %~(+ix))
            , 1%(ζ^.gridThickness) )
              : xcat (ix + ζ^.gridLength) cells
       gather [ζ] = ζ
       gather (ζ₀ : others) = case gather others of
               ζo | ζ₀^.gridThickness == ζo^.gridThickness
                    -> ζo & gridLength %~ (ζ₀^.gridLength +)
                          & gridContents %~ (ζ₀^.gridContents ++)