{-| GCode types

This module exports types for constructing 'Code' values

-}

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Data.GCode.Types (
      Class(..)
    , AxisDesignator(..)
    , ParamDesignator(..)
    , allClasses
    , allAxisDesignators
    , zeroAxes
    , allParamDesignators
    , asChars
    , Axes
    , Params
    , Limits
    , ParamLimits
    , Code(..)
    , GCode
    , toCodeClass
    , toAxis
    , toParam
    , (&)
    , cls
    , axis
    , param
    , num
    , sub
    , axes
    , params
    , comment
    , emptyCode
    , defaultPrec
    , Style(..)
    , defaultStyle
    ) where


import Data.ByteString (ByteString)
import Data.Map (Map)

import qualified Data.Char
import qualified Data.Map

-- | Code class
data Class =
    G           -- ^ G-code
  | M           -- ^ M-code
  | T           -- ^ T-code (select tool)
  | PStandalone -- ^ Stand-alone P-code
  | FStandalone -- ^ Stand-alone F-code
  | SStandalone -- ^ Stand-alone S-code
  deriving (Show, Enum, Eq, Ord)

allClasses :: [Class]
allClasses = [G, M, T, PStandalone, FStandalone, SStandalone]

-- | Axis letter
data AxisDesignator =
    X -- ^ X-axis
  | Y -- ^ Y-axis
  | Z -- ^ Z-axis
  | A -- ^ A-axis
  | B -- ^ B-axis
  | C -- ^ C-axis
  | U -- ^ U-axis
  | V -- ^ V-axis
  | W -- ^ W-axis
  | E -- ^ Extruder axis
  | L
  deriving (Show, Enum, Eq, Ord)

allAxisDesignators :: [AxisDesignator]
allAxisDesignators = [X, Y, Z, A, B, C, U, V, W, E, L]

-- | Return `Axes` with each known at zero position
zeroAxes :: Axes
zeroAxes = Data.Map.fromList $ map (\a -> (a, 0)) allAxisDesignators

-- | Param letter
data ParamDesignator =
    S -- ^ S parameter - usually spindle RPM
  | P -- ^ P parameter
  | F -- ^ F parameter - usually feedrate
  | H -- ^ H paramater - used by tool length offset
  | R -- ^ R parameter
  | I -- ^ X offset for arcs
  | J -- ^ Y offset for arcs
  | K -- ^ Z offset for arcs
  deriving (Show, Enum, Eq, Ord)

allParamDesignators :: [ParamDesignator]
allParamDesignators = [S, P, F, R, I, J, K]

asChars :: Show a => [a] -> [Char]
asChars types = map ((!! 0) . show) types

fromChar :: Show a => Char -> [a] -> Maybe a
fromChar c types = Data.Map.lookup (Data.Char.toUpper c)
  $ Data.Map.fromList (zip (asChars types) types)

-- |Convert 'Char' representation of a code to its 'Class'
toCodeClass :: Char -> Maybe Class
toCodeClass c = fromChar c allClasses

-- |Convert 'Char' representation of an axis to its 'AxisDesignator'
toAxis :: Char -> Maybe AxisDesignator
toAxis c = fromChar c allAxisDesignators

-- |Convert 'Char' representation of a param to its 'ParamDesignator'
toParam :: Char -> Maybe ParamDesignator
toParam c = fromChar c allParamDesignators

-- | Map of 'AxisDesignator' to 'Double'
type Axes = Map AxisDesignator Double

-- | Map of 'AxisDesignator' to pair of 'Double's indicating lower and upper limits of travel
type Limits = Map AxisDesignator (Double, Double)

-- | Map of 'ParamDesignator' to 'Double'
type Params = Map ParamDesignator Double

-- | Map of 'ParamDesignator' to pair of 'Double's indicating lower and upper limits of this parameter
type ParamLimits = Map ParamDesignator (Double, Double)

-- | List of 'Code's
type GCode = [Code]

data Code =
    Code {
        codeCls :: Maybe Class      -- ^ Code 'Class' (M in M5)
      , codeNum :: Maybe Int        -- ^ Code value (81 in G81)
      , codeSub :: Maybe Int        -- ^ Code subcode (1 in G92.1)
      , codeAxes :: Axes            -- ^ Code 'Axes'
      , codeParams :: Params        -- ^ Code 'Params'
      , codeComment :: ByteString   -- ^ Comment following this Code
    }
  | Comment ByteString              -- ^ Standalone comment
  | Empty                           -- ^ Empty lines
  | Other ByteString                -- ^ Parser unhandled lines
  deriving (Show, Eq, Ord)


-- endofunctors for manipulating `Code`
cls :: Class -> Code -> Code
cls x c = c { codeCls = Just x}

num :: Int -> Code -> Code
num x c = c { codeNum = Just x}

sub :: Int -> Code -> Code
sub x c = c { codeSub = Just x}

axes :: Axes -> Code -> Code
axes x c = c { codeAxes = x}

axis :: AxisDesignator -> Double -> Code -> Code
axis des val c = c { codeAxes = Data.Map.insert des val $ codeAxes c }

params :: Params -> Code -> Code
params x c = c { codeParams = x}

param :: ParamDesignator -> Double -> Code -> Code
param des val c = c { codeParams = Data.Map.insert des val $ codeParams c }

comment :: ByteString -> Code -> Code
comment x c = c { codeComment = x}

-- code & num 10 & comment "& example"
(&) :: a -> (a -> c) -> c
(&) = flip ($)

emptyCode :: Code
emptyCode = Code {
    codeCls     = Nothing
  , codeNum     = Nothing
  , codeSub     = Nothing
  , codeAxes    = mempty
  , codeParams  = mempty
  , codeComment = mempty
  }


data Style =
  Style {
      stylePrecision :: Int
    , styleColorful :: Bool
  } deriving (Show)

defaultPrec :: Int
defaultPrec = 6

defaultStyle :: Style
defaultStyle = Style defaultPrec False