{-# OPTIONS --without-K --safe #-}

module Fragment.Tactic.Fragment where

open import Reflection hiding (name; Type; _≟_)
open import Reflection.Term using (_≟_)

open import Level using (_⊔_)
open import Relation.Binary using (Setoid)

open import Data.Bool using (Bool; _∨_; true; false; if_then_else_)
open import Data.Nat using ()
open import Data.List using (List; []; _∷_; map; sum; length)
open import Data.Vec using (fromList)
open import Data.Product using (_×_; _,_)
open import Data.Unit using (; tt)
open import Data.String using (String; _++_)
open import Data.Maybe using (Maybe; just; nothing)

open import Fragment.Tactic.Utils

open import Fragment.Equational.Theory
open import Fragment.Equational.Model
open import Fragment.Equational.FreeExtension hiding (reduce)
open import Fragment.Algebra.Signature
import Fragment.Algebra.Free as Free

read-goal : List (Arg Term)  TC (Term × List (Arg Term))
read-goal (vArg x  xs) = return (x , xs)
read-goal (_  xs)      = read-goal xs
read-goal []            = typeError (strErr "failed to read goal"  [])

read-goals : List (Arg Term)  TC (Term × Term)
read-goals xs = do (fst , xs)  read-goal xs
                   (snd , _)  read-goal xs
                   return (fst , snd)

extract-goals : Term  TC (Term × Term)
extract-goals (var _ args)  = read-goals args
extract-goals (con _ args)  = read-goals args
extract-goals (def _ args)  = read-goals args
extract-goals (meta _ args) = read-goals args
extract-goals t             =
  typeError (strErr "can't read goals from"  termErr t  [])

record Operator : Set where
  constructor operator
  field
    name       : Name
    normalised : Term
    arity      : 

open Operator

record CoreCtx : Set where
  field
    signature : Term
    theory    : Term
    model     : Term
    carrier   : Term
    frex      : Term

record GlobalCtx : Set where
  field
    core      : CoreCtx
    operators : List Operator

    telescope : List (Arg String)
    lhs       : Term
    rhs       : Term

  open CoreCtx core public

pattern modelType Θ a  = def (quote Model) (vArg Θ  hArg a  hArg   [])

ctx : Name  Term  Term  TC GlobalCtx
ctx frex model goal
  = do τ  inferType model
       γ  inferType goal

       signature  extract-Σ τ
       theory  extract-Θ τ
       carrier  normalise (def (quote ∥_∥) (vra theory  vra model  []))

       let core = record { signature = signature
                         ; theory    = theory
                         ; model     = model
                         ; carrier   = carrier
                         ; frex      = def frex []
                         }

       operators  extract-ops core

       (inner , telescope)  strip-telescope γ
       (lhs , rhs)  extract-goals inner

       return (record { core      = core
                      ; operators = operators
                      ; telescope = telescope
                      ; lhs       = lhs
                      ; rhs       = rhs
                      })

  where modelErr :  {a} {A : Set a}  Term  String  TC A
        modelErr t err =
          typeError ( termErr t
                     strErr "isn't a"
                     nameErr (quote Model)
                     strErr ("(" ++ err ++ ")")
                     []
                    )

        extract-Σ : Term  TC Term
        extract-Σ (modelType Θ _ _) = normalise (def (quote Theory.Σ) (vra Θ  []))
        extract-Σ t                 = modelErr t "when extracting the signature"

        extract-Θ : Term  TC Term
        extract-Θ (modelType Θ _ _) = normalise Θ
        extract-Θ t                 = modelErr t "when extracting the theory"

        extract-arity : CoreCtx  Name  TC 
        extract-arity ctx op
          = do τ  inferType (def (quote _⟦_⟧_)
                                  ( vra (CoreCtx.theory ctx)
                                   vra (CoreCtx.model ctx)
                                   vra (con op [])
                                   []
                                  ))
               α  extract-type-arg τ
               vec-len α

        extract-interp : CoreCtx  Name    TC Term
        extract-interp ctx op arity
          = do let t = def (quote _⟦_⟧_)
                           ( vra (CoreCtx.theory ctx)
                            vra (CoreCtx.model ctx)
                            vra (con op [])
                            []
                           )
               normalise (n-ary arity (apply t (vra (vec (debrujin arity))  [])))

        extract-ops : CoreCtx  TC (List Operator)
        extract-ops ctx
          = do ops  normalise (def (quote ops) (vra (CoreCtx.signature ctx)  []))
               symbols  extract-constructors ops
               arities  listTC (map (extract-arity ctx) symbols)
               interps  listTC (mapList (map (extract-interp ctx) symbols) arities)
               return (mapList (mapList (map operator symbols) interps) arities)

record FoldCtx {a b} (A : Set a) (B : Set b) : Set (a  b) where
  constructor foldCtx
  field
    global : GlobalCtx

    f : Term  B  A × B
    g : Operator  List A  A
    ε : B

  open GlobalCtx global public

mutual

  map-fold :  {a b} {A : Set a} {B : Set b}
              FoldCtx A B  List (Arg Term)  B  TC (List A × B)
  map-fold ctx [] acc = return ([] , acc)
  map-fold ctx (vArg x  xs) acc
    = do (x , acc)  fold-acc ctx x acc
         (xs , acc)  map-fold ctx xs acc
         return (x  xs , acc)
  map-fold ctx (_  xs) acc = map-fold ctx xs acc

  fold-inner :  {a b} {A : Set a} {B : Set b}
                 FoldCtx A B  Operator  Term  B  TC (List A × B)
  fold-inner ctx op (var _ args)     acc = map-fold ctx (ekat (arity op) args) acc
  fold-inner ctx op (con _ args)     acc = map-fold ctx (ekat (arity op) args) acc
  fold-inner ctx op (def _ args)     acc = map-fold ctx (ekat (arity op) args) acc
  fold-inner ctx op (meta _ args)    acc = map-fold ctx (ekat (arity op) args) acc
  fold-inner ctx op (pat-lam _ args) acc = map-fold ctx (ekat (arity op) args) acc
  fold-inner _ _ t _ = typeError (termErr t  strErr "has no arguments"  [])

  fold-acc :  {a b} {A : Set a} {B : Set b}  FoldCtx A B  Term  B  TC (A × B)
  fold-acc ctx t acc
    = do op?  find  x  prefix (arity x) (normalised x) t) (FoldCtx.operators ctx)
         resolve op?
    where resolve : Maybe Operator  TC _
          resolve (just op)
            = do (args , acc)  fold-inner ctx op t acc
                 return ((FoldCtx.g ctx) op args , acc)
          resolve nothing = return ((FoldCtx.f ctx) t acc)

fold :  {a b} {A : Set a} {B : Set b}  FoldCtx A B  Term  TC (A × B)
fold ctx t = fold-acc ctx t (FoldCtx.ε ctx)

fold-⊤ :  {a} {A : Set a}  GlobalCtx
          (Term  A)  (Operator  List A  A)  Term  TC A
fold-⊤ ctx f g t
  = do (x , _)  fold (foldCtx ctx  x _  (f x , tt)) g tt) t
       return x

accumulate :  {a} {A : Set a}  GlobalCtx
              (Term  A  A)  A  Term  TC A
accumulate ctx f ε t
  = do (_ , acc)  fold (foldCtx ctx  x acc  (tt , f x acc))  _ _  tt) ε) t
       return acc

leaves : GlobalCtx  Term  TC 
leaves ctx = fold-⊤ ctx  _  1)  _  sum)

mutual
  argsOpen : Term  List (Arg Term)  TC Bool
  argsOpen τ [] = return false
  argsOpen τ (vArg x  xs)
    = do head  isOpen τ x
         tail  argsOpen τ xs
         return (head  tail)
  argsOpen τ (_  xs) = argsOpen τ xs

  isOpen : Term  Term  TC Bool
  isOpen τ t@(var _ _)  = hasType τ t
  isOpen τ t@(meta _ _) = hasType τ t
  isOpen τ (con c args) = argsOpen τ args
  isOpen τ (def f args) = argsOpen τ args
  isOpen τ unknown      = return true
  isOpen τ _            = return false

findOpen : GlobalCtx  List Term  Term  TC (List Term)
findOpen ctx ε t = flatten (accumulate ctx binding (return ε) t)
  where binding : Term  TC (List Term)  TC (List Term)
        binding t env
          = do env  env
               let τ = CoreCtx.carrier (GlobalCtx.core ctx)
               open?  isOpen τ t
               if open? then return (insert env t)
                        else return env

buildSyntax : GlobalCtx  List Term  Term  TC Term
buildSyntax ctx env t = fold-⊤ ctx buildInj buildTerm t
  where bt : Term
        bt = def (quote Free.BT)
                 ( vra (GlobalCtx.signature ctx)
                  vra (GlobalCtx.carrier ctx)
                  vra (lit (nat (length env)))
                  []
                 )

        buildAtom : Term  Term
        buildAtom t =
          con (quote Free.Term.atom)
              ( hra (GlobalCtx.signature ctx)
               hra unknown
               hra bt
               vra t
               []
              )

        buildDyn :   Term
        buildDyn n =
          con (quote Free.BT.dyn)
              ( hra (GlobalCtx.signature ctx)
               hra unknown
               hra (GlobalCtx.carrier ctx)
               hra (lit (nat (length env)))
               vra (fin n)
               []
              )

        buildSta : Term  Term
        buildSta t =
          con (quote Free.BT.sta)
              ( hra (GlobalCtx.signature ctx)
               hra unknown
               hra (GlobalCtx.carrier ctx)
               hra (lit (nat (length env)))
               vra t
               []
              )

        buildInj : Term  Term
        buildInj t with indexOf env t
        ...           | just n  = buildAtom (buildDyn n)
        ...           | nothing = buildAtom (buildSta t)

        buildTerm : Operator  List Term  Term
        buildTerm op xs =
          con (quote Free.Term.term)
               ( hra (GlobalCtx.signature ctx)
                hra unknown
                hra bt
                hra (lit (nat (arity op)))
                vra (con (name op) [])
                vra (vec (fromList xs))
                []
               )

environment : List Term  Term
environment env = vec (fromList env)

macro
  fragment : Name  Term  Term  TC 
  fragment frex model goal
    = do ctx  ctx frex model goal
         env  findOpen ctx [] (GlobalCtx.lhs ctx)
         env  findOpen ctx env (GlobalCtx.rhs ctx)
         lhs  buildSyntax ctx env (GlobalCtx.lhs ctx)
         rhs  buildSyntax ctx env (GlobalCtx.rhs ctx)
         let n = length env
         let frex = def (quote FreeExtension._[_])
                        ( hra (GlobalCtx.theory ctx)
                         vra (GlobalCtx.frex ctx)
                         vra (GlobalCtx.model ctx)
                         vra (lit (nat n))
                         []
                        )
         let setoid = def (quote ∥_∥/≈)
                          ( hra (GlobalCtx.theory ctx)
                           vra frex
                           []
                          )
         let p = def (quote Setoid.refl) (vra setoid  [])
         let frexify = def (quote frexify)
                           ( vra (GlobalCtx.theory ctx)
                            vra (GlobalCtx.frex ctx)
                            vra (GlobalCtx.model ctx)
                            vra (vec (fromList env))
                            hra lhs
                            hra rhs
                            vra p
                            []
                           )
         frexify  restore-telescope (GlobalCtx.telescope ctx) frexify
         unify frexify goal