-- |This module contains 'toIsabelle' definitions of all the core Zeno types.
module Zeno.Isabellable.Core ()
where

import Prelude ()
import Zeno.Prelude
import Zeno.Core
import Zeno.Isabellable.Class

import qualified Data.Map as Map
import qualified Data.Text as Text

instance Isabellable Id where
  toIsabelle = fromString . ("z_" ++) . show

instance Isabellable ZBindings where
  toIsabelle binds =
    "\n\nfun " ++ decls_s ++ "\nwhere\n  " ++ exprs_s
    where
    flat = flattenBindings binds

    exprs_s = intercalate "\n| " $ do
      (fun, expr) <- flat
      let (vars, expr') = flattenLambdas expr
      return $
        "\"" ++ (toIsabelle . unflattenApp . map Var) (fun : vars)
        ++ " = " ++ (runIndented . indent . toIsabelleI) expr' ++ "\""

    decls_s = intercalate "\nand " $ do
      (var, _) <- flat
      let var_name = toIsabelle var
          type_s = " :: " ++ (quote . toIsabelle . getType) var
      return $ if (isOperator . Text.unpack) var_name
        then (toIsabelle . varId) var ++ type_s ++ " (infix \"" ++ var_name ++ "\" 42)"
        else var_name ++ type_s

instance Isabellable ZVar where
  toIsabelle var =
    case varName var of
      Nothing -> toIsabelle (varId var)
      Just name -> (fromString . convertName) name
    where
    convertName :: String -> String
    convertName [':'] = "#"
    convertName ('$':'c':xs) = convertName xs
    convertName ('[':']':[]) = "[]"
    convertName name' = id
      . prefixOperator
      . replace '/' '!'
      $ name
      where
      name = convert name'
      prefixOperator s
        | isOperator s = "Z" ++ s
        | otherwise = s

instance Isabellable ZEquality where
  toIsabelle (x :=: y) =
    case toIsabelle y of
      "True" -> toIsabelle x
      "False" -> "~(" ++ toIsabelle x ++ ")"
      y' -> toIsabelle x ++ " = " ++ y'

instance Isabellable ZClause where
  toIsabelle (Clause goal conds)
    | null conds = toIsabelle goal
    | otherwise = conds' ++ " ==> " ++ toIsabelle goal
      where
      conds' | length conds == 1 = toIsabelle (head conds)
             | otherwise = "[| " ++
                 intercalate "; " (map toIsabelle conds)
                 ++ " |]"

instance Isabellable ZQuantified where
  toIsabelle (Quantified vars obj)
    | null vars = toIsabelle obj
    | otherwise =
       "!! " ++ concatMap isaTyped vars
        ++ ". " ++ toIsabelle obj
      where
      isaTyped var = "(" ++ toIsabelle var ++
        " :: " ++ toIsabelle (getType var) ++ ") "

instance Isabellable ZHypothesis where
  toIsabelle = toIsabelle . hypClause

instance Isabellable ZExpr where
  toIsabelleI Err = error $
    "Cannot yet have _|_ in our Isabelle/HOL output; " ++
    "all your functions must be total. "
  toIsabelleI (Var var) = do
    var' <- toIsabelleI var
    if (isOperator . Text.unpack) var' && var' /= "[]"
      then toIsabelleI (varId var)
      else return var'
  toIsabelleI (flattenApp -> Var fun : args)
    | (show fun == "(,)" && length args == 2)
      || (show fun == "(,,)" && length args == 3)
      || (show fun == "(,,,)" && length args == 4) = do
         args' <- mapM toIsabelleI args
         return $
           "(" ++ intercalate ", " args' ++ ")"
  toIsabelleI (App (App (Var fun) arg1) arg2)
    | (isOperator . Text.unpack . toIsabelle) fun = do
      arg1' <- toIsabelleI arg1
      arg2' <- toIsabelleI arg2
      let arg1_s = if isTerm arg1 then arg1' else "(" ++ arg1' ++ ")"
          arg2_s = if isTerm arg2 then arg2' else "(" ++ arg2' ++ ")"
          fun_s = toIsabelle fun
      if fun_s == "#" && arg2_s == "[]"
        then return $ "[" ++ arg1_s ++ "]"
        else return $ "(" ++ arg1_s ++ " " ++ fun_s ++ " " ++ arg2_s ++ ")"
  toIsabelleI (App x y) = do
    x' <- toIsabelleI x
    y' <- toIsabelleI y
    let y'' = if isApp y && Text.head y' /= '(' then "(" ++ y' ++ ")" else y'
    return $ x' ++ " " ++ y''
  toIsabelleI (Lam var rhs) = do
    rhs' <- toIsabelleI rhs
    return $ "(%" ++ toIsabelle var ++ ". " ++ rhs' ++ ")"
  toIsabelleI expr@(Let binds rhs) = do
    case flattenBindings binds of
      _ : _ : _ -> error $
        "Cannot have mutually recursive let bindings within an Isabelle/HOL expression."
        ++ "\nThese can crop up with pre-defined type-class functions and I haven't "
        ++ "created a fix yet."
      [(var, expr)] -> do
        i <- indentation
        expr' <- toIsabelleI expr
        rhs' <- toIsabelleI rhs
        return $
          i ++ "(let " ++ toIsabelle var ++ " = " ++ expr'
          ++ i ++ "in " ++ rhs' ++ ")"
  toIsabelleI (Cse _ expr alts) = indent $ do
    expr' <- toIsabelleI expr
    alts' <- mapM toIsabelleI alts
    i <- indentation
    let alts_s = intercalate (i ++ "| ") (reverse alts')
    return $
      i ++ "(case " ++ expr' ++ " of"
      ++ i ++ "  " ++ alts_s ++ ")"

instance Isabellable ZAlt where
  toIsabelleI (Alt con vars rhs) = do
    rhs' <- indent (toIsabelleI rhs)
    return $ lhs ++ " => " ++ rhs'
    where
    lhs = case con of
      AltCon var -> toIsabelle $ unflattenApp (map Var (var : vars))
      AltDefault -> "_"

instance Isabellable ZDataType where
  toIsabelle (DataType _ name args cons) =
    "\n\ndatatype" ++ fromString args_s ++ " " ++ fromString (convert name)
      ++ "\n  = " ++ con_decl
    where
    args' = map (("'" ++) . show) (reverse args)
    args_s = concatMap (" " ++) args'

    con_decl = intercalate "\n  | " con_decls

    con_decls = do
      con <- cons
      let con_name = toIsabelle con
          con_type = snd (flattenForAllType (getType con))
          arg_types = butlast (flattenFunType con_type)
          arg_list = concatMap ((" " ++) . quote . toIsabelle) arg_types
      return $
        if (isOperator . Text.unpack) con_name
          then toIsabelle (varId con) ++ arg_list
            ++ " (infix \"" ++ con_name ++ "\" 42)"
          else con_name ++ arg_list

instance Isabellable ZType where
  toIsabelle (VarType var) = (fromString . convert . show) var
  toIsabelle (PolyVarType id) = fromString $ "'" ++ show id
  toIsabelle (ForAllType _ rhs) = toIsabelle rhs
  toIsabelle (flattenAppType -> VarType fun : args)
    | (show fun == "(,)" && length args == 2)
      || (show fun == "(,,)" && length args == 3)
      || (show fun == "(,,,)" && length args == 4) =
         (intercalate " * " . map toIsabelle) args
  toIsabelle (AppType fun arg) = arg' ++ " " ++ fun'
    where
    arg' = if isFunType arg || isAppType arg
      then "(" ++ toIsabelle arg ++ ")"
      else toIsabelle arg

    fun' = if isFunType fun
      then "(" ++ toIsabelle fun ++ ")"
      else toIsabelle fun

  toIsabelle (FunType arg res) = arg' ++ " => " ++ toIsabelle res
    where
    arg' = if isFunType arg
      then "(" ++ toIsabelle arg ++ ")"
      else toIsabelle arg
