module Ast where

import Data.Char (isDigit)
import Data.List (intercalate)
import Types
import Guid

data Module = Module [String] Exports Imports [Statement]

type Exports = [String]

type Imports = [(String, ImportMethod)]
data ImportMethod = As String | Hiding [String] | Importing [String]


data Pattern = PData String [Pattern] | PVar String | PAnything
               deriving (Eq)

data Expr = IntNum Int
          | FloatNum Float
          | Chr Char
          | Str String
          | Boolean Bool
          | Range Expr Expr
          | Access Expr String
          | Binop String Expr Expr
          | Lambda String Expr
          | App Expr Expr
          | If Expr Expr Expr
          | Lift Expr [Expr]
          | Fold Expr Expr Expr
          | Async Expr
          | Input String
          | Let [Definition] Expr
          | Var String
          | Case Expr [(Pattern,Expr)]
          | Data String [Expr]
            deriving (Eq)

data Definition = Definition String [String] Expr
                  deriving (Eq)

data Statement = Def String [String] Expr
               | Datatype String [X] [(String,[Type])]
               | ImportEvent String Expr String Type
               | ExportEvent String String Type
                 deriving (Eq,Show)

cons h t = Data "Cons" [h,t]
nil      = Data "Nil" []
list     = foldr cons nil
tuple es = Data ("Tuple" ++ show (length es)) es

delist (Data "Cons" [h,t]) = h : delist t
delist _ = []


pcons h t = PData "Cons" [h,t]
pnil      = PData "Nil" []
plist     = foldr pcons pnil
ptuple es = PData ("Tuple" ++ show (length es)) es

instance Show Pattern where
    show (PVar x)  = x
    show PAnything = "_"
    show (PData "Cons" [hd@(PData "Cons" _),tl]) =
        parens (show hd) ++ " : " ++ show tl
            where parens s = "(" ++ s ++ ")"
    show (PData "Cons" [hd,tl]) = show hd ++ " : " ++ show tl
    show (PData "Nil" []) = "[]"
    show (PData name ps) =
        if take 5 name == "Tuple" && all isDigit (drop 5 name) then
            parens . intercalate ", " $ map show ps
        else (if null ps then id else parens) $ unwords (name : map show ps)
            where parens s = "(" ++ s ++ ")"

instance Show Expr where
  show (IntNum n) = show n
  show (FloatNum n) = show n
  show (Chr c) = show c
  show (Str s) = show s
  show (Boolean b) = show b
  show (Range e1 e2) = "[" ++ show e1 ++ ".." ++ show e2 ++ "]"
  show (Access e x) = show' e ++ "." ++ x
  show (Binop op e1 e2) = show' e1 ++ " " ++ op ++ " " ++ show' e2
  show (Lambda x e) = let (xs,e') = getLambdas (Lambda x e) in
                      concat [ "\\", intercalate " " xs, " -> ", show e' ]
  show (App e1 e2) = show' e1 ++ " " ++ show' e2
  show (If e1 e2 e3) = concat [ "if ", show e1, " then ", show e2, " else ", show e3 ]
  show (Let defs e) = "let { " ++ intercalate " ; " (map show defs) ++ " } in " ++ show e
  show (Var x) = x
  show (Case e pats) = "case " ++ show e ++ " of { " ++ intercalate " ; " (map (\(p,e) -> show p ++ " -> " ++ show e) pats) ++ " }"
  show (Data name es)
      | name == "Cons" = ("["++) . (++"]") . intercalate "," . map show $ delist (Data "Cons" es)
      | name == "Nil" = "[]"
      | otherwise = name ++ " " ++ intercalate " " (map show' es)
  show (Lift f es) = concat [ "lift", show $ length es, " ", show' f, " ", intercalate " " (map show' es) ]
  show (Fold e1 e2 e3) = concat [ "foldp ", show' e1, " ", show' e2, " ", show' e3 ]
  show (Async e) = "async " ++ show' e
  show (Input i) = i


instance Show Definition where
  show (Definition v [] e) = v ++ " = " ++ show e
  show (Definition f args e) = f ++ " " ++ intercalate " " args ++ " = " ++ show e

getLambdas (Lambda x e) = (x:xs,e')
    where (xs,e') = getLambdas e
getLambdas e = ([],e)

show' e = if needsParens e then "(" ++ show e ++ ")" else show e

needsParens (Binop _ _ _) = True
needsParens (Lambda _ _) = True
needsParens (App _ _) = True
needsParens (If _ _ _) = True
needsParens (Let _ _) = True
needsParens (Case _ _) = True
needsParens (Data name (x:xs)) = name /= "Cons"
needsParens (Lift _ _) = True
needsParens (Fold _ _ _) = True
needsParens (Async _) = True
needsParens _ = False