{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Braids represented as Haskell types with support for generation and transformations.
--
-- = Braid Typeclass
--
-- `Braid` is a typeclass over the braid rep itself and its value type. Since a goal of this library is to use braids for non-mathematical purposes (ie music composition), a Braid can be indexed over any `Integral` type, to support braids representing pitch values in a register for instance.
--
-- = Generators
--
-- All braids are represented using Artin generators as `Gen`, with `Polarity` defining the "power" of a generator as `O`ver or `U`nder.
--
-- Generator indexes are usually 0-indexed, which differs from the 1-indexed generators in the literature. However, again these braids can represent other ranges of numbers as branch indexes.
--
-- = Braid instances
--
-- `Artin` creates canonical, "one-at-a-time", generator braids.
--
-- @
-- Artin [Gen 0 O,Gen 1 U]
-- @
--
-- `MultiGen` creates "compressed", "many-at-a-time" braids of `Step`s, which
-- prevent invalid adjacent generators.
--
-- @
-- MultiGen [Step (Gen 1 U) [Gen 0 U],Step (Gen 1 O) []]
-- @
--
-- `DimBraid` is for creating "padded" braids, since generators cannot express the absence of a cross.
--
-- = Birman\/Ko\/Lee generators.
--
-- `bandGen` creates Birman\/Ko\/Lee-style band generators.
--
-- = Transformations\/Moves
--
-- In addition to operations like `merge` etc, the type `Move` represents Reidemeister-type isotopy moves. `makeTree` unfolds a potentially-infinite tree representing all possible applications of a move.
--
-- = Graphics
--
-- `renderBraid`, `renderBraids` and `renderStrand` allow drawings of braids, admitting extra functions for colorizing etc.
--
-- @
-- renderBraid 60 [colorStrands] "braid.png" $ bandGen 0 5
-- @
--
-- <<http://i.imgur.com/JsK2D1p.png>>
--
--
module Fadno.Braids
    (
    -- * Braid Types
     module Fadno.Braids.Internal
    -- * Braid find\/merge\/clear
     ,find,find',merge,mergeAt,mergeAt',clear,clearMerge
    -- * Isotopy\/Reidemeister moves
      ,reidemeister2,reidemeister3,findMoves,applyMove,moves,makeTree
    -- * Band generators
     ,bandGen
    -- * Braid Graphics
    ,module Fadno.Braids.Graphics
    ) where
import Control.Lens hiding (op,(#),Empty)
import Fadno.Braids.Internal
import Fadno.Braids.Graphics
import Data.Tree
import Data.Default






-- | Birman, Ko, Lee "band generators" (sigma-s-t)
bandGen :: Integral a => a -> a -> Artin a
bandGen s t | s >= t = error "invalid, s >= t"
            | otherwise = Artin $
                          map (`Gen` O) (reverse [s + 1 .. t - 1]) ++
                          [Gen s O] ++
                          map (`Gen` U) [s + 1 .. t - 1]


-- | Merge one braid into another at offsets.
mergeAt :: (Integral a, Braid b a) => Int -> a -> b a -> b a -> MultiGen a
mergeAt x y ba bb = mergeAt' x y (toGens ba) (toGens bb)

-- | Matrix version.
mergeAt' :: forall a . (Integral a) => Int -> a -> [[Gen a]] -> [[Gen a]] -> MultiGen a
mergeAt' x y ba bb = MultiGen $ loop (offset ba) bb
    where loop :: [[Gen a]] -> [[Gen a]] -> [Step a]
          loop [] [] = []
          loop (a:as) [] = gensToStep a:loop as []
          loop [] (b:bs) = gensToStep b:loop [] bs
          loop (a:as) (b:bs) = foldl (flip insertS) (gensToStep a) b:loop as bs
          offset gens = replicate x [] ++ offsetGenVals y gens

offsetGenVals :: Integral a => a -> [[Gen a]] -> [[Gen a]]
offsetGenVals y = map (map (fmap (+y)))

normalGenVals :: Integral a => [[Gen a]] -> [[Gen a]]
normalGenVals gs = offsetGenVals (- (minimum . map _gPos $ concat gs)) gs

-- | Rectangular gen eraser.
clear :: Integral a => Int -> a -> Int -> a -> [[Gen a]] -> [[Gen a]]
clear x y w h = clrx 0 where
    clrx _ [] = []
    clrx xi (s:ss) = (if xi >= x && xi < x + w then clry s else s):clrx (succ xi) ss
    clry [] = []
    clry (g@(Gen i _):gs) | i >= y && i < y + h = clry gs
                          | otherwise = g:clry gs


-- | Merge one braid into another.
merge :: forall b a . (Integral a, Braid b a) => b a -> b a -> MultiGen a
merge = mergeAt 0 0

_bands :: MultiGen Int
_bands = mergeAt 5 5 (bandGen 0 4) (bandGen 0 9)


_testpath :: FilePath
_testpath = "output/test.png"

-- renderBraid :: Braid b a => Int -> [BraidDrawF a] -> FilePath -> b a -> IO ()

_drawLoops,_drawStrands :: (Show a, Integral a, Braid b a) => b a -> IO ()
_drawLoops = renderBraid def { stepWidth = 400 } [colorLoops] _testpath
_drawStrands = renderBraid def { stepWidth = 400 } [colorStrands] _testpath




-- | Reidemeister move 2, [s1,s1^-1] === flat
reidemeister2 :: Integral a => Move Artin a
reidemeister2 = Move (Artin [Gen 0 O,Gen 0 U]) (Artin [])

-- | Reidemeister move 3, [s1,s2,s1^-1] === [s2^-1,s1,s2], and inverse polarity.
-- Rule: a pattern of [(i,p),(i',p),(i,^p)] moves to [(i',^p),(i,p),(i',p)],
-- where i' = i `op` i where op is plus or minus; with the reversed lists too.
reidemeister3 :: Integral a => [Move Artin a]
reidemeister3 = [ mk (zipWith Gen is ps) | ps <- [[U,U,O],[O,O,U]], is <- [[0,1,0],[1,0,1]]]
    where mk gs = Move (Artin gs) $
                        Artin $ over (traverse.gPos)
                                  (\i -> if i == 0 then 1 else 0) (reverse gs)

_drawReid3s :: IO ()
_drawReid3s = renderBraids def { stepWidth = 80 } [colorStrands] "output/reid3.png" $
             map (\(Move a b) -> [a,b]) (reidemeister3 :: [Move Artin Int])


-- drawMove (last reidemeister3) bands
_drawMove :: (Integral i, Braid a i, Braid b i) => Move a i -> b i -> IO ()
_drawMove m b = renderBraids def { stepWidth = 80 } [colorStrands] "output/move.png"
               [toMultiGen b:map toMultiGen [view _1 m, view _2 m],
                --[toMultiGen b],
                map (\l -> applyMove m l b) (findMoves m b)]




-- | Find all locations of a sub-braid within a braid.
find :: (Integral i, Braid a i, Braid b i) => a i -> b i -> [Loc i]
find ba = find' (normalGenVals $ toGens ba) (stepCount ba) (strandCount ba)

-- | Find all locations of a sub-braid within a braid, matrix version.
find' :: (Integral i, Braid b i) => [[Gen i]] -> Int -> i -> b i -> [Loc i]
find' ba w h bb = [ Loc x y | x <- [0 .. stepCount bb] ,
                    y <- [minIndex bb .. maxIndex bb] ,
                    test x y ]
    where gba = normalGenVals ba
          gbb = toGens bb
          test x y = gbb == toGens (clearMerge gba x y w h gbb)

-- | clear generators and merge. TODO: doesn't clear adjacent gens.
clearMerge :: Integral a =>
     [[Gen a]] -> Int -> a -> Int -> a -> [[Gen a]] -> MultiGen a
clearMerge ba x y w h = mergeAt' x y ba . clear x y w h

-- | Locates all move location in a braid.
findMoves :: (Integral i, Braid a i, Braid b i) => Move a i -> b i -> [Loc i]
findMoves m@(Move m1 _) = find' (toGens m1) (moveW m) (moveH m)


-- | Apply a move at a location.
applyMove :: (Integral i, Braid a i, Braid b i) => Move a i -> Loc i -> b i -> MultiGen i
applyMove m@(Move _ m2) (Loc x y)  =
    clearMerge (toGens m2) x y (moveW m) (moveH m) . toGens

-- | Test a collection of moves against a braid and pair results with location.
moves :: (Integral i, Braid a i, Braid b i) => [Move a i] -> b i -> [(Move a i,[Loc i])]
moves mvs target = filter (not . null . snd) $ map (\m -> (m, findMoves m target)) mvs


-- | Unfold a tree of all possible move applications on a braid.
-- A permutation is the permuted braid + the [(move,loc)]s that got us there.
-- Thus the root is (original braid, []); children are [(b1,(move,loc))].
makeTree :: (Integral i, Braid a i, Braid b i) =>
            [Move a i] -> b i -> Tree (MultiGen i,[(Move a i,Loc i)])
makeTree mvs org = unfoldTree go (toMultiGen org,[]) where
    go n@(seed,path) = (n,concatMap (gen seed path) $ moves mvs seed)
    gen target path (mv,locs) = map (\l -> (applyMove mv l target,(mv,l):path)) locs


-- let t = makeTree reidemeister3 _bands
-- let t = makeTree (reidemeister3 ++ map inverse reidemeister3) _bands
-- renderBraids 100 [colorStrands] "allmoves.png" $ nub $ map (return.fst) $ flatten t

zipTail :: (a -> a -> c) -> [a] -> [c]
zipTail f = zipWith f <*> tail