/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.

Authors: Wojciech Nawrocki
-/
import Lean.PrettyPrinter.Delaborator.Basic
import Lean.Server.Rpc.Basic
import Lean.Widget.TaggedText

/-! RPC infrastructure for storing and formatting code fragments, in particular `Expr`s,
with environment and subexpression information. -/

namespace Lean.Widget
open Server

-- TODO: Some of the `WithBlah` types exist mostly because we cannot derive multi-argument RPC wrappers.
-- They will be gone eventually.
structure InfoWithCtx where
  ctx : Elab.ContextInfo
  info : Elab.Info
  deriving Inhabited, RpcEncoding with { withRef := true }

structure CodeToken where
  info : WithRpcRef InfoWithCtx
  -- TODO(WN): add fields for semantic highlighting
  -- kind : Lsp.SymbolKind
  deriving Inhabited, RpcEncoding

/-- Pretty-printed syntax (usually but not necessarily an `Expr`) with embedded `Info`s. -/
abbrev CodeWithInfos := TaggedText CodeToken

def CodeWithInfos.pretty (tt : CodeWithInfos) :=
  tt.stripTags

open Expr in
/-- Find a subexpression of `e` using the pretty-printer address scheme. -/
-- NOTE(WN): not currently in use
partial def traverse (e : Expr) (addr : Nat) : MetaM (LocalContext × Expr):= do
  let e ← Meta.instantiateMVars e
  go (tritsLE [] addr |>.drop 1) (← getLCtx) e
where
  tritsLE (acc : List Nat) (n : Nat) : List Nat :=
    if n == 0 then acc
    else tritsLE (n % 3 :: acc) (n / 3)

  go (addr : List Nat) (lctx : LocalContext) (e : Expr) : MetaM (LocalContext × Expr) := do
    match addr with
    | [] => return (lctx, e)
    | a::as => do
      let go' (e' : Expr) := do
        go as (← getLCtx) e'

      let eExpr ← match a, e with
        | 0, app e₁ e₂ _      => go' e₁
        | 1, app e₁ e₂ _      => go' e₂
        | 0, lam _ e₁ e₂ _    => go' e₁
        | 1, lam n e₁ e₂ data =>
          Meta.withLocalDecl n data.binderInfo e₁ fun fvar =>
            go' (e₂.instantiate1 fvar)
        | 0, forallE _ e₁ e₂ _    => go' e₁
        | 1, forallE n e₁ e₂ data =>
          Meta.withLocalDecl n data.binderInfo e₁ fun fvar =>
            go' (e₂.instantiate1 fvar)
        | 0, letE _ e₁ e₂ e₃ _ => go' e₁
        | 1, letE _ e₁ e₂ e₃ _ => go' e₂
        | 2, letE n e₁ e₂ e₃ _ =>
          Meta.withLetDecl n e₁ e₂ fun fvar => do
            go' (e₃.instantiate1 fvar)
        | 0, mdata _ e₁ _  => go' e₁
        | 0, proj _ _ e₁ _ => go' e₁
        | _, _             => return (lctx, e) -- panic! s!"cannot descend {a} into {e.expr}"

-- TODO(WN): should the two fns below go in `Lean.PrettyPrinter` ?
open PrettyPrinter in
private def formatWithOpts (e : Expr) (optsPerPos : Delaborator.OptionsPerPos)
    : MetaM (Format × Std.RBMap Nat Elab.Info compare) := do
    let currNamespace ← getCurrNamespace
    let openDecls ← getOpenDecls
    let opts ← getOptions
    let e ← Meta.instantiateMVars e
    let (stx, infos) ← PrettyPrinter.delabCore currNamespace openDecls e optsPerPos
    let stx := sanitizeSyntax stx |>.run' { options := opts }
    let stx ← PrettyPrinter.parenthesizeTerm stx
    let fmt ← PrettyPrinter.formatTerm stx
    return (fmt, infos)

/-- Pretty-print the expression and its subexpression information. -/
def formatInfos (e : Expr) : MetaM (Format × Std.RBMap Nat Elab.Info compare) :=
  formatWithOpts e {}

/-- Like `formatInfos` but with `pp.all` set at the top-level expression. -/
def formatExplicitInfos (e : Expr) : MetaM (Format × Std.RBMap Nat Elab.Info compare) :=
  let optsPerPos := Std.RBMap.ofList [(1, KVMap.empty.setBool `pp.all true)]
  formatWithOpts e optsPerPos

/-- Tags a pretty-printed `Expr` with infos from the delaborator. -/
partial def tagExprInfos (ctx : Elab.ContextInfo) (infos : Std.RBMap Nat Elab.Info compare) (tt : TaggedText (Nat × Nat))
    : CodeWithInfos :=
  go tt
where
  go (tt : TaggedText (Nat × Nat)) :=
    tt.rewrite fun (n, _) subTt =>
      match infos.find? n with
      | none   => go subTt
      | some i => TaggedText.tag ⟨WithRpcRef.mk { ctx, info := i }⟩ (go subTt)

def exprToInteractive (e : Expr) : MetaM CodeWithInfos := do
  let (fmt, infos) ← formatInfos e
  let tt := TaggedText.prettyTagged fmt
  let ctx := {
    env := ← getEnv
    mctx := ← getMCtx
    options := ← getOptions
    currNamespace := ← getCurrNamespace
    openDecls := ← getOpenDecls
    fileMap := default
  }
  return tagExprInfos ctx infos tt

def exprToInteractiveExplicit (e : Expr) : MetaM CodeWithInfos := do
  let (fmt, infos) ← formatExplicitInfos e
  let tt := TaggedText.prettyTagged fmt
  let ctx := {
    env := ← getEnv
    mctx := ← getMCtx
    options := ← getOptions
    currNamespace := ← getCurrNamespace
    openDecls := ← getOpenDecls
    fileMap := default
  }
  let infos := infos.erase 1 -- remove highlight for entire expression in popups
  return tagExprInfos ctx infos tt

end Lean.Widget
