-- | Translate concrete syntax to Haskell
module GF.Compile.ConcreteToHaskell(concretes2haskell,concrete2haskell) where

import PGF2(Literal(..))
import Data.List(isPrefixOf,sort,sortOn)
import qualified Data.Map as Map
import GF.Text.Pretty
import GF.Grammar.Predef
import GF.Grammar.Grammar
import GF.Grammar.Macros
import GF.Infra.Ident
import GF.Infra.Option
import GF.Haskell as H
import GF.Compile.GrammarToCanonical

-- | Generate Haskell code for the all concrete syntaxes associated with
-- the named abstract syntax in given the grammar.
concretes2haskell opts absname gr = do
  gr <- grammar2canonical opts absname gr
  let abstr:concrs = modules gr
  return [(filename,render80 $ concrete2haskell opts abstr concr)
             | concr@(MN mn,_) <- concrs,
               let filename = showIdent mn ++ ".hs" :: FilePath
         ]

-- | Generate Haskell code for the given concrete module.
-- The only options that make a difference are
-- @-haskell=noprefix@ and @-haskell=variants@.
concrete2haskell opts abstr@(absname,_) concr@(cncname,mi) =
  haskPreamble absname cncname $$
  vcat (
    nl:Comment "--- Parameter types ---":
    [paramDef id ps | (id,ResParam (Just (L _ ps)) _) <- Map.toList (jments mi)] ++
    nl:Comment "--- Type signatures for linearization functions ---":
    [signature id | (id,CncCat _ _ _ _ _) <- Map.toList (jments mi)] ++
    nl:Comment "--- Linearization types ---":
    [lincatDef id ty | (id,CncCat (Just (L _ ty)) _ _ _ _) <- Map.toList (jments mi)] ++
    nl:Comment "--- Linearization functions ---":
    concat (Map.elems lindefs) ++
    nl:Comment "--- Type classes for projection functions ---":
    -- map labelClass (S.toList labels) ++
    nl:Comment "--- Record types ---":
    [] -- concatMap recordType recs
    )
  where
    nl = Comment ""

    signature c = TypeSig lf (Fun abs (pure lin))
      where
        abs = tcon0 (prefixIdent "A." (gId c))
        lin = tcon0 lc
        lf = linfunName c
        lc = lincatName c

    gId :: Ident -> Ident
    gId = (if haskellOption opts HaskellNoPrefix then id else prefixIdent "G")

    va = haskellOption opts HaskellVariants
    pure = if va then ListT else id

    haskPreamble :: ModuleName -> ModuleName -> Doc
    haskPreamble absname cncname =
      "{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, LambdaCase #-}" $$
      "module" <+> cncname <+> "where" $$
      "import Prelude hiding (Ordering(..))" $$
      "import Control.Applicative((<$>),(<*>))" $$
      "import qualified" <+> absname <+> "as A" $$
      "" $$
      "-- | Token sequences, output form linearization functions" $$
      "type Str = [Tok] -- token sequence" $$
      "" $$
      "-- | Tokens" $$
      "data Tok = TK String | TP [([Prefix],Str)] Str | BIND | SOFT_BIND | SOFT_SPACE | CAPIT | ALL_CAPIT" $$
      "         deriving (Eq,Ord,Show)" $$
      "" $$
      "--- Standard definitions ---" $$
      "linString (A.GString s) ="<+>pure "R_s [TK s]" $$
      "linInt (A.GInt i) ="<+>pure "R_s [TK (show i)]" $$
      "linFloat (A.GFloat x) ="<+>pure "R_s [TK (show x)]" $$
      "" $$
      "----------------------------------------------------" $$
      "-- Automatic translation from GF to Haskell follows" $$
      "----------------------------------------------------"
      where
        pure = if va then brackets else pp

    paramDef id pvs = Data (conap0 (gId id)) (map paramCon pvs) derive
      where
        paramCon (id,ctxt) = ConAp (gId id) [tcon0 (gId cat) | (_,_,QC (_,cat)) <- ctxt]
        derive = ["Eq","Ord","Show"]

    convLinType (Sort s)
      | s == cStr = tcon0 (identS "Str")
    convLinType (QC (_,p)) = tcon0 (gId p)
    convLinType (RecType lbls) = tcon (rcon' ls) (map convLinType ts)
      where (ls,_,ts) = unzip3 $ sortOn (\(l,_,_)->l) lbls
    convLinType (Table pt lt) = Fun (convLinType pt) (convLinType lt)

    lincatDef c ty = tsyn0 (lincatName c) (convLinType ty)

    lindefs =
      Map.fromListWith (++) 
        [linDef id absctx cat lincat rhs |
            (id,CncFun (Just (absctx,cat,_,lincat)) (Just (L _ rhs)) _ _) <- Map.toList (jments mi)]

    linDef f absctx cat lincat rhs0 =
      (cat,[Eqn (linfunName cat,lhs) rhs'])
      where
        lhs = [ConP (aId f) (map VarP abs_args)]
        aId f = prefixIdent "A." (gId f)

        --[C.Type absctx (TypeApp cat _)] = [t | FunDef f' t<-funs, f'==f]
        (xs,rhs) = termFormCnc rhs0

        abs_args = map abs_arg args
        abs_arg = prefixIdent "abs_"
        args = map (prefixIdent "g" . snd) xs

        rhs' = lets (zipWith letlin args absctx)
                    (convert rhs)
          where
            vs = [(x,a)|((_,x),a)<-zip xs args]

        letlin a acat =
          (a,Ap (Var (linfunName acat)) (Var (abs_arg a)))

    convert (Vr v) = Var (gId v)
    convert (EInt n) = lit n
    convert (EFloat d) = lit d
    convert (K s) = single (Const "TK" `Ap` lit s)
    convert Empty = List []
    convert (App t1 t2) = Ap (convert t1) (convert t2)
    convert (R lbls) = aps (rcon ls) (map (convert.snd) ts)
                         where (ls,ts) = unzip (sortOn fst lbls)
    convert (P t lbl) = ap (proj lbl) (convert t)
    convert (ExtR t1 t2) = Const "ExtR" -- TODO
    convert (T _ cs) = LambdaCase (map ppCase cs)
      where
        ppCase (p,t) = (convertPatt p,convert t)
    convert (V _ ts) = Const "V" -- TODO
    convert (S t p)
      | va        = select_va (convert t) (convert p)
      | otherwise = Ap (convert t) (convert p)
      where
        select_va (List [t]) (List [p]) = Op t "!" p
        select_va (List [t]) p = Op t "!$" p
        select_va t p = Op t "!*" p
    convert (Q  (_,id)) = single (Var id)
    convert (QC (_,id)) = single (Var id)
    convert (C t1 t2)
      | va        = concat_va (convert t1) (convert t2)
      | otherwise = plusplus  (convert t1) (convert t2)
      where
        concat_va (List [List ts1]) (List [List ts2]) = List [List (ts1++ts2)]
        concat_va t1 t2 = Op t1 "+++" t2
    convert (Glue t1 t2) = Const "Glue"
    convert (FV ts)
      | va        = join (List (map convert ts))
      | otherwise = case ts of
                      []     -> Const "error" `Ap` Const (show "empty variant")
                      (t:ts) -> convert t
      where
        join (List [x]) = x
        join x = Const "concat" `Ap` x
    convert (Alts def alts) = single (Const "TP" `Ap` List (map convAlt alts) `Ap` convert def)
      where
        convAlt (t1,t2) = Pair (convert t1) (convert t2)
    convert (Strs ss) = List (map lit ss)
    convert t = error (show t)

    convertPatt (PC c     ps) = ConP (gId c) (map convertPatt ps)
    convertPatt (PP (_,c) ps) = ConP (gId c) (map convertPatt ps)
    convertPatt (PV v)
      | v == identW           = WildP
      | otherwise             = VarP v
    convertPatt (PR lbls)     = ConP (rcon' ls) (map convertPatt ps)
      where (ls,ps) = unzip $ sortOn fst lbls
    convertPatt (PString s)   = Lit s
    convertPatt (PT _ p)      = convertPatt p
    convertPatt (PAs v p)     = AsP v (convertPatt p)
    convertPatt (PImplArg p)  = convertPatt p
    convertPatt (PTilde _)    = WildP
    convertPatt (PAlt _ _)    = WildP -- TODO
    convertPatt p = error (show p)

    lit s = Const (show s) -- hmm

    ap = if va then ap' else Ap
       where
         ap' (List [f]) x = fmap f x
         ap' f x = Op f "<*>" x
         fmap f (List [x]) = Ap f x
         fmap f x = Op f "<$>" x

    aps f [] = f
    aps f (a:as) = aps (ap f a) as

proj = Var . identS . proj'
proj' (LIdent l) = "proj_" ++ showRawIdent l
rcon = Var . rcon'
rcon' = identS . rcon_name
rcon_name ls = "R"++concat (sort ['_':showRawIdent l | LIdent l <- ls])

lincatName,linfunName :: Ident -> Ident
lincatName c = prefixIdent "Lin" c
linfunName c = prefixIdent "lin" c
