module Nix.Parser.Operators where

import           Data.Data (Data(..))
import           Data.Foldable (concat)
import qualified Data.Map as Map
import           Data.Maybe (catMaybes)
import           Data.Typeable (Typeable)
import           GHC.Exts
import           GHC.Generics
import           Nix.Expr
import           Prelude hiding (readFile, concat, concatMap, elem, mapM,
                                 sequence, minimum, foldr)

data NSpecialOp = NHasAttrOp | NSelectOp | NAppOp
  deriving (Eq, Ord, Generic, Typeable, Data, Show)

data NAssoc = NAssocNone | NAssocLeft | NAssocRight
  deriving (Eq, Ord, Generic, Typeable, Data, Show)

data NOperatorDef
  = NUnaryDef String NUnaryOp
  | NBinaryDef NAssoc [(String, NBinaryOp)]
  deriving (Eq, Ord, Generic, Typeable, Data, Show)

nixOperators :: [Either NSpecialOp NOperatorDef]
nixOperators =
  [ Left NSelectOp
  , Left NAppOp
  , Right $ NUnaryDef "-" NNeg
  , Left NHasAttrOp
  ] ++ map Right
  [ NBinaryDef NAssocRight [("++", NConcat)]
  , NBinaryDef NAssocLeft [("*", NMult), ("/", NDiv)]
  , NBinaryDef NAssocLeft [("+", NPlus), ("-", NMinus)]
  , NUnaryDef  "!"  NNot
  , NBinaryDef NAssocRight [("//", NUpdate)]
  , NBinaryDef NAssocLeft [("<", NLt), (">", NGt), ("<=", NLte), (">=", NGte)]
  , NBinaryDef NAssocNone [("==", NEq), ("!=", NNEq)]
  , NBinaryDef NAssocLeft [("&&", NAnd)]
  , NBinaryDef NAssocLeft [("||", NOr)]
  , NBinaryDef NAssocNone [("->", NImpl)]
  ]

data OperatorInfo = OperatorInfo
  { precedence    :: Int
  , associativity :: NAssoc
  , operatorName  :: String
  } deriving (Eq, Ord, Generic, Typeable, Data, Show)

getUnaryOperator :: NUnaryOp -> OperatorInfo
getUnaryOperator = (m Map.!) where
  m = Map.fromList . concat . zipWith buildEntry [1..] . reverse $
        nixOperators
  buildEntry i = \case
    Right (NUnaryDef name op) -> [(op, OperatorInfo i NAssocNone name)]
    _ -> []

getBinaryOperator :: NBinaryOp -> OperatorInfo
getBinaryOperator = (m Map.!) where
  m = Map.fromList . concat . zipWith buildEntry [1..] . reverse $
        nixOperators
  buildEntry i = \case
    Right (NBinaryDef assoc ops) -> do
      [(op, OperatorInfo i assoc name) | (name,op) <- ops]
    _ -> []

getSpecialOperatorPrec :: NSpecialOp -> Int
getSpecialOperatorPrec = (m Map.!) where
  m = Map.fromList . catMaybes . zipWith buildEntry [1..] . reverse $
        nixOperators
  buildEntry _ (Right _) = Nothing
  buildEntry i (Left op) = Just (op, i)

selectOp :: OperatorInfo
selectOp = OperatorInfo (getSpecialOperatorPrec NSelectOp) NAssocLeft "."

hasAttrOp :: OperatorInfo
hasAttrOp = OperatorInfo (getSpecialOperatorPrec NHasAttrOp) NAssocLeft "?"

appOp :: OperatorInfo
appOp = OperatorInfo (getSpecialOperatorPrec NAppOp) NAssocLeft " "