{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE TypeFamilies          #-}
{-# LANGUAGE ViewPatterns          #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}

-----------------------------------------------------------------------------
-- |
-- Module      :  Diagrams.TwoD.Tilings
-- Copyright   :  (c) 2011 Brent Yorgey
-- License     :  BSD-style (see LICENSE)
-- Maintainer  :  byorgey@cis.upenn.edu
--
-- Tools for generating and drawing plane tilings made of regular
-- polygons.
--
-----------------------------------------------------------------------------
module Diagrams.TwoD.Tilings (

  -- * The ring Q[sqrt 2, sqrt 3]

    Q236, rt2, rt3, rt6

  , toFloating

  , Q2, toV2, toP2

  -- * Regular polygons

  , TilingPoly(..)
  , polySides, polyFromSides
  , polyCos, polySin
  , polyRotation, polyExtRotation

  -- * Tilings

  -- ** Types
  , Tiling(..)
  , Edge, mkEdge

  , Polygon(..)

  -- ** Generation

  , TilingState(..), initTilingState
  , TilingM

  , generateTiling

  -- ** Pre-defined tilings

  , t3, t4, t6
  , mk3Tiling, t4612, t488, t31212

  , t3636
  , semiregular
  , rot
  , t3464, t33434, t33344, t33336L, t33336R

  -- * Diagrams

  , drawEdge
  , drawPoly
  , polyColor
  , drawTiling
  , drawTilingStyled

  ) where

import           Control.Monad        (when, zipWithM_)
import           Control.Monad.State
import           Control.Monad.Writer

import           Data.Function        (on)
import           Data.List            (mapAccumL, sort)

import qualified Data.Foldable        as F
import qualified Data.Set             as S

import           Data.Colour
import           Diagrams.Prelude

------------------------------------------------------------
-- The ring Q[sqrt(2), sqrt(3)]
------------------------------------------------------------

-- Instead of using Doubles, which can't be compared for equality, it
-- suffices to use elements of the rationals with sqrt(2) and sqrt(3)
-- adjoined.

-- | @Q236 a b c d@ represents @a + b sqrt(2) + c sqrt(3) + d
--   sqrt(6)@.  Note that the @Ord@ instance is suitable for use in
--   `Map` and `Set`, but does not correspond to numeric ordering
--   (@Q236@ is not an ordered field under this ordering).
data Q236 = Q236 Rational Rational Rational Rational
  deriving (Eq, Ord, Show, Read)

-- | Convert a @Q236@ value to a @Double@.
toFloating :: Floating n => Q236 -> n
toFloating (Q236 a b c d) = fromRational a
                        + fromRational b * sqrt 2
                        + fromRational c * sqrt 3
                        + fromRational d * sqrt 6

rt2, rt3, rt6 :: Q236
rt2 = Q236 0 1 0 0
rt3 = Q236 0 0 1 0
rt6 = rt2*rt3

instance Num Q236 where
  (Q236 a1 b1 c1 d1) + (Q236 a2 b2 c2 d2)
      = Q236 (a1 + a2) (b1 + b2) (c1 + c2) (d1 + d2)
  (Q236 a1 b1 c1 d1) - (Q236 a2 b2 c2 d2)
      = Q236 (a1 - a2) (b1 - b2) (c1 - c2) (d1 - d2)
  (Q236 a1 b1 c1 d1) * (Q236 a2 b2 c2 d2) =
    Q236 (a1*a2 + 2*b1*b2 + 3*c1*c2 + 6*d1*d2)
         (a1*b2 + b1*a2 + 3*c1*d2 + 3*d1*c2)
         (a1*c2 + 2*b1*d2 + c1*a2 + 2*d1*b2)
         (a1*d2 + b1*c2 + c1*b2 + d1*a2)
  abs (Q236 a b c d) = Q236 (abs a) (abs b) (abs c) (abs d)
  fromInteger z = Q236 (fromInteger z) 0 0 0
  signum = error "no signum for Q236"

instance Fractional Q236 where
  recip q@(Q236 a b c d) = Q236 (a3/α) (b3/α) (c3/α) (d3/α)
    where
      q'                 = Q236 a (-b) (-c) d
      rs@(Q236 r 0 0 s)  = q * q'
      rs'                = Q236 r 0 0 (-s)
      (Q236 α 0 0 0)     = rs * rs'
      (Q236 a3 b3 c3 d3) = q' * rs'
  fromRational r = Q236 r 0 0 0

type Q2 = V2 Q236

toV2 :: Floating n => Q2 -> V2 n
toV2 = fmap toFloating

toP2 :: Floating n => Q2 -> P2 n
toP2 = P . toV2

------------------------------------------------------------
-- Polygons
------------------------------------------------------------

-- | Regular polygons which may appear in a tiling of the plane.
data TilingPoly = Triangle | Square | Hexagon | Octagon | Dodecagon
  deriving (Eq, Ord, Show, Read, Enum, Bounded)

polySides :: Num a => TilingPoly -> a
polySides Triangle  = 3
polySides Square    = 4
polySides Hexagon   = 6
polySides Octagon   = 8
polySides Dodecagon = 12

polyFromSides :: (Num a, Eq a, Show a) => a -> TilingPoly
polyFromSides 3  = Triangle
polyFromSides 4  = Square
polyFromSides 6  = Hexagon
polyFromSides 8  = Octagon
polyFromSides 12 = Dodecagon
polyFromSides n  = error $ "Bad polygon number: " ++ show n

-- | Cosine of a polygon's internal angle.
polyCos :: TilingPoly -> Q236
polyCos Triangle  = 1/2
polyCos Square    = 0
polyCos Hexagon   = -1/2
polyCos Octagon   = -1/2
polyCos Dodecagon = -1/2 * rt3

-- | Sine of a polygon's internal angle.
polySin :: TilingPoly -> Q236
polySin Triangle  = (1/2) * rt3
polySin Square    = 1
polySin Hexagon   = (1/2) * rt3
polySin Octagon   = (1/2) * rt2
polySin Dodecagon = 1/2

{-
   R_th = ( cos th  -sin th )
          ( sin th   cos th )

-}

-- | Rotate by polygon internal angle.
polyRotation :: TilingPoly -> Q2 -> Q2
polyRotation p (V2 x y) = V2 (x*c - y*s) (x*s + y*c)
  where c = polyCos p
        s = polySin p

{-
   (cos th  sin th)  ( -1  0 )  =  (-cos th  -sin th)
   (-sin th  cos th) ( 0  -1 )     (sin th   -cos th)
-}

-- | Rotate by polygon external angle.
polyExtRotation :: TilingPoly -> Q2 -> Q2
polyExtRotation p (V2 x y) = V2 (-x*c - y*s) (x*s - y*c)
  where c = polyCos p
        s = polySin p

------------------------------------------------------------
-- Tilings
------------------------------------------------------------

-- | A tiling, represented as a sort of zipper. @curConfig@ indicates
--   the polygons around the current vertex, in couterclockwise order
--   starting from the edge along which we entered the vertex.
--   @follow@ allows one to move along an edge to an adjacent vertex,
--   where the edges are numbered counterclockwise from zero,
--   beginning with the edge along which we entered the current
--   vertex.
data Tiling = Tiling { curConfig :: [TilingPoly]
                     , follow    :: Int -> Tiling
                     }

-- | An edge is represented by a pair of vertices.  Do not use the
--   @Edge@ constructor directly; use 'mkEdge' instead.
data Edge = Edge Q2 Q2
  deriving (Eq, Ord, Show)

-- | Smart constructor for @Edge@, which puts the vertices in a
--   canonical order.
mkEdge :: Q2 -> Q2 -> Edge
mkEdge v1 v2 | v1 <= v2  = Edge v1 v2
             | otherwise = Edge v2 v1

-- | A polygon is represented by a list of its vertices, in
--   counterclockwise order.  However, the @Eq@ and @Ord@ instances
--   for polygons ignore the order.
newtype Polygon = Polygon { polygonVertices :: [Q2] }
  deriving Show

instance Eq Polygon where
  (Polygon vs1) == (Polygon vs2) = sort vs1 == sort vs2

instance Ord Polygon where
  compare = compare `on` (sort . polygonVertices)

-- | The state maintained while generating a tiling, recording which
--   vertices have been visited and which edges and polygons have been
--   drawn.
data TilingState = TP { visitedVertices :: S.Set Q2
                      , visitedEdges    :: S.Set Edge
                      , visitedPolygons :: S.Set Polygon
                      }

initTilingState :: TilingState
initTilingState = TP S.empty S.empty S.empty

-- | The @TilingM@ monad tracks a @TilingState@, and can output
--   elements of some monoid @w@ along the way.
type TilingM w a = WriterT w (State TilingState) a

generateTiling :: forall w. Monoid w
               => Tiling        -- ^ The tiling to generate
               -> Q2            -- ^ The location of the starting vertex.
               -> Q2            -- ^ The starting direction, i.e. the
                                --   direction along which we came into
                                --   the starting vertex.
               -> (Q2 -> Bool)  -- ^ Predicate on vertices specifying
                                --   which should be visited.  The
                                --   vertices for which the predicate
                                --   evaluates to True must form a
                                --   single connected component.
               -> (Edge -> w)          -- ^ what to do with edges
               -> (Polygon -> w)       -- ^ what to do with polygons
               -> w
generateTiling t v d vPred e p
  = evalState (execWriterT (generateTiling' t v d)) initTilingState where

  generateTiling' :: Tiling -> Q2 -> Q2 -> TilingM w ()
  generateTiling' t v d
      -- stop if the current vertex fails the predicate
    | not (vPred v) = return ()
    | otherwise = do
        ts <- get

        -- stop if we've seen this vertex before
        when (v `S.notMember` visitedVertices ts) $ do

          -- otherwise, mark it as visited
          modify (\ts -> ts { visitedVertices = v `S.insert` visitedVertices ts })

          -- get the neighboring vertices and the polygons surrounding
          -- this vertex, and filter out ones we've already generated
          let (neighbors, polys) = genNeighbors t v d
              edges  = S.fromList $ map (mkEdge v) neighbors
              edges' = edges `S.difference` visitedEdges ts
              polys' = polys `S.difference` visitedPolygons ts

          -- generate some edges and polygons
          F.mapM_ (tell . e) edges'
          F.mapM_ (tell . p) polys'

          -- remember that we generated them
          modify (\ts -> ts { visitedEdges = edges' `S.union` visitedEdges ts })
          modify (\ts -> ts { visitedPolygons = polys' `S.union` visitedPolygons ts })

          -- follow edges and continue recursively
          zipWithM_ (\d i -> generateTiling' (follow t i) (v ^+^ d) d)
            (map (^-^ v) neighbors) [0..]

-- | Generate the neighboring vertices and polygons of a given vertex.
genNeighbors :: Tiling -> Q2 -> Q2 -> ([Q2], S.Set Polygon)
genNeighbors t v d = (neighbors, S.fromList polys) where
  (neighbors, polys)
    = unzip . snd
      $ mapAccumL
          (\d' poly -> (polyRotation poly d', (v ^+^ d', genPolyVs poly v d')))
          (negated d)
          (curConfig t)

-- | Generate the vertices of the given polygon, with one vertex at the given point
--   and an adjacent vertex at the given offset.
genPolyVs :: TilingPoly
          -> Q2          -- ^ one vertex
          -> Q2          -- ^ vector to second vertex
          -> Polygon
genPolyVs p v d = Polygon
                . scanl (^+^) v
                . take (polySides p - 1)
                . iterate (polyExtRotation p)
                $ d

------------------------------------------------------------
-- Diagrams
------------------------------------------------------------

-- | Draw an edge with the given style.
drawEdge :: (Renderable (Path V2 n) b, TypeableFloat n) =>
            Style V2 n -> Edge -> QDiagram b V2 n Any
drawEdge s (Edge v1 v2) = (toP2 v1 ~~ toP2 v2) # applyStyle s

-- | Draw a polygon with the given style.
drawPoly :: (Renderable (Path V2 n) b, TypeableFloat n) =>
            (Polygon -> Style V2 n) -> Polygon -> QDiagram b V2 n Any
drawPoly s p = applyStyle (s p) . strokeLocLoop . mapLoc closeLine . fromVertices . map toP2 . polygonVertices $ p

-- Simple per-polygon color scheme
polyColor :: (Floating a, Ord a) => TilingPoly -> Colour a
polyColor Triangle  = yellow
polyColor Square    = mediumseagreen
polyColor Hexagon   = blueviolet
polyColor Octagon   = lightsteelblue
polyColor Dodecagon = cornflowerblue

-- | Draw a tiling, with a given width and height and default colors
--   for the polygons.
drawTiling :: (Renderable (Path V2 n) b, TypeableFloat n)
           => Tiling -> n -> n -> QDiagram b V2 n Any
drawTiling =
  drawTilingStyled
    mempty
    (\p -> mempty
           # lw none
           # fc ( polyColor
                . polyFromSides
                . length
                . polygonVertices
                $ p
                )
    )

-- | Draw a tiling with customizable styles for the polygons.  This is
--   just an example, which you can use as the basis of your own
--   tiling-drawing routine.
drawTilingStyled :: forall b n. (Renderable (Path V2 n) b, TypeableFloat n)
                 => Style V2 n -> (Polygon -> Style V2 n)
                 -> Tiling -> n -> n -> QDiagram b V2 n Any
drawTilingStyled eStyle pStyle t w h =
  mkDia $ generateTiling t (V2 0 0) (V2 1 0) inRect

            -- draw the edges and polygons into separate
            -- diagrams, so we can make sure all the edges are
            -- overlaid on top of all the polygons at the end
            (liftA2 (,) (drawEdge eStyle) mempty)
            (liftA2 (,) mempty (drawPoly pStyle))
  where
    inRect (toV2 -> V2 x y) = -w/2 <= x && x <= w/2 && -h/2 <= y && y <= h/2
    mkDia (es, ps) = viewRect (es <> ps)
    viewRect = withEnvelope (rect w h :: D V2 n)

------------------------------------------------------------
-- Some pre-defined tilings
------------------------------------------------------------

-- Regular tilings

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t3D.svg#diagram=t3D&width=300>>
t3 :: Tiling
t3 = Tiling (replicate 6 Triangle) (const t3)

-- > import Diagrams.TwoD.Tilings
-- > t3D = drawTiling t3 10 10

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t4D.svg#diagram=t4D&width=300>>
t4 :: Tiling
t4 = Tiling (replicate 4 Square) (const t4)

-- > import Diagrams.TwoD.Tilings
-- > t4D = drawTiling t4 10 10

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t6D.svg#diagram=t6D&width=300>>
t6 :: Tiling
t6 = Tiling (replicate 3 Hexagon) (const t6)

-- > import Diagrams.TwoD.Tilings
-- > t6D = drawTiling t6 10 10

-- Semi-regular tilings

-- | Create a tiling with the same 3 polygons surrounding each vertex.
--   The argument is the number of sides of the polygons surrounding a vertex.
mk3Tiling :: [Int] -> Tiling
mk3Tiling (ps@[a,b,c])
      = Tiling
          (map polyFromSides ps)
          (\i -> case i `mod` 3 of
                   0 -> mk3Tiling (reverse ps)
                   1 -> mk3Tiling [a,c,b]
                   2 -> mk3Tiling [b,a,c]
                   _ -> error "i `mod` 3 is not 0, 1,or 2! the sky is falling!"
          )
mk3Tiling _ = error "mk3Tiling may only be called on a list of length 3."

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t4612D.svg#diagram=t4612D&width=300>>
t4612 :: Tiling
t4612 = mk3Tiling [4,6,12]

-- > import Diagrams.TwoD.Tilings
-- > t4612D = drawTiling t4612 10 10

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t488D.svg#diagram=t488D&width=300>>
t488 :: Tiling
t488 = mk3Tiling [4,8,8]

-- > import Diagrams.TwoD.Tilings
-- > t488D = drawTiling t488 10 10

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t31212D.svg#diagram=t31212D&width=300>>
t31212 :: Tiling
t31212 = mk3Tiling [3,12,12]

-- > import Diagrams.TwoD.Tilings
-- > t31212D = drawTiling t31212 10 10

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t3636D.svg#diagram=t3636D&width=300>>
t3636 :: Tiling
t3636 = mkT [3,6,3,6]
  where mkT :: [Int] -> Tiling
        mkT ps = Tiling (map polyFromSides ps)
                        (\i -> mkT $ if even i then reverse ps else ps)

-- > import Diagrams.TwoD.Tilings
-- > t3636D = drawTiling t3636 10 10

-- | Create a tiling where every vertex is the same up to rotation and
--   translation (but /not/ reflection).  Arbitrarily pick one of the
--   edges emanating from a vertex and number the edges
--   counterclockwise starting with 0 for the chosen edge.
semiregular :: [Int]   -- ^ The number of sides of the polygons
                       --   surrounding a typical vertex,
                       --   counterclockwise starting from edge 0.
            -> [Int]   -- ^ The transition list: if the /i/th entry of
                       --   this list is /j/, it indicates that the edge
                       --   labeled /i/ is labeled /j/ with respect to
                       --   the vertex on its other end.
            -> Tiling
semiregular ps trans = mkT 0
  where mkT i = Tiling
                  (map polyFromSides (rot i ps))
                  (\j -> mkT $ rot i trans !! j)

rot :: (Num a, Eq a) => a -> [t] -> [t]
rot 0 xs     = xs
rot _ []     = []
rot n (x:xs) = rot (n-1) (xs ++ [x])

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t3464D.svg#diagram=t3464D&width=300>>
t3464 :: Tiling
t3464 = semiregular [4,3,4,6] [3,2,1,0]

-- > import Diagrams.TwoD.Tilings
-- > t3464D = drawTiling t3464 10 10

{-

The above is worth a few lines of explanation.  There is only one type
of vertex, of degree 4, hence there are four possible states depending
on which edge one entered the vertex on.  We can arbitrarily choose
state 0 to be the one in which the surrounding polygons, ccw from the
edge on which the vertex was entered, are 4,3,4,6.  The second list
then records the states in which one ends up after following edges 0,
1, 2... (numbered ccw with edge 0 being the one entered on) starting
from state 0.  The transitions from other states can be worked out by
appropriate cyclic shifts.

The tilings below are worked out in a similar manner.

-}

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t33434D.svg#diagram=t33434D&width=300>>
t33434 :: Tiling
t33434  = semiregular [3,4,3,4,3] [0,2,1,4,3]

-- > import Diagrams.TwoD.Tilings
-- > t33434D = drawTiling t33434 10 10

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t33344D.svg#diagram=t33344D&width=300>>
t33344 :: Tiling
t33344  = semiregular [4,3,3,3,4] [0,4,2,3,1]

-- > import Diagrams.TwoD.Tilings
-- > t33344D = drawTiling t33344 10 10

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t33336LD.svg#diagram=t33336LD&width=300>>
t33336L :: Tiling
t33336L = semiregular [3,3,3,3,6] [4,1,3,2,0]

-- > import Diagrams.TwoD.Tilings
-- > t33336LD = drawTiling t33336L 10 10

-- | <<diagrams/src_Diagrams_TwoD_Tilings_t33336RD.svg#diagram=t33336RD&width=300>>
t33336R :: Tiling
t33336R = semiregular [3,3,3,3,6] [4,2,1,3,0]

-- > import Diagrams.TwoD.Tilings
-- > t33336RD = drawTiling t33336R 10 10
