{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
module Diagrams.TwoD.Path.Metafont.Parser
       (metafontParser) where

import Control.Lens ((^.))

import Text.Parsec
import Text.Parsec.Text

import Diagrams.Prelude hiding (option)
import Diagrams.TwoD.Path.Metafont.Types

num :: Parser Double
num = read <$> float where
  sign = plus <|> minus <|> unsigned
  plus = char '+' *> unsigned
  minus = (:) <$> char '-' <*> unsigned
  unsigned = many1 digit
  decimal = option "" (try $ (:) <$> char '.' <*> unsigned)
  float = (++) <$> sign <*> decimal

-- points

xy :: Parser (Double, Double)
xy = do
  spaces
  x <- num
  spaces *> char ',' *> spaces
  y <- num
  spaces
  return (x,y)

pt :: Parser P2
pt = char '(' *> (p2 <$> xy) <* char ')'

-- Joins

join :: Parser (PathJoin (Maybe PathDir) BasicJoin)
join = straightJoin <|> do
  d1' <- optionMaybe pathDir
  j' <- tenseLine <|> dotsJoin
  d2' <- optionMaybe pathDir
  return $ PJ d1' j' d2'

dotsJoin :: Parser BasicJoin
dotsJoin = string ".." *> spaces *>
           (boundedJoin <|>tensionJoin <|> controlJoin <|> plainJoin)

plainJoin :: Parser BasicJoin
plainJoin = pure (Left $ TJ t1' t1') where
  t1' = TensionAmt 1

tensionJoin :: Parser BasicJoin
tensionJoin = do
  string "tension"
  spaces
  t1' <- num
  t2' <- try (spaces *> string "and" *> spaces *> num <* spaces) <|> pure t1'
  string ".."
  return . Left $ TJ (TensionAmt t1') (TensionAmt t2')

controlJoin :: Parser BasicJoin
controlJoin = do
  string "controls" *> spaces
  z1 <- pt
  spaces *> string "and" <* spaces
  z2 <- pt
  spaces *> string ".."
  return . Right $ CJ z1 z2

boundedJoin :: Parser BasicJoin
boundedJoin = char '.' *> pure (Left $ TJ t t) where t = TensionAtLeast 1

straightJoin :: Parser (PathJoin (Maybe PathDir) BasicJoin)
straightJoin = try (string "--" *> notFollowedBy (char '-')) *> pure (PJ c jj c)
  where
    c = Just $ PathDirCurl 1
    jj = Left $ TJ (TensionAmt 1) (TensionAmt 1)

tenseLine :: Parser BasicJoin
-- 4096 is the constant 'infinity' in Plain MetaFont
tenseLine = string "---" *> pure (Left $ TJ t t) where t = TensionAmt 4096

-- Directions

dir :: Parser PathDir
dir = PathDirDir . r2 <$> xy

curl :: Parser PathDir
curl = PathDirCurl <$> (string "curl" *> spaces *> num)

pathDir :: Parser PathDir
pathDir = do
  char '{' *> spaces
  d <- curl <|> dir
  spaces *> char '}'
  return d

-- Segments & Paths

mfs :: Parser (MetafontSegment (Maybe PathDir) BasicJoin)
mfs = MFS <$> pt <*> join <*> lookAhead pt

matches :: Stream s m t => ParsecT s u m a -> ParsecT s u m Bool
matches p = option False (p *> return True)

-- | Parse a 'Text' value in Metafont syntax, as destribed in /The
-- METAFONTbook/.
metafontParser :: Parser (MFPath (Maybe PathDir) BasicJoin)
metafontParser = do
  ss <- many1 (try mfs)
  lastP <- pt
  lastD <- optionMaybe pathDir
  c  <- matches $ string "..cycle"
  if c
     then return . MFP c $ ss ++ [MFS lastP (PJ lastD (Left $ TJ (TensionAmt 1) (TensionAmt 1)) Nothing) (head ss^.x1)]
    else return $ MFP c ss