{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ConstraintKinds #-}

module Draw.PuzzleTypes (
    lits, litsplus, geradeweg, fillomino, masyu, nurikabe, latintapa,
    sudoku, thermosudoku, pyramid, kpyramid, slither,
    liarslither, tightfitskyscrapers, wordloop, wordsearch,
    curvedata, doubleback, slalom, compass, boxof2or3,
    afternoonskyscrapers, meanderingnumbers, tapa, japanesesums,
    coral, maximallengths, primeplace, labyrinth, bahnhof,
    cave, angleLoop, shikaku, slovaksums,
    blackoutDominos, anglers, skyscrapers,
    summon, baca, buchstabensalat, doppelblock, sudokuDoppelblock,
    dominos, skyscrapersStars, fillominoCheckered, numberlink,
    slithermulti, dominoPills, fillominoLoop, loopki, litssym,
    scrabble, neighbors, starwars, heyawake, wormhole, pentominous,
    starbattle, colorakari, persistenceOfMemory, abctje, kropki,
    statuepark, pentominousBorders, nanroSignpost, tomTom,
    horseSnake, illumination, pentopia,
    pentominoPipes, greaterWall, galaxies, mines, tents,
    pentominoSums, coralLits, coralLitso, snake, countryRoad,
    killersudoku, friendlysudoku, japsummasyu
  ) where

import Diagrams.Prelude hiding (Loop, N, coral, size)

import Data.Char (isUpper)
import Data.List (nub, sort, sortOn)
import qualified Data.Map.Strict as Map

import Draw.Style
import Draw.PuzzleGrids
import Draw.Draw
import Draw.Grid
import qualified Draw.Pyramid as DPyr
import Draw.Elements
import Draw.Lib
import Draw.Widths

import Data.Grid
import Data.GridShape
import Data.Elements
import qualified Data.Pyramid as Pyr

unimplemented :: String -> a
unimplemented x = error (x ++ " unimplemented")

lits :: Backend' b => Drawers b AreaGrid ShadedGrid
lits = drawers
    (grid gDefault <> drawAreasGray)
    ((drawAreas <> grid gDefault) . fst <> drawShade . snd)

litsplus :: Backend' b => Drawers b AreaGrid ShadedGrid
litsplus = lits

litssym :: Backend' b => Drawers b AreaGrid ShadedGrid
litssym = drawers
    p
    (p . fst <> drawShade . snd)
  where
    p g = drawAreas g <> grid gDefault g <> translate (c g) (scale 0.5 $ smallPearl MBlack)
    c g = let (rs, cs) = size . Map.mapKeys toCoord $ g
          in r2 ((fromIntegral rs) / 2, (fromIntegral cs) / 2)

solstyle :: (HasStyle a, InSpace V2 Double a) => a -> a
solstyle = lc (blend 0.8 black white) . lwG (3 * onepix)

geradeweg :: Backend' b => Drawers b (Grid C (Maybe Int)) (Loop C)
geradeweg = drawers
    drawIntGrid
    (placeGrid . fmap drawInt . clues . fst
     <> solstyle . drawEdges . snd
     <> grid gDefault . fst)

fillomino :: Backend' b => Drawers b (Grid C (Maybe Int)) (Grid C Int)
fillomino = drawers
    (placeGrid . fmap drawInt . clues <> grid gDashed)
    ((placeGrid . fmap drawInt <> drawEdges . borders <> grid gDashed) . snd)

fillominoCheckered :: Backend' b => Drawers b (Grid C (Maybe Int)) (Grid C Int)
fillominoCheckered = drawers
    (placeGrid . fmap  drawInt . clues <> grid gDashed)
    ((placeGrid . fmap drawInt
      <> drawEdges . borders
      <> grid gDashed
      <> shadeGrid . checker) . snd)
  where
    checker = fmap pickColour . colour
    pickColour 1 = Nothing
    pickColour 2 = Just gray
    pickColour _ = Just red

fillominoLoop :: Backend' b => Drawers b (Grid C (Maybe Int))
                                              (Grid C Int, Loop C)
fillominoLoop = drawers
    (placeGrid . fmap drawInt . clues <> grid gDashed)
    ((placeGrid . fmap drawInt . fst
      <> solstyle . drawEdges . snd
      <> drawEdges . borders . fst
      <> grid gDashed . fst) . snd)

masyu :: Backend' b =>
         Drawers b (Grid C (Maybe MasyuPearl)) (Loop C)
masyu = drawers
    p
    (solstyle . drawEdges . snd <> p . fst)
  where
    p = placeGrid . fmap pearl . clues <> grid gDefault

nurikabe :: Backend' b =>
            Drawers b (Grid C (Maybe Int)) ShadedGrid
nurikabe = drawers
    drawIntGrid
    (drawIntGrid . fst <> drawShade . snd)

latintapa :: Backend' b =>
             Drawers b (Grid C (Maybe [String])) (Grid C (Maybe Char))
latintapa = drawers
    l
    (l . fst <> placeGrid . fmap drawChar . clues . snd)
  where
    l = grid gDefault <> drawWordsClues

sudoku :: Backend' b =>
          Drawers b (Grid C (Maybe Int)) (Grid C (Maybe Int))
sudoku = drawers
    (placeGrid . fmap drawInt . clues <> sudokugrid)
    ((placeGrid . fmap drawInt . clues <> sudokugrid) . snd)

thermosudoku :: Backend' b =>
                Drawers b (Grid C (Maybe Int), [Thermometer]) (Grid C (Maybe Int))
thermosudoku = drawers
    (placeGrid . fmap drawInt . clues . fst <> sudokugrid . fst <> drawThermos . snd)
    (placeGrid . fmap drawInt . clues . snd <> sudokugrid . snd <> drawThermos . snd . fst)

killersudoku :: Backend' b =>
                Drawers b (AreaGrid, Map.Map Char Int, Grid C (Maybe Int)) (Grid C Int)
killersudoku = drawers
    (p <> placeGrid . fmap drawInt . clues . trd3)
    (placeGrid . fmap drawInt . snd <> p . fst)
  where
    fst3 (x,_,_) = x
    trd3 (_,_,z) = z
    p = cages <> sudokugrid . fst3
    cages (g, m, _) = drawCages (Map.filter (/= '.') g) (Map.map drawInt m)

pyramid :: Backend' b =>
    Drawers b Pyr.Pyramid Pyr.PyramidSol
pyramid = drawers
    DPyr.pyramid
    (DPyr.pyramid . merge)
  where
    merge (p, q) = Pyr.mergepyramidsol p q

kpyramid :: Backend' b =>
    Drawers b Pyr.RowKropkiPyramid Pyr.PyramidSol
kpyramid = drawers
    DPyr.kpyramid
    (DPyr.kpyramid . merge)
  where
    merge (p, q) = Pyr.mergekpyramidsol p q

slither :: Backend' b =>
           Drawers b (Grid C (Maybe Int)) (Loop N)
slither = drawers
    drawSlitherGrid
    (drawSlitherGrid . fst <> solstyle . drawEdges . snd)

liarslither :: Backend' b =>
               Drawers b (Grid C (Maybe Int)) (Loop N, Grid C Bool)
liarslither = drawers
    drawSlitherGrid
    (placeGrid . fmap (solstyle . drawCross) . snd . snd
     <> drawSlitherGrid . fst
     <> solstyle . drawEdges . fst . snd)

slithermulti :: Backend' b =>
                Drawers b (Grid C (Maybe Int), Int) [Edge N]
slithermulti = drawers
    (drawSlitherGrid . fst <> n)
    (drawSlitherGrid . fst . fst <> solstyle . drawEdges . snd)
  where
    n (g, l) = placeNoteTR (size' g) (drawInt l ||| strutX 0.2 ||| miniloop)
    size' = size . Map.mapKeys toCoord

tightfitskyscrapers :: Backend' b =>
                       Drawers b (OutsideClues C (Maybe Int), Grid C (Tightfit ()))
                                      (Grid C (Tightfit Int))
tightfitskyscrapers = drawers
    (placeGrid . fmap drawInt . clues . outsideClues . fst
     <> drawTightGrid (const mempty) . snd)
    (placeGrid . fmap drawInt . clues . outsideClues . fst . fst
     <> drawTightGrid drawInt . snd)

wordgrid :: Backend' b =>
            Grid C (Maybe Char) -> [String] -> Diagram b
wordgrid g ws = stackWords ws `besidesR` drawCharGrid g

wordloop :: Backend' b =>
            Drawers b (Grid C (Maybe Char), [String]) (Grid C (Maybe Char))
wordloop = drawers
    (uncurry wordgrid)
    (drawCharGrid . snd)

wordsearch :: Backend' b =>
              Drawers b (Grid C (Maybe Char), [String])
                             (Grid C (Maybe Char), [MarkedWord])
wordsearch = drawers
    (uncurry wordgrid)
    (solstyle . drawMarkedWords . snd . snd
     <> drawCharGrid . fst . snd)

curvedata :: Backend' b =>
             Drawers b (Grid C (Maybe [Edge N])) [Edge C]
curvedata = drawers
    (placeGrid . fmap drawCurve . clues
     <> grid gDefault)
    (placeGrid . fmap drawCurve . clues . fst
     <> solstyle . drawEdges . snd
     <> grid gDefault . fst)

doubleback :: Backend' b =>
              Drawers b AreaGrid (Loop C)
doubleback = drawers
    p
    (solstyle . drawEdges . snd <> p . fst)
  where
    p = grid gDefault <> drawAreasGray

slalom :: Backend' b =>
          Drawers b (Grid N (Maybe Int)) (Grid C SlalomDiag)
slalom = drawers
    p
    (p . fst <> placeGrid . fmap (solstyle . drawSlalomDiag) . snd)
  where
    p = placeGrid . fmap drawSlalomClue . clues
        <> grid gDefault . cellGrid

compass :: Backend' b =>
           Drawers b (Grid C (Maybe CompassC)) AreaGrid
compass = drawers
    (placeGrid . fmap drawCompassClue . clues <> grid gDashed)
    (placeGrid . fmap drawCompassClue . clues . fst
     <> (grid gDashed <> drawAreasGray) . snd)

boxof2or3 :: Backend' b =>
             Drawers b (Grid N MasyuPearl, [Edge N]) ()
boxof2or3 = drawers
    (placeGrid . fmap smallPearl . fst
     <> drawThinEdges . snd)
    (unimplemented "boxof2or3 solution")

afternoonskyscrapers :: Backend' b =>
                        Drawers b (Grid C Shade) (Grid C (Maybe Int))
afternoonskyscrapers = drawers
    (grid gDefault <> placeGrid . fmap drawShadow)
    (drawIntGrid . snd <> placeGrid . fmap drawShadow . fst)

meanderingnumbers :: Backend' b =>
                        Drawers b AreaGrid (Grid C (Maybe Int))
meanderingnumbers = drawers
    (grid gDefault <> drawAreas)
    (drawIntGrid . snd <> drawAreas . fst)

tapa :: Backend' b =>
        Drawers b (Grid C (Maybe TapaClue)) ShadedGrid
tapa = drawers
    tapaGrid
    (tapaGrid . fst <> drawShade . snd)
  where
    tapaGrid = placeGrid . fmap drawTapaClue . clues <> grid gDefault

japanesesums :: Backend' b =>
                Drawers b (OutsideClues C [Int], String)
                               (Grid C (Either Black Int))
japanesesums = drawers
    (outsideIntGrid . fst <> n)
    (outsideIntGrid . fst . fst <> japcells . snd)
  where
    n (ocs, ds) = placeNoteTL (0, h ocs) (drawText ds # scale 0.8)
    japcells = placeGrid . fmap japcell
    japcell (Left Black) = fillBG gray
    japcell (Right x) = drawInt x
    h = snd . outsideSize

coral :: Backend' b =>
          Drawers b (OutsideClues C [String]) ShadedGrid
coral = drawers
    drawMultiOutsideGrid
    (drawMultiOutsideGrid . fst <> drawShade . snd)

maximallengths :: Backend' b =>
                  Drawers b (OutsideClues C (Maybe Int)) (Loop C)
maximallengths = drawers
    g
    (solstyle . drawEdges . snd <> g . fst)
  where
    g = placeGrid . fmap drawInt . clues . outsideClues
        <> grid gDefault . outsideGrid

primeplace :: Backend' b =>
              Drawers b (Grid C PrimeDiag) (Grid C Int)
primeplace = drawers
    g
    (placeGrid . fmap drawInt . snd <> g . fst)
  where
    g = grid gStyle
        <> placeGrid . fmap drawPrimeDiag
    gStyle = GridStyle LineThin LineThick Nothing VertexNone

labyrinth :: Backend' b =>
             Drawers b (Grid C (Maybe Int), [Edge N], String) (Grid C (Maybe Int))
labyrinth = drawers
    (placeGrid . fmap drawInt . clues . fst3 <> p <> n)
    (placeGrid . fmap drawInt . clues . snd <> p . fst)
  where
    p (g, e, _) = drawEdges e <> grid gPlain g
    n (g, _, ds) = placeNoteTR (size' g) (drawText ds # scale 0.8)
    size' = size . Map.mapKeys toCoord
    fst3 (x,_,_) = x

bahnhof :: Backend' b =>
            Drawers b (Grid C (Maybe BahnhofClue)) [Edge C]
bahnhof = drawers
    (placeGrid . fmap drawBahnhofClue . clues <> grid gDefault)
    (placeGrid . fmap drawBahnhofStation . clues . fst
     <> solstyle . drawEdges . snd
     <> grid gDefault . fst)
  where
    drawBahnhofStation = either drawInt (const mempty)

blackoutDominos :: Backend' b =>
                   Drawers b (Grid C (Clue Int), DigitRange)
                                  (Grid C (Clue Int), AreaGrid)
blackoutDominos = drawers
    p
    ((placeGrid . fmap drawInt . clues . fst
      <> grid gDashedThick . fst
      <> drawAreas . snd
      <> shadeGrid . fmap cols . snd) . snd)
  where
    p (g, ds) = (placeGrid . fmap drawInt . clues <> grid gDashedThick $ g)
                `aboveT`
                drawDominos ds
    cols 'X' = Just gray
    cols _   = Nothing

angleLoop ::
    Backend' b =>
    Drawers b (Grid N (Clue Int)) VertexLoop
angleLoop = drawers
    (cs <> gr)
    (cs . fst
     <> lineJoin LineJoinBevel . solstyle . strokeLocLoop . vertexLoop . snd
     <> gr . fst)
  where
    cs = placeGrid . fmap drawAnglePoly . clues
    gr = grid gPlainDashed . cellGrid

anglers ::
    Backend' b =>
    Drawers b (OutsideClues C (Clue Int), Grid C (Maybe Fish)) [Edge C]
anglers = drawers
    (p <> g)
    (p . fst <> solstyle . drawEdges . snd <> g . fst)
  where
    p = placeGrid . fmap drawInt' . clues . outsideClues . fst <>
        placeGrid . fmap drawFish' . clues . snd
    g = grid gDefault . snd
    drawInt' x = drawInt x <> (square 0.6 # lc white # fc white)
    drawFish' x = drawFish x <> (square 0.6 # lc white # fc white)

cave ::
    Backend' b =>
    Drawers b (Grid C (Maybe Int)) ShadedGrid
cave = drawers
    (grid gDashDash <> placeGrid . fmap drawInt . clues)
    (drawEdges . edgesGen (/=) not . snd
     <> placeGrid . fmap drawInt . clues . fst
     <> drawShade . snd
     <> grid gStyle . fst)
  where
    gDashDash = GridStyle LineDashed LineDashed Nothing VertexNone
    gStyle = GridStyle LineDashed LineNone (Just $ FrameStyle 8 gray)
                       VertexNone

skyscrapers ::
    Backend' b =>
    Drawers b (OutsideClues C (Maybe Int), String) (Grid C (Maybe Int))
skyscrapers = drawers
    (g . fst <> n)
    (g . fst . fst <> placeGrid . fmap drawInt . clues . snd)
  where
    g = placeGrid . fmap drawInt . clues . outsideClues
        <> grid gDefault . outsideGrid
    n (oc, s) = placeNoteTR (outsideSize oc) (drawText s)

shikaku :: Backend' b => Drawers b (Grid C (Maybe Int)) AreaGrid
shikaku = drawers
    p
    (drawAreas . snd <> p . fst)
  where
    p = placeGrid . fmap drawInt . clues <> grid gDashed

slovaksums :: Backend' b => Drawers b (Grid C (Maybe SlovakClue), String) (Grid C (Maybe Int))
slovaksums = drawers
    (p . fst <> n)
    (placeGrid . fmap drawInt . clues . snd <> p . fst . fst)
  where
    n (g, ds) = placeNoteTR (size' g) (drawText ds # scale 0.8)
    p = grid gDefault <> placeGrid . fmap drawSlovakClue . clues
    size' = size . Map.mapKeys toCoord

skyscrapersStars ::
    Backend' b =>
    Drawers b (OutsideClues C (Maybe Int), Int)
                   (Grid C (Either Int Star))
skyscrapersStars = drawers
    (g <> n)
    (g . fst <> placeGrid . fmap (either drawInt drawStar) . snd)
  where
    g = (placeGrid . fmap drawInt . clues . outsideClues
         <> grid gDefault . outsideGrid) . fst
    n (oc, s) = placeNoteTR (outsideSize oc)
                          (drawInt s ||| strutX 0.2 ||| drawStar Star)

summon ::
    Backend' b =>
    Drawers b (AreaGrid, OutsideClues C (Maybe Int), String) (Grid C (Maybe Int))
summon = drawers
    (p <> n)
    (placeGrid . fmap drawInt . clues . snd <> p . fst)
  where
    p (g, oc, _) = grid gDefault g <> drawAreasGray g
                <> (placeGrid . clues . outsideClues
                    . al . fmap (fmap (scale 0.7 . drawInt)) $ oc)
    al :: Backend' b => OutsideClues k (Maybe (Diagram b)) -> OutsideClues k (Maybe (Diagram b))
    al (OC l r b t) = OC l (map (fmap alignL) r) b t

    n (g, _, ds) = placeNoteBR (size' g) (drawText ds # scale 0.7)
    size' = size . Map.mapKeys toCoord

baca ::
    Backend' b =>
    Drawers b (Grid C (Maybe Char),
                    OutsideClues C [Int],
                    OutsideClues C (Maybe Char))
                   (Grid C (Either Black Char))
baca = drawers
    (inside <> outside)
    (outside . fst <> placeGrid . fmap drawVal . snd <> inside . fst)
  where
    inside (g,_,_) = placeGrid . fmap (fc gray . drawChar) . clues $ g
    outside (g,tl,br) =
              grid gDefault g
              <> (placeGrid . fmap drawInt
                  . multiOutsideClues $ tl)
              <> (placeGrid . fmap drawChar . clues
                  . outsideClues $ br)
    drawVal (Right c) = drawChar c
    drawVal (Left _) = fillBG gray

buchstabensalat ::
    Backend' b =>
    Drawers b (OutsideClues C (Maybe Char), String) (Grid C (Maybe Char))
buchstabensalat = drawers
    (p <> n)
    (p . fst <> placeGrid . fmap drawChar . clues . snd)
  where
    p = (placeGrid . fmap drawChar . clues . outsideClues
         <> grid gDefault . outsideGrid) . fst
    n (ocs, ls) = placeNoteTR (outsideSize ocs) (drawText ls # scale 0.8)

doppelblock ::
    Backend' b =>
    Drawers b (OutsideClues C (Maybe Int))
              (Grid C (Either Black Int))
doppelblock = drawers
    (p <> n)
    (p . fst <> placeGrid . fmap drawVal . snd)
  where
    p = placeGrid . fmap (scale 0.8 . drawInt) . clues . outsideClues
        <> grid gDefault . outsideGrid
    n ocs = placeNoteTL (0, h) (drawText ds # scale 0.8)
      where
        h = snd (outsideSize ocs)
        ds = "1-" ++ show (h - 2)
    drawVal (Right c) = drawInt c
    drawVal (Left _) = fillBG gray

sudokuDoppelblock ::
    Backend' b =>
    Drawers b (AreaGrid, OutsideClues C (Maybe Int))
                   (Grid C (Either Black Int))
sudokuDoppelblock = drawers
    p
    (p . fst <> placeGrid . fmap drawVal . snd)
  where
    p = placeGrid . fmap (scale 0.8 . drawInt) . clues . outsideClues . snd
        <> (grid gDefault <> drawAreas) . fst
    drawVal (Right c) = drawInt c
    drawVal (Left _) = fillBG gray

dominos ::
    Backend' b =>
    Drawers b (Grid C (Clue Int), DigitRange) AreaGrid
dominos = drawers
    p
    (placeGrid . fmap drawInt . clues . fst . fst
     <> (grid gDashed <> drawAreasGray) . snd)
  where
    p (g, r) =
        ((placeGrid . fmap drawInt . clues <> grid gDashed) $ g)
        `aboveT`
        drawDominos r

dominoPills ::
    Backend' b =>
    Drawers b (Grid C (Clue Int), DigitRange, DigitRange) AreaGrid
dominoPills = drawers
    p
    (placeGrid . fmap drawInt . clues . fst3 . fst
     <> (grid gDashed <> drawAreasGray) . snd)
  where
    fst3 (a,_,_) = a
    p (g, ds, ps) =
        ((placeGrid . fmap drawInt . clues <> grid gDashed) $ g)
        `aboveT`
        (drawDominos ds ||| strutX 0.5 ||| drawPills ps)

numberlink ::
    Backend' b =>
    Drawers b (Grid C (Maybe Int)) [Edge C]
numberlink = drawers
    drawIntGrid
    (placeGrid . fmap drawInt' . clues . fst
     <> solstyle . drawEdges . snd
     <> grid gDefault . fst)
  where
    drawInt' x = drawInt x <> (square 0.7 # lc white # fc white)

loopki :: Backend' b =>
          Drawers b (Grid C (Maybe MasyuPearl)) (Loop N)
loopki = drawers
    p
    (solstyle . drawEdges . snd <> p . fst)
  where
    p = placeGrid . fmap (scale 0.5 . pearl) . clues <> grid gSlither

scrabble :: Backend' b =>
            Drawers b (Grid C Bool, [String]) (Grid C (Maybe Char))
scrabble = drawers
    p
    (placeGrid . fmap drawCharFixed . clues . snd <> gr . fst . fst)
  where
    p (g, ws) = stackWords ws `besidesR` gr g
    gr = grid gDefault <> drawShade

neighbors :: Backend' b =>
             Drawers b (Grid C Bool, Grid C (Maybe Int)) (Grid C Int)
neighbors = drawers
    (placeGrid . fmap drawInt . clues . snd <> (grid gDefault <> drawShade) . fst)
    (placeGrid . fmap drawInt . snd <> (grid gDefault <> drawShade) . fst . fst)

starwars :: Backend' b =>
            Drawers b (AreaGrid, [MarkedLine C]) (Grid C (Maybe Star))
starwars = drawers
    p
    (p . fst <> placeGrid . fmap drawStar . clues . snd)
  where
    p = ((drawAreas <> grid gDefault) . fst <> drawMarkedLines . snd)

starbattle :: Backend' b =>
              Drawers b (AreaGrid, Int) (Grid C (Maybe Star))
starbattle = drawers
    (p <> n)
    ((p <> n) . fst <> placeGrid . fmap drawStar . clues . snd)
  where
    p = (drawAreas <> grid gDefault) . fst
    n (g, k) = placeNoteTR (size' g)
                         (drawInt k ||| strutX 0.2 ||| drawStar Star)
    size' = size . Map.mapKeys toCoord

heyawake :: Backend' b =>
            Drawers b (AreaGrid, Grid C (Maybe Int)) (Grid C Bool)
heyawake = drawers
    (as <> cs)
    (as . fst <> drawShade . snd <> cs . fst)
  where
    as = (drawAreas <> grid gDefault) . fst
    cs = placeGrid . fmap drawInt . clues . snd

wormhole :: Backend' b =>
            Drawers b (Grid C (Maybe (Either Int Char))) ()
wormhole = drawers
    (placeGrid . fmap (either drawInt drawChar) . clues <> grid gDashed)
    mempty

pentominous ::
    Backend' b =>
    Drawers b (Grid C (Maybe Char)) (Grid C Char)
pentominous = drawers
    (placeGrid . fmap drawChar . clues <> grid gDashed)
    (placeGrid . fmap drawChar . clues . fst <>
     (drawAreas <> grid gDashed) . snd)

colorakari ::
    Backend' b =>
    Drawers b (Grid C (Maybe Char)) (Grid C (Maybe Char))
colorakari = drawers
    (placeGrid . fmap drawColorClue . clues <> grid gDefault)
    (unimplemented "color akari solution")
  where
    drawColorClue 'X' = fillBG black
    drawColorClue c = case col c of Nothing -> error "invalid color"
                                    Just c' -> drawText [c] # scale 0.5
                                               <> circle (1/3) # fc c'
                                               <> fillBG black
    col c = case c of 'R' -> Just red
                      'G' -> Just green
                      'B' -> Just blue
                      'Y' -> Just yellow
                      'C' -> Just cyan
                      'M' -> Just magenta
                      'W' -> Just white
                      _   -> Nothing

persistenceOfMemory ::
    Backend' b =>
    Drawers b (AreaGrid, (Grid C (Maybe MEnd))) (Loop C)
persistenceOfMemory = drawers
    (ends_ <> areas)
    (ends_ . fst <> solstyle . drawEdges . snd <> areas . fst)
  where
    ends_ = placeGrid . fmap drawEnd . clues . snd
    areas = (drawAreas <> grid gDashed <> shadeGrid . fmap cols) . fst
    cols c | isUpper c  = Just (blend 0.25 black white)
           | otherwise  = Nothing

mappingTable :: Backend' b => [(String, String)] -> Diagram b
mappingTable = b . g
  where
    b = placeGrid . fmap drawText <> grid gPlain
    g ps = Map.fromList $
               [ (C 0 (l-i-1), x) | (i, x) <- zip [0..] c1 ] ++
               [ (C 1 (l-i-1), x) | (i, x) <- zip [0..] c2 ]
      where
        l = length ps
        c1 = map fst ps
        c2 = map snd ps

abctje ::
    Backend' b =>
    Drawers b (DigitRange, [(String, Int)]) [(Int, Char)]
abctje = drawers
    p
    ((mappingTable . h  ||| const (strutX 1.0) ||| mappingTable . h') . snd)
  where
    p (ds, cs) = (digNote ds `aboveT` (stackWordsLeft ws ||| strutX 1.0 ||| stackWordsRight ns))
                 `besidesR` (strutX 2.0 ||| mappingTable ps ||| strutX 1.0 ||| mappingTable ps')
      where
        ws = map fst cs
        ns = map (show . snd) cs
        ls = nub . sort . concatMap fst $ cs
        ps = [ (x:[], "") | x <- ls ]
        ps' = [ (show x, "") | x <- digitList ds ]
    digNote (DigitRange x y) = note . drawText $ show x ++ "-" ++ show y
    h = sortOn fst . map (\(x, y) -> (y:[], show x))
    h' = map (\(x, y) -> (show x, y:[]))

kropki ::
    Backend' b =>
    Drawers b (Map.Map (Edge N) KropkiDot) (Grid C Int)
kropki = drawers
    (p <> n)
    (placeGrid . fmap drawInt . snd <> p . fst)
  where
    p = placeGrid' . Map.mapKeys midPoint . fmap kropkiDot <> grid gDefault . sizeGrid . sz
    n g = placeNoteTR (w, h) (drawText ds # scale 0.8)
      where
        (w, h) = sz g
        ds = "1-" ++ show h
    sz m = edgeSize (Map.keys m)

statuepark ::
    Backend' b =>
    Drawers b (Grid C (Maybe MasyuPearl)) (Grid C Bool)
statuepark = drawers
    p
    (p . fst <> drawShade . snd)
  where
    p = placeGrid . fmap pearl . clues <> grid gDashed

pentominousBorders ::
    Backend' b =>
    Drawers b (Grid C (), [Edge N]) (Grid C Char)
pentominousBorders = drawers
    (drawEdges . snd <> grid gDashed . fst)
    ((drawAreas <> grid gDashed) . snd)

smallHintRooms ::
    Backend' b =>
    (AreaGrid, Grid C (Maybe Int)) -> Diagram b
smallHintRooms = ((drawAreas <> grid gDashed) . fst <> placeGrid . fmap hintTL . fmap show . clues . snd)

nanroSignpost ::
    Backend' b =>
    Drawers b (AreaGrid, Grid C (Maybe Int)) (Grid C Int)
nanroSignpost = drawers
    smallHintRooms
    (placeGrid . fmap drawInt . snd <> smallHintRooms . fst)

tomTom ::
    Backend' b =>
    Drawers b (AreaGrid, Grid C (Maybe String)) (Grid C Int)
tomTom = drawers
    p
    (placeGrid . fmap drawInt . snd <> p . fst)
  where
    p = ((drawAreas <> grid gDashed) . fst <> placeGrid . fmap hintTL . clues . snd)

horseSnake ::
    Backend' b =>
    Drawers b (Grid C (Maybe (Either MEnd Int))) [Edge C]
horseSnake = drawers
    p
    (solstyle . drawEdges . snd <> p . fst)
  where
    p = (placeGrid . fmap (either drawBigEnd drawInt) . clues <> grid gDashed)

illumination ::
    Backend' b =>
    Drawers b (OutsideClues C (Maybe Fraction)) (Grid N (Maybe PlainNode), [Edge N])
illumination = drawers
    p
    ((placeGrid . fmap (const (smallPearl MWhite)) . clues . fst <> drawEdges . snd) . snd <> p . fst)
  where
    p = placeGrid . fmap drawFraction . clues . outsideClues
        <> grid gDashed . outsideGrid

pentopia ::
    Backend' b =>
    Drawers b (Grid C (Maybe Myopia)) (Grid C Bool)
pentopia = drawers
    p
    (p . fst <> drawShade . snd)
  where
    p = placeGrid . fmap drawMyopia . clues <> grid gDefault

pentominoPipes ::
    Backend' b =>
    Drawers b (Grid N Char) (Grid N KropkiDot, [Edge N])
pentominoPipes = drawers
    (placeGrid . fmap drawCharOpaque <> grid gSlither . cellGrid)
    ((placeGrid . fmap kropkiDot . fst
      <> drawEdges . snd) . snd
     <> grid gSlither . cellGrid . fst)

greaterWall ::
    Backend' b =>
    Drawers b ([GreaterClue], [GreaterClue]) (Grid C Bool)
greaterWall = drawers
    ((plc <> grid gDefault . outsideGrid) . munge)
    undefined
  where
    munge (rs,cs) = OC (map (reverse . greaterClue) (reverse rs)) [] []
                       (map (map (rotateBy (-1/4))) . map (reverse . greaterClue) $ cs)
    plc ocs = placeGrid' . Map.mapKeys toPt . multiOutsideClues $ ocs
      where
        OC l _ _ _ = ocs
        h = length l
        h' = fromIntegral h
        -- toPoint c = p2 (1/2, 1/2) .+^ r2i (c .--. C 0 0)
        -- terrible hack
        toPt c@(C x y) | x < 0  = let p = toPoint c in scaleX 0.7 p .+^ r2 (-1/2, 0)
                       | y >= h = let p = toPoint c in scaleY 0.7 (p .-^ r2 (0,h')) .+^ r2 (0, 1/2 + h')
        toPt c = toPoint c

galaxies ::
    Backend' b =>
    Drawers b (Grid C (), Grid N (), Grid C (), Map.Map (Edge N) ()) AreaGrid
galaxies = drawers
    p
    (p . fst <> drawAreas . snd)
  where
    p = (gals <> grid gDashed . fst4)
    gal = const (kropkiDot KWhite)
    gals (_, a,b,c) = (placeGrid . fmap gal $ a)
                   <> (placeGrid . fmap gal $ b)
                   <> (placeGrid' . fmap gal . Map.mapKeys midPoint $ c)
    fst4 (a,_,_,_) = a

mines ::
    Backend' b =>
    Drawers b (Grid C (Maybe Int)) ShadedGrid
mines = drawers
    p
    (p . fst <> placeGrid . fmap (const (pearl MBlack)) . Map.filter id . snd)
  where
    p = grid gDefault <> placeGrid . fmap (\i -> drawInt i <> fillBG lightgray) . clues

tents ::
    Backend' b =>
    Drawers b (OutsideClues C (Maybe Int), Grid C (Maybe Tree)) (Grid C (Maybe PlacedTent))
tents = drawers
    p
    (p . fst <> placeGrid . fmap drawTent . clues . snd)
  where
    p = placeGrid . fmap drawInt . clues . outsideClues . fst
        <> placeGrid . fmap drawTree . clues . snd
        <> grid gDashed . snd

pentominoSums :: Backend' b => Drawers b (OutsideClues C [String], String)
                               (Grid C (Either Pentomino Int), [(Char, Int)], OutsideClues C [String])
pentominoSums = drawers
    p
    (solgrid ||| const (strutX 1.0) ||| table)
  where
    p (ocs, ds) =
        (((drawMultiOutsideGrid ocs <> n (ocs, ds)) ||| strutX 1.0 ||| emptyTable ocs)
        `aboveT` drawPentominos)
    n (ocs, ds) = placeNoteTL (0, h ocs) (drawText ds # scale 0.8)
    h = snd . outsideSize
    emptyTable = mappingTable . emptys
    emptys = map (\k -> (k, "")) . nub . sort . concat . outsideValues
    solgrid =
        skel . fst3 . snd
        <> drawMultiOutsideGrid . trd3 . snd
        <> cells . fst3 . snd
    fst3 (x,_,_) = x
    trd3 (_,_,z) = z
    skel = skeletonStyle . drawEdges . skeletons . lefts
    skeletonStyle = lc white . lwG (3 * onepix)
    lefts = clues . fmap (either Just (const Nothing))
    cells = placeGrid . fmap (\v -> case v of
        Left _  -> fillBG gray
        Right x -> drawInt x)
    table ((cs, _), (_, m, _)) = mappingTable m'
      where
        m' = Map.toList (Map.union (Map.fromList a) (Map.fromList (emptys cs)))
        a = map (\(k, v) -> ([k], show v)) m

coralLits ::
    Backend' b =>
    Drawers b (OutsideClues C [String]) (Grid C (Maybe Char))
coralLits = drawers
    (\ocs -> drawMultiOutsideGrid ocs `aboveT` drawLITS)
    (skeletonStyle . drawEdges . skeletons . clues . snd
     <> drawMultiOutsideGrid . fst
     <> placeGrid . fmap (const (fillBG gray)) . clues . snd)
  where
    skeletonStyle = lc white . lwG (3 * onepix)

coralLitso ::
    Backend' b =>
    Drawers b (OutsideClues C [String]) (Grid C (Either Black Char))
coralLitso = drawers
    (\ocs -> drawMultiOutsideGrid ocs `aboveT` drawLITSO)
    (drawMultiOutsideGrid . fst
     <> skeletonStyle . drawEdges . skeletons . rights . snd
     <> placeGrid . fmap (const (fillBG gray)) . lefts . snd)
  where
    skeletonStyle = solstyle
    lefts = clues . fmap (either Just (const Nothing))
    rights = clues . fmap (either (const Nothing) Just)

snake ::
    Backend' b =>
    Drawers b (OutsideClues C (Maybe Int), Grid C (Maybe MEnd))
                   (Grid C (Maybe (Either MEnd Black)))
snake = drawers p s
  where
    cs = placeGrid . fmap drawInt . clues . outsideClues . fst
    p = cs
        <> placeGrid . fmap drawBigEnd . clues . snd
        <> grid gDefault . snd
    s = cs . fst
        <> grid gDefault . snd
        <> placeGrid . fmap (either (drawBigEnd <> gr) gr) . clues . snd
    gr = const (fillBG gray)

countryRoad ::
    Backend' b =>
    Drawers b (AreaGrid, Grid C (Maybe Int)) (Loop C)
countryRoad = drawers
    smallHintRooms
    (solstyle . drawEdges . snd <> smallHintRooms . fst)

friendlysudoku ::
    Backend' b =>
    Drawers b (Map.Map (Edge N) KropkiDot, Grid C (Maybe Int)) (Grid C Int)
friendlysudoku = drawers
    p
    (placeGrid . fmap drawInt . snd <> p . fst)
  where
    p = placeGrid' . Map.mapKeys midPoint . fmap kropkiDot . fst
      <> placeGrid . fmap drawInt . clues . snd
      <> sudokugrid . snd

japsummasyu :: Backend' b =>
          Drawers b (OutsideClues C [String]) ()
japsummasyu = drawers
    (placeMultiOutside . fmap (fmap (scale 0.8 . drawText))
                     <> grid gDashDash . outsideGrid)
    (error "japsummasyu solution not implemented")
  where
    gDashDash = GridStyle LineDashed LineDashed Nothing VertexNone