{-# LANGUAGE OverloadedStrings #-}
-----------------------------------------------------------------------------
-- |
-- Module      : Hasmin.Types.Position
-- Copyright   : (c) 2017 Cristian Adrián Ontivero
-- License     : BSD3
-- Stability   : experimental
-- Portability : non-portable
--
-----------------------------------------------------------------------------
module Hasmin.Types.Position (
      Position(..)
    , PosKeyword(..)
    , minifyPosition
    , p50
    , l0
    , centerpos
    ) where

import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Lazy.Builder (singleton, fromText)
import Data.Maybe (isJust, isNothing)
import Hasmin.Class
import Hasmin.Types.Dimension
import Hasmin.Types.PercentageLength
import Hasmin.Utils

data PosKeyword = PosCenter
                | PosLeft
                | PosRight
                | PosTop
                | PosBottom
  deriving (Show, Eq, Enum, Bounded)
instance ToText PosKeyword where
  toBuilder PosCenter = "center"
  toBuilder PosTop    = "top"
  toBuilder PosRight  = "right"
  toBuilder PosBottom = "bottom"
  toBuilder PosLeft   = "left"

-- TODO turn this into a proper algebraic type with all the cases, avoding Maybe
-- and making invalid Position values impossible.
data Position = Position { origin1 :: Maybe PosKeyword
                         , offset1 :: Maybe PercentageLength
                         , origin2 :: Maybe PosKeyword
                         , offset2 :: Maybe PercentageLength
                         } deriving (Show)
instance Minifiable Position where
  minify p = pure $ minifyPosition p

instance ToText Position where
  toBuilder (Position a b c d) = mconcatIntersperse fromText (singleton ' ') $ filter (not . T.null) [f a, f b, f c, f d]
    where f :: ToText a => Maybe a -> Text
          f = maybe mempty toText

minifyPosition :: Position -> Position
-- Single keyword
minifyPosition p@(Position (Just x) Nothing Nothing Nothing) = f x
  where mkPos1 y    = Position Nothing y Nothing Nothing
        f PosCenter = mkPos1 p50
        f PosRight  = mkPos1 p100
        f PosLeft   = mkPos1 l0
        f _         = p
-- Keyword and <lenght-percentage>
minifyPosition p@(Position (Just x) Nothing Nothing (Just y)) =
    minifyPos2 x y
  where mkPos2 i j = if isZero j
                        then if i == p50
                                then Position (Just PosTop) Nothing Nothing Nothing
                                else Position Nothing i Nothing l0
                        else Position Nothing i Nothing (Just j)
        minifyPos2 PosLeft a   = mkPos2 l0 a
        minifyPos2 PosRight a  = mkPos2 p100 a
        minifyPos2 PosCenter a = mkPos2 p50 a
        minifyPos2 _ _         = p
minifyPosition p@(Position Nothing (Just x) (Just y) Nothing) =
    minifyPos2 x y
  where mkPos2 i j = if isZero i
                        then Position Nothing l0 Nothing j
                        else Position Nothing (Just i) Nothing j
        minifyPos2 a PosTop    = mkPos2 a l0
        minifyPos2 a PosBottom = mkPos2 a p100
        minifyPos2 a PosCenter = mkPos2 a Nothing
        minifyPos2 _ _         = p
-- Two <lenght-percentage>
minifyPosition p@(Position Nothing (Just x) Nothing (Just y)) = f x y
  where f :: PercentageLength -> PercentageLength -> Position
        f (Left 50) (Left 50)   = Position Nothing p50 Nothing Nothing
        f (Left 50) (Left 100)  = Position (Just PosBottom) Nothing Nothing Nothing
        f (Left 100) (Left 100) = Position Nothing p100 Nothing p100
        f (Left 100) (Left 50)  = Position Nothing p100 Nothing Nothing
        f a b
          | isZero a = if isZero b
                          then Position Nothing l0 Nothing l0
                          else case b of
                                 Left 50  -> Position Nothing l0 Nothing Nothing
                                 Left 100 -> Position Nothing l0 Nothing p100
                                 _        -> p { offset1 = l0 }
          | isZero b = case a of
                         Left 50  -> Position (Just PosTop) Nothing Nothing Nothing
                         Left 100 -> Position Nothing p100 Nothing l0
                         _        -> p { offset2 = l0 }
          | b == Left 50 = Position Nothing (Just a) Nothing Nothing
          | otherwise    = p
minifyPosition (Position (Just x) (Just y) Nothing Nothing) =
  uncurry (\a b -> Position a b Nothing Nothing) (minAxis x y)
-- 2 keywords
minifyPosition p@(Position (Just x) Nothing (Just y) Nothing) = f x y
  where f :: PosKeyword -> PosKeyword -> Position
        -- 'center', 'center center' == '50% 50%'.
        f PosCenter PosCenter =  Position Nothing p50 Nothing Nothing
        -- 'left', 'left center', 'center left' == '0% 50%'.
        f PosLeft PosCenter   = Position Nothing l0 Nothing Nothing
        f PosCenter PosLeft   = Position Nothing l0 Nothing Nothing
        -- 'top left', 'left top' == '0% 0%'.
        f PosTop PosLeft      = Position Nothing l0 Nothing l0
        f PosLeft PosTop      = Position Nothing l0 Nothing l0
        -- 'top', 'top center', 'center top' == '50% 0%'.
        f PosTop PosCenter    = p { origin2 = Nothing }
        f PosCenter PosTop    = p { origin1 = Just PosTop, origin2 = Nothing }
        -- 'right top' and 'top right' == '100% 0%'.
        f PosRight PosTop     = Position Nothing p100 Nothing l0
        f PosTop PosRight     = Position Nothing p100 Nothing l0
        -- 'right', 'right center', 'center right' == '100% 50%'.
        f PosRight PosCenter  = Position Nothing p100 Nothing Nothing
        f PosCenter PosRight  = Position Nothing p100 Nothing Nothing
        -- 'bottom left' and 'left bottom' == '0% 100%'.
        f PosBottom PosLeft   = Position Nothing l0 Nothing p100
        f PosLeft PosBottom   = Position Nothing l0 Nothing p100
        -- 'bottom', 'bottom center', 'center bottom' == '50% 100%'.
        f PosBottom PosCenter = p { origin2 = Nothing }
        f PosCenter PosBottom = p { origin1 = Just PosBottom, origin2 = Nothing }
        -- 'bottom right', 'right bottom'             == '100% 100%'.
        f PosBottom PosRight = Position Nothing p100 Nothing p100
        f PosRight PosBottom = Position Nothing p100 Nothing p100
        f _ _ = p
-- keyword pl keyword syntax
minifyPosition p@(Position (Just x) (Just y) (Just z) Nothing)
    | x == PosTop || x == PosBottom || z == PosLeft || z == PosRight =
        minifyPosition $ Position (Just z) Nothing (Just x) (Just y)
    | otherwise = minifyPos3 x y z
  where minifyPos3 PosLeft b PosBottom
          | isZero b     = Position Nothing l0 Nothing p100
          | b == Left 50 = Position (Just PosBottom) Nothing Nothing Nothing
          | otherwise    = Position Nothing (Just b) Nothing p100
        minifyPos3 PosLeft b PosTop
          | isZero b     = Position Nothing l0 Nothing l0
          | b == Left 50 = Position (Just PosTop) Nothing Nothing Nothing
          | otherwise    = Position Nothing (Just b) Nothing l0
        minifyPos3 PosLeft b PosCenter
          | isZero b     = Position Nothing l0 Nothing Nothing
          | b == Left 50 = Position Nothing p50 Nothing Nothing
          | otherwise    = Position Nothing (Just b) Nothing Nothing
        minifyPos3 PosRight b PosTop
          | isZero b     = Position Nothing p100 Nothing l0
          | otherwise    = Position (Just PosRight) (Just b) Nothing l0
        minifyPos3 PosRight b PosBottom
          | isZero b     = Position Nothing p100 Nothing p100
          | otherwise    = Position (Just PosRight) (Just b) Nothing p100
        minifyPos3 PosRight b PosCenter
          | isZero b     = Position Nothing p100 Nothing Nothing
          | otherwise    = Position (Just PosRight) (Just b) Nothing p50
        minifyPos3 _ _ _ = p
minifyPosition p@(Position (Just c) Nothing (Just a) (Just b)) = f $ minAxis a b
  where isHorizontal i = i == PosLeft || i == PosRight
        f (x, y)
          | c == PosLeft && x == Just PosTop && isJust y = minifyPosition $ Position Nothing l0 Nothing y
          | c == PosCenter && isHorizontal a = Position x y Nothing Nothing
          | c == PosTop && isNothing x =
              if y == p50
                 then Position (Just PosTop) Nothing Nothing Nothing
                 else Position x y Nothing l0
          | c == PosBottom && isNothing x =
              if y == p50
                 then Position (Just PosBottom) Nothing Nothing Nothing
                 else Position x y Nothing p100
          | otherwise = if Just a == x && Just b == y
                           then p
                           else minifyPosition $ p {origin2 = x, offset2 = y }
-- 4 value syntax
minifyPosition p@(Position (Just PosLeft) (Just _) (Just PosTop) (Just _)) =
    minifyPosition p { origin1 = Nothing, origin2 = Nothing }
minifyPosition (Position (Just a) (Just b) (Just c) (Just d)) =
    minifyPos4 a b c d
minifyPosition p = p

-- Sort values so that the Xs are first, then the Ys, and later minify.
minifyPos4 :: PosKeyword -> PercentageLength -> PosKeyword -> PercentageLength -> Position
minifyPos4 v1 v2 v3 v4
    | v1 == PosTop || v1 == PosBottom || v3 == PosLeft || v3 == PosRight = minifyPos4' v3 v4 v1 v2
    | otherwise = minifyPos4' v1 v2 v3 v4
  where minifyPos4' PosLeft a PosTop b  = minifyPosition $ Position Nothing (Just a) Nothing (Just b)
        minifyPos4' PosRight a PosTop b
            | isZero a  = minifyPosition $ Position Nothing p100 Nothing (Just b)
            | isZero b  = minifyPosition $ Position (Just PosRight) (Just a) (Just PosTop) Nothing
            | otherwise = Position (Just PosRight) (Just a) (Just PosTop) (Just b)
        minifyPos4' PosLeft a PosBottom b
            | isZero b  = minifyPosition $ Position Nothing (Just a) Nothing p100
            | isZero a  = minifyPosition $ Position (Just PosLeft) Nothing (Just PosTop) (Just b)
            | otherwise = Position (Just PosLeft) (Just a) (Just PosBottom) (Just b)
        minifyPos4' PosRight a PosBottom b
            | isZero a && isZero b = Position Nothing p100 Nothing p100
            | otherwise            = Position (Just PosRight) (Just a) (Just PosBottom) (Just b)
        minifyPos4' a b c d = Position (Just a) (Just b) (Just c) (Just d)

minAxis :: PosKeyword -> PercentageLength -> (Maybe PosKeyword, Maybe PercentageLength)
minAxis PosTop x =
    case x of
      Left 50 -> (Just PosCenter, Nothing)
      b       -> if isZero b
                    then (Just PosTop, Nothing)
                    else (Just PosTop, Just x)
minAxis PosLeft x =
    case x of
      Left 50 -> (Nothing, p50)
      smth    -> if isZero smth
                    then (Nothing, l0)
                    else (Just PosLeft, Just x)
minAxis PosRight x
    | isZero x  = (Nothing, p100)
    | otherwise = (Just PosRight, Just x)
minAxis PosBottom x
    | isZero x  = (Just PosBottom, Nothing)
    | otherwise = (Just PosBottom, Just x)
minAxis PosCenter x = (Just PosCenter, Just x)

instance Eq Position where
  x == y = let (Position a b c d) = minifyPosition x
               (Position e f g h) = minifyPosition y
           in a == e && b == f && c == g && d == h
              || a == g && b == h && c == e && d == f

l0 :: Maybe PercentageLength
l0 = Just $ Right NullLength

p100 :: Maybe PercentageLength
p100 = Just $ Left 100

p50 :: Maybe PercentageLength
p50 = Just $ Left 50

centerpos :: Position
centerpos = Position (Just PosCenter) Nothing Nothing Nothing