{- |
Module      : Data.Matrix.AsXYZ.ParseXY
Copyright   : (c) Jun Narumi 2020
License     : BSD3
Maintainer  : narumij@gmail.com
Stability   : experimental
Portability : ?

Jones-Faithful notation parser for planegroup.

-}
module Data.Matrix.AsXYZ.ParseXY (
  equivalentPositions,
  transformPpAB,
  transformQqXY,
  ) where

import Control.Monad
import Data.Char
import Data.Maybe
import Data.List
import Text.ParserCombinators.Parsec
import Data.Matrix.AsXYZ.ParseXYZ (ReadNum(..),overlap,sign,elementBody,minus,ratio,integral,floating)
import Data.Matrix.AsXYZ.Common (Var(..))

xy :: CharParser () Char
xy = oneOf "xyXY"

ab :: CharParser () Char
ab = oneOf "abAB"

-- | General equivalent positions parser
--
-- >>> parse (equivalentPositions integral) "" "x+1,y+2"
-- Right [[1,0,1],[0,1,2]]
equivalentPositions :: Num a =>
              ReadNum a -- ^ use converter below
             -> CharParser () [[a]]
equivalentPositions = components xy

-- | Same as equivalentPositions but uses abc instead of xyz
--
-- >>> parse (transformPpAB integral) "" "a+1,b+2"
-- Right [[1,0,1],[0,1,2]]
transformPpAB :: Num a => ReadNum a -> CharParser () [[a]]
transformPpAB = components ab

-- | Alias of equivalentPositions
--
-- >>> parse (transformQqXY integral) "" "x+1,y+2"
-- Right [[1,0,1],[0,1,2]]
transformQqXY :: Num a => ReadNum a -> CharParser () [[a]]
transformQqXY = components xy

v c = f $ toLower <$> c
  where
    f (Just 'x') = X
    f (Just 'a') = X
    f (Just 'y') = Y
    f (Just 'b') = Y
    f Nothing = Z

one :: Num a => CharParser () Char -> ReadNum a -> CharParser () (Var a)
one var numRead = do
  s <- optionMaybe sign
  option () spaces
  (n,l) <- elementBody var numRead
  return $ v l . minus s . fromMaybe 1 $ n

other :: Num a => CharParser () Char -> ReadNum a -> CharParser () (Var a)
other var numRead = do
  s <- sign
  option () spaces
  (n,l) <- elementBody var numRead
  return $ v l . minus (Just s) . fromMaybe 1 $ n

constructRow :: Num a => [Var a] -> [a]
constructRow = map (fromMaybe 0 . listToMaybe . catMaybes) . transpose . map toArray
  where
    toArray (X n) = [Just n,Nothing,Nothing]
    toArray (Y n) = [Nothing,Just n,Nothing]
    toArray (Z n) = [Nothing,Nothing,Just n]

component :: Num b => CharParser () Char -> ReadNum b -> CharParser () [b]
component var numRead = do
  option () spaces
  x <- one var numRead
  xs <- many (other var numRead)
  option () spaces
  let mm = x : xs
  if overlap (map void mm)
    then
      fail "overlaps var type"
    else
      return (constructRow mm)

components :: Num a => CharParser () Char -> ReadNum a -> CharParser () [[a]]
components var conv = do
  a <- component var conv
  char ','
  b <- component var conv
  return [a,b]