{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
module Axiomatize where

import Language.Haskell.TH
import Language.Haskell.TH.Quote

import Debug.Trace

import Control.Applicative
import Data.List ((\\))
import Data.Maybe (fromMaybe)

data Proof = Proof

axiomatize :: Q [Dec] -> Q [Dec]
axiomatize q = do d <- q
                  let vts = [(x, t) | FunD x _ <- d, SigD y t <- d, x == y ]
                  ds <- mapM (axiomatizeOne vts) d
                  return $ trace (show d) $ concat ds

axiomatizeOne :: [(Name, Type)] -> Dec -> Q [Dec]
axiomatizeOne env f@(FunD name cs)
  = do axioms <- makeAxioms (lookup name env) name cs
       return $ f:axioms
axiomatizeOne _ (SigD _ _)
  = return []
axiomatizeOne _ d
  = error $ "axiomatizeOne: Cannot axiomatize" ++ show d

makeAxioms :: Maybe Type -> Name -> [Clause] -> Q [Dec]
makeAxioms t f cs = concat <$> mapM go cs
  where
    go :: Clause -> Q [Dec]
    go (Clause ps (NormalB (CaseE e ms)) []) = mapM (makeAxiomMatch f ps e) ms
    go (Clause ps (NormalB _) [])            = makeAxiomPattern t f ps
    go d = error $ "makeAxioms: Cannot axiomatize\n" ++ show d



makeAxiomPattern :: Maybe Type -> Name -> [Pat] -> Q [Dec]
makeAxiomPattern t g ps
  = do ifs <- mapM reify (fst <$> ds)
       ff <- makeFun f xs <$> axiom_body
       ft <- makeSigT t f ps
       return $ [ff] ++ ft
  where
    f = mkName $ makeNamePattern g (fst <$> ds)
    ds = [(n, dps) |  ConP n dps <- ps]
    xs = [x | VarP x <- (ps ++ concat (snd <$> ds))]

makeSigT Nothing _ _
  = return []
makeSigT (Just t) f ps
  = do r <- [t|Proof|]
       ifs <- mapM reify (fst . snd <$> ds)
       let ts2 = concat $ zipWith makePTys ds ifs
       return $ [SigD f $ mkUnivArrow (as, ts1 ++ ts2, r)]
  where
    (as, ts, _) = bkUnivFun t
    ts1 = [t | (t, VarP _) <- zip ts ps]
    ds  = [(t, (n, dps)) |  (t, ConP n dps) <- zip ts ps]

makePTys :: (Type, (Name, [Pat])) -> Info -> [Type]
makePTys (tr, (n, dps)) (DataConI m t _ _) | n == m
  = (applySub θ <$> [t | (t, VarP _) <- zip ts dps])
  where (as, ts, r) = bkUnivFun t
        θ = unify r tr
makePTys _ _ = error "makePTys: on invalid arguments"


unify (VarT n) t = [(n,t)]
unify t (VarT n) = [(n,t)]
unify (AppT t1 t2) (AppT t1' t2') = unify t1 t1' ++ unify t2 t2'
unify (ForallT _ _ t1) t2 = unify t1 t2
unify t1 (ForallT _ _ t2) = unify t1 t2
unify _ _  = []

applySub :: [(Name, Type)] -> Type -> Type
applySub θ t@(VarT v) = fromMaybe t (lookup v θ)
applySub θ (AppT t1 t2) = AppT (applySub θ t1) (applySub θ t2)
applySub θ (ForallT _ _ _) = error "applySub: TODO"
applySub θ t = t


bkUnivFun = go [] []
  where
    go as xs (ForallT vs _ t)   = go (as ++ vs) xs t
    go as xs (AppT (AppT ArrowT tx) t) = go as (tx:xs) t
    go as xs t                  = (as, reverse xs, t)

mkUnivArrow (as, ts, r) = ForallT as [] $ mkArrow ts r
  where
    mkArrow []     r = r
    mkArrow (t:ts) r = AppT (AppT ArrowT t) $ mkArrow ts r

makeAxiomMatch :: Name -> [Pat] -> Exp -> Match -> Q Dec
makeAxiomMatch g ps (VarE x) (Match (ConP dc dps) bd decs)
  = makeFun f xs <$> axiom_body
  where f  = mkName $ makeName g x dc
        xs = [p | VarP p <- ps ++ dps] \\ [x]

makeFun :: Name -> [Name] -> Exp -> Dec
makeFun f xs bd = FunD f [Clause (VarP <$> xs) (NormalB bd) []]

axiom_body :: Q Exp
axiom_body = [|Proof|]

sep = "_"
mkSep :: [String] -> String
mkSep []  = []
mkSep [x] = x
mkSep (x:xs) = x ++ sep ++ mkSep xs

eq  = "is"
makeName fname x dc
  = mkSep ["axiom", nameBase fname, nameBase x, eq, nameBase dc]

makeNamePattern fname dcs = mkSep $ ["axiom", nameBase fname] ++ (nameBase <$> dcs)
