{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Shoes where

import           Control.Monad.Identity
import           Control.Monad.Writer
import           Data.Text (Text)
import qualified Data.Text as T

-- | AST Type Definitions

data ShoesItem = ShoesItem
  { siItem  :: ShoesAST
  , siProps :: [Property]
  } deriving Show

newtype Action = Action { runAction :: IO () }

instance Show Action where
  show _ = "Action { ... }"

data ShoesAST
  = Button Text Action
  | Stack [ShoesItem]
  | Flow [ShoesItem]
  | Title Int Text
  | Para [Text]
  | Image Text
  | EditLine Text
  | EditBox Text
  | App ShoesAST
    deriving (Show)

data Property
  = Width ShoesUnit
  | Height ShoesUnit
  | Margin ShoesUnit
    deriving Show

-- | Number magic

instance (Num a, Num b, a ~ b) => Num ((Integer -> a) -> b) where
  fromInteger n f = f n
  (x + y) f = (x f + y f)
  (x - y) f = (x f - y f)
  (x * y) f = (x f * y f)
  abs x f = x (f . abs)
  signum x f = x (f . signum)

newtype Pixels = Pixels Int deriving (Eq, Show, Num)
newtype Percent = Percent Float deriving (Eq, Show, Num)
newtype Pt = Pt Int deriving (Eq, Show, Num)

-- data ShoesUnit
--   = Pixels Int
--   | Percent Float
--   | Pt Int
--    deriving (Eq, Show)

percent :: Integer -> Percent
percent x = Percent (fromIntegral x * 0.01)

px :: Integer -> Pixels
px = Pixels . fromIntegral

pt :: Integer -> Pt
pt = Pt . fromIntegral

data ShoesUnit
  = SUPixels Pixels
  | SUPercent Percent
  | SUPt Pt
    deriving (Eq, Show)

class ShoesNum x            where
  toShoesUnit :: x -> ShoesUnit

instance ShoesNum Pixels where
  toShoesUnit = SUPixels

instance ShoesNum Percent where
  toShoesUnit = SUPercent

instance ShoesNum Pt where
  toShoesUnit = SUPt

instance ShoesNum Integer   where
  toShoesUnit = SUPixels . Pixels . fromIntegral

-- | Property stuff

width :: ShoesNum n => n -> Property
width = Width . toShoesUnit

height :: ShoesNum n => n -> Property
height = Width . toShoesUnit

margin :: ShoesNum n => n -> Property
margin = Width . toShoesUnit

-- | Monad stuff

type ShoesM a = WriterT [ShoesItem] Identity a
type Shoes = ShoesM ()

shoesToItems :: Shoes -> [ShoesItem]
shoesToItems = snd . runIdentity . runWriterT

emit :: ShoesItem -> Shoes
emit x = tell [x]

class ShoesContainer a r | r -> a where
  mkC :: ([Property] -> Shoes -> Shoes) -> a -> r
  mkC constr = mkCW constr []

  mkCW :: ([Property] -> Shoes -> Shoes) -> [Property] -> a -> r

instance (a ~ Shoes, r ~ Shoes) => ShoesContainer [Property] (a -> r) where
  mkCW constr p p' = constr (p <> p')

instance ShoesContainer Shoes Shoes where
  mkCW constr p = constr p

--

class ShoesAction a r | r -> a where
  mkA :: ([Property] -> IO () -> Shoes) -> a -> r
  mkA c = mkAW c []
  mkAW :: ([Property] -> IO () -> Shoes) -> [Property] -> a -> r

instance ShoesAction [Property] (IO () -> Shoes) where
  mkAW c p p' = c (p <> p')

instance ShoesAction (IO ()) Shoes where
  mkAW c props = c props

--

class ShoesApp a r | r -> a where
  mkP :: ([Property] -> Shoes -> IO ()) -> a -> r
  mkP c = mkPW c []
  mkPW :: ([Property] -> Shoes -> IO ()) -> [Property] -> a -> r

instance ShoesApp [Property] (Shoes -> IO ()) where
  mkPW c p p' = c (p <> p')

instance ShoesApp Shoes (IO ()) where
  mkPW c props = c props

shoes :: ShoesApp a r => a -> r
shoes = mkP (\ _ shoes -> print $ shoesToItems shoes)

alert :: Text -> IO ()
alert _ = return ()

button :: ShoesAction a r => Text -> a -> r
button name = mkA $ \props action -> emit $ ShoesItem
  { siItem = Button name (Action action)
  , siProps = props
  }

stack :: ShoesContainer a r => a -> r
stack = mkC $ \props elem -> emit $ ShoesItem
  { siItem  = Stack (shoesToItems elem)
  , siProps = props
  }

flow :: ShoesContainer a r => a -> r
flow = mkC $ \props elem -> emit $ ShoesItem
  { siItem  = Flow (shoesToItems elem)
  , siProps = props
  }

text = undefined
image = undefined
editLine = undefined
tagline = undefined

para :: [Text] -> Shoes
para ts = emit (ShoesItem (Para ts) [])

caption = undefined
subtitle = undefined

editBox = undefined
ins = undefined
strong = undefined
em = undefined
code = undefined

banner :: Text -> Shoes
banner t = emit (ShoesItem (Title 1 t) [])

inscription = undefined
title = undefined

-- | Examples

pg20 :: IO ()
pg20 = shoes $ do
  button "Trurl?" $ do
    alert "Klapaucius!"

pg21 :: IO ()
pg21 = shoes [width (280 px), height (350 px)] $ do
  flow [width (280 px), width (10 px)] $ do
    stack [width (100 percent)] $ do
      banner "A POEM"
    stack [width (80 px)] $ do
      para [ "Goes like:" ]
    stack [width (-90 px)] $ do
      para [ "The sun.\n"
           , "a lemon.\n"
           , "the goalie.\n"
           , "a fireplace.\n\n"
           , "i want to write\n"
           , "a poem for the\n"
           , "kids who haven't\n"
           , "even heard one yet\n\n"
           , "and the goalie guards\n"
           , "the fireplace."
           ]

pg22 :: IO ()
pg22 = shoes $ do
  para [ "Testing test test. "
       , "Breadsticks. "
       , "Breadsticks. "
       , "Breadsticks. "
       , "Very good."
       ]

pg23 :: IO ()
pg23 = shoes $ do
  para [ "Testing test test. "
       , strong "Breadsticks. "
       , em "Breadsticks. "
       , code "Breadsticks. "
       , strong (ins "Very good.")
       ]

pg23' :: IO ()
pg23' = shoes $ do
  title "Title"
  subtitle "Subtitle"
  tagline "Tagline"
  caption "Caption"
  para ["Para"]
  inscription "Inscription"

{-
pg29 = shoes $ do
  o <- oval [top 0, left 0, radius 40]
  stack [margin 40] $ do
    title "Dancing With a Circle"
    subtitle "How graceful and round."
  motion $ \ x y -> do
    move x (width - x) (height - y)
-}

pg32 :: IO ()
pg32 = shoes $ do
  image "j.jpg"

pg35 :: IO ()
pg35 = shoes $ do
  editLine [width (400 px)]

pg35' :: IO ()
pg35' = shoes $ do
  e <- editLine [width (400 px)]
  button "O.K." $ do
    txt <- text e
    alert txt

pg36 :: IO ()
pg36 = shoes $ do
  editBox [width (400 px), height (240 px)] "Would that I..."

pg36' :: IO ()
pg36' = shoes $ do
  stack $ do
    editLine "Sample sample."
    button "Breadsticks." (return ())
