import data.rat.basic
import data.list.defs

import tactic.rewrite_search.core.common

import ..types
import .common

open tactic

namespace tactic.rewrite_search.discovery

open tactic.rewrite_search

def BUNDLE_CHUNK_SIZE := 1

-- TODO Be smarter about calculating this.
meta def score_bundle (b : bundle_ref) (sample : list expr) : tactic ℕ := do
  mems ← b.get_members,
  mems.mfoldl (λ sum n, do
    e ← mk_const n,
    ret ← are_promising_rewrites (rewrite_list_from_lemma e) sample,
    return $ if ret then sum + 1 else sum
  ) 0

-- TODO report the lemma(s) which caused a selected bundle to be chosen,
-- so that that lemma could just be tagged individually.

-- TODO at the end of the search report which "desperations" things happened
-- (bundles added, random lemmas found and used) so that they can be addressed
-- more easily/conveniently.

def min_rel {α : Type} (l : list α) (r : α → α → Prop) [decidable_rel r] : option α :=
l.foldl (λ o a, match o with | none := some a | some b := if r b a then b else a end) none

meta def try_bundles (conf : config) (rs : list (expr × bool)) (p : progress) (sample : list expr) : tactic (progress × list (expr × bool)) :=
  if p.persistence < persistence.try_bundles then
    return (p, [])
  else do
    bs ← list.filter (λ b, b ∉ p.seen_bundles) <$> get_bundles,
    bs ← bs.mmap $ λ b, (do s ← score_bundle b sample, return (b, s)),
    (awful_bs, interesting_bs) ← pure $ bs.partition $ λ b, b.2 = 0,
    let p := {p with seen_bundles := p.seen_bundles.append (awful_bs.map prod.fst)},
    match min_rel interesting_bs (λ a b, a.2 > b.2) with
    | none := do
      if conf.trace_discovery then
      discovery_trace format!"Could not find any promising bundles of the {bs.length} non-suggested bundles considered: {bs.map $ λ b, b.1.bundle.name}"
      else skip,
      return (p, [])
    | some (b, score) := do
      if conf.trace_discovery then
      discovery_trace format!"Found a promising bundle (of {bs.length} considered) \"{b.bundle.name}\"! If we succeed, please suggest this bundle for consideration."
      else skip,
      ms ← b.get_members >>= load_names,
      return (p, rewrite_list_from_lemmas ms)
    end

end tactic.rewrite_search.discovery


/-
Copyright (c) 2021 Microsoft Corporation. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Leonardo de Moura
-/
import Lean.Meta.Tactic.Injection
import Lean.Meta.Tactic.Apply
import Lean.Meta.Tactic.Cases
import Lean.Meta.Tactic.Subst
import Lean.Meta.Tactic.Simp.Types
import Lean.Meta.Tactic.Assumption

namespace Lean.Meta

private def mkAnd? (args : Array Expr) : Option Expr := do
  if args.isEmpty then
    return none
  else
    let mut result := args.back
    for arg in args.reverse[1:] do
      result := mkApp2 (mkConst ``And) arg result
    return result

private partial def mkInjectiveTheoremTypeCore? (ctorVal : ConstructorVal) (useEq : Bool) : MetaM (Option Expr) := do
  let us := ctorVal.levelParams.map mkLevelParam
  forallBoundedTelescope ctorVal.type ctorVal.numParams fun params type =>
  forallTelescope type fun args1 resultType => do
    let jp (args2 args2New : Array Expr) : MetaM (Option Expr) := do
      let lhs := mkAppN (mkAppN (mkConst ctorVal.name us) params) args1
      let rhs := mkAppN (mkAppN (mkConst ctorVal.name us) params) args2
      let eq ← mkEq lhs rhs
      let mut eqs := #[]
      for arg1 in args1, arg2 in args2 do
        let arg1Type ← inferType arg1
        if !(← isProp arg1Type) && arg1 != arg2 then
          if (← isDefEq arg1Type (← inferType arg2)) then
            eqs := eqs.push (← mkEq arg1 arg2)
          else
            eqs := eqs.push (← mkHEq arg1 arg2)
      if let some andEqs ← mkAnd? eqs then
        let result ←
          if useEq then
            mkEq eq andEqs
          else
            mkArrow eq andEqs
        mkForallFVars params (← mkForallFVars args1 (← mkForallFVars args2New result))
      else
        return none
    let rec mkArgs2 (i : Nat) (type : Expr) (args2 args2New : Array Expr) : MetaM (Option Expr) := do
      if h : i < args1.size then
        match (← whnf type) with
        | Expr.forallE n d b _ =>
          let arg1 := args1.get ⟨i, h⟩
          if arg1.occurs resultType then
            mkArgs2 (i + 1) (b.instantiate1 arg1) (args2.push arg1) args2New
          else
            withLocalDecl n (if useEq then BinderInfo.default else BinderInfo.implicit) d fun arg2 =>
              mkArgs2 (i + 1) (b.instantiate1 arg2) (args2.push arg2) (args2New.push arg2)
        | _ => throwError "unexpected constructor type for '{ctorVal.name}'"
      else
        jp args2 args2New
    if useEq then
      mkArgs2 0 type #[] #[]
    else
      withNewBinderInfos (params.map fun param => (param.fvarId!, BinderInfo.implicit)) <|
      withNewBinderInfos (args1.map fun arg1 => (arg1.fvarId!, BinderInfo.implicit)) <|
        mkArgs2 0 type #[] #[]

private def mkInjectiveTheoremType? (ctorVal : ConstructorVal) : MetaM (Option Expr) :=
  mkInjectiveTheoremTypeCore? ctorVal false

private def injTheoremFailureHeader (ctorName : Name) : MessageData :=
  m!"failed to prove injectivity theorem for constructor '{ctorName}', use 'set_option genInjectivity false' to disable the generation"

private def throwInjectiveTheoremFailure {α} (ctorName : Name) (mvarId : MVarId) : MetaM α :=
  throwError "{injTheoremFailureHeader ctorName}{indentD <| MessageData.ofGoal mvarId}"

private def solveEqOfCtorEq (ctorName : Name) (mvarId : MVarId) (h : FVarId) : MetaM Unit := do
  match (← injection mvarId h) with
  | InjectionResult.solved => unreachable!
  | InjectionResult.subgoal mvarId .. =>
    (← splitAnd mvarId).forM fun mvarId =>
      unless (← assumptionCore mvarId) do
        throwInjectiveTheoremFailure ctorName mvarId

private def mkInjectiveTheoremValue (ctorName : Name) (targetType : Expr) : MetaM Expr :=
  forallTelescopeReducing targetType fun xs type => do
    let mvar ← mkFreshExprSyntheticOpaqueMVar type
    solveEqOfCtorEq ctorName mvar.mvarId! xs.back.fvarId!
    mkLambdaFVars xs mvar

def mkInjectiveTheoremNameFor (ctorName : Name) : Name :=
  ctorName ++ `inj

private def mkInjectiveTheorem (ctorVal : ConstructorVal) : MetaM Unit := do
  let some type ← mkInjectiveTheoremType? ctorVal
    | return ()
  let value ← mkInjectiveTheoremValue ctorVal.name type
  addDecl <| Declaration.thmDecl {
    name        := mkInjectiveTheoremNameFor ctorVal.name
    levelParams := ctorVal.levelParams
    type        := (← instantiateMVars type)
    value       := (← instantiateMVars value)
  }

def mkInjectiveEqTheoremNameFor (ctorName : Name) : Name :=
  ctorName ++ `injEq

private def mkInjectiveEqTheoremType? (ctorVal : ConstructorVal) : MetaM (Option Expr) :=
  mkInjectiveTheoremTypeCore? ctorVal true

private def mkInjectiveEqTheoremValue (ctorName : Name) (targetType : Expr) : MetaM Expr := do
  forallTelescopeReducing targetType fun xs type => do
    let mvar ← mkFreshExprSyntheticOpaqueMVar type
    let [mvarId₁, mvarId₂] ← apply mvar.mvarId! (mkConst ``Eq.propIntro)
      | throwError "unexpected number of subgoals when proving injective theorem for constructor '{ctorName}'"
    let (h, mvarId₁) ← intro1 mvarId₁
    let (_, mvarId₂) ← intro1 mvarId₂
    solveEqOfCtorEq ctorName mvarId₁ h
    let mvarId₂ ← casesAnd mvarId₂
    let mvarId₂ ← substEqs mvarId₂
    applyRefl mvarId₂ (injTheoremFailureHeader ctorName)
    mkLambdaFVars xs mvar

private def mkInjectiveEqTheorem (ctorVal : ConstructorVal) : MetaM Unit := do
  let some type ← mkInjectiveEqTheoremType? ctorVal
    | return ()
  let value ← mkInjectiveEqTheoremValue ctorVal.name type
  let name := mkInjectiveEqTheoremNameFor ctorVal.name
  addDecl <| Declaration.thmDecl {
    name
    levelParams := ctorVal.levelParams
    type        := (← instantiateMVars type)
    value       := (← instantiateMVars value)
  }
  addSimpLemma name (post := true) AttributeKind.global (prio := eval_prio default)

register_builtin_option genInjectivity : Bool := {
  defValue := true
  descr    := "generate injectivity theorems for inductive datatype constructors"
}

def mkInjectiveTheorems (declName : Name) : MetaM Unit := do
  if (← getEnv).contains ``Eq.propIntro && genInjectivity.get (← getOptions) &&  !(← isInductivePredicate declName) then
    let info ← getConstInfoInduct declName
    unless info.isUnsafe do
      for ctor in info.ctors do
        let ctorVal ← getConstInfoCtor ctor
        if ctorVal.numFields > 0 then
          mkInjectiveTheorem ctorVal
          mkInjectiveEqTheorem ctorVal

end Lean.Meta


import Lean
import Lean.Meta
import Lean.Parser
import LeanCodePrompts.CheckParse
import LeanCodePrompts.ParseJson
import LeanCodePrompts.Autocorrect
import LeanCodePrompts.KeywordSummary.KeywordExtraction
import LeanCodePrompts.EgsTranslate
open Lean Meta

open Lean Elab Parser Command

def fileName := "data/safe_prompts.json"

/-- extract prompt pairs from JSON response to local server -/
def sentenceSimPairs
  (s: String)
  (theoremField : String := "theorem")
   : MetaM  <| Except String (Array (String × String)) := do
  let json ← readJson s
  return do
    (← json.getArr?).mapM <| fun j => do
      let docstring ← j.getObjValAs? String "doc_string"
      let typeField :=
        if fileName ∈ ["data/mathlib4-prompts.json"] then "type"
        else theoremField
      let thm ← j.getObjValAs? String typeField
      pure (docstring, thm)

-- #eval sentenceSimPairs egSen

namespace GPT

def message (role content : String) : Json :=
  Json.mkObj [("role", role), ("content", content)]

def prompt (sys: String) (egs : List <| String × String)(query : String) : Json :=
  let head := message "system" sys
  let egArr :=
    egs.bind (fun (ds, thm) => [message "user" ds, message "assistant" thm])
  Json.arr <| head :: egArr ++ [message "user" query] |>.toArray

def sysPrompt := "You are a coding assistant who translates from natural language to Lean Theorem Prover code following examples. Follow EXACTLY the examples given."

def makePrompt(query : String)(pairs: Array (String × String)) : Json:= prompt sysPrompt pairs.toList query

def makeFlipPrompt(query : String)(pairs: Array (String × String)) : Json:= prompt sysPrompt (pairs.toList.map (fun (x, y) => (y, x))) query

def jsonToExprStrArray (json: Json) : TermElabM (Array String) := do
  let outArr : Array String ←
    match json.getArr? with
    | Except.ok arr =>
        let parsedArr : Array String ←
          arr.filterMapM <| fun js =>
            match js.getObjVal? "message" with
            | Except.ok jsobj =>
                match jsobj.getObjVal? "content" with
                | Except.ok jsstr =>
                  match jsstr.getStr? with
                  | Except.ok str => pure (some str)
                  | Except.error e =>
                    throwError m!"json string expected but got {js}, error: {e}"
                | Except.error _ =>
                  throwError m!"no content field in {jsobj}"
            | Except.error _ =>
                throwError m!"no message field in {js}"

        pure parsedArr
    | Except.error e => throwError m!"json parsing error: {e}"

end GPT

/-- make prompt from prompt pairs -/
@[deprecated GPT.makePrompt]
def makePrompt(prompt : String)(pairs: Array (String × String)) : String :=
      pairs.foldr (fun  (ds, thm) acc =>
        -- acc ++ "/-- " ++ ds ++" -/\ntheorem" ++ thm ++ "\n" ++ "\n"
s!"/-- {ds} -/
theorem {thm} :=

{acc}"
          ) s!"/-- {prompt} -/
theorem "

/-- make prompt for reverse translation from prompt pairs -/
@[deprecated GPT.makeFlipPrompt]
def makeFlipPrompt(statement : String)(pairs: Array (String × String)) : String :=
      pairs.foldr (fun  (ds, thm) acc =>
s!"theorem {thm} :=
/- {ds} -/

{acc}"
          ) s!"theorem {statement} :=
/- "

/-- make prompt for reverse translation from prompt pairs -/
def makeFlipStatementsPrompt(statement : String)(pairs: Array (String × String)) : String :=
      pairs.foldr (fun  (ds, thm) acc =>
s!"{thm} :=
/- {ds} -/

{acc}"
          ) s!"{statement} :=
/- "

def openAIKey : IO (Option String) := IO.getEnv "OPENAI_API_KEY"

/--query OpenAI Codex with given prompt and parameters -/
def codexQuery(prompt: String)(n: Nat := 1)
  (temp : JsonNumber := ⟨2, 1⟩)(stopTokens: Array String :=  #[":=", "-/"]) : MetaM Json := do
  let key? ← openAIKey
  let key :=
    match key? with
    | some k => k
    | none => panic! "OPENAI_API_KEY not set"
  let dataJs := Json.mkObj [("model", "code-davinci-002"), ("prompt", prompt), ("temperature", Json.num temp), ("n", n), ("max_tokens", 150), ("stop", Json.arr <| stopTokens |>.map Json.str)]
  let data := dataJs.pretty
  trace[Translate.info] "OpenAI query: {data}"
  let out ←  IO.Process.output {
        cmd:= "curl",
        args:= #["https://api.openai.com/v1/completions",
        "-X", "POST",
        "-H", "Authorization: Bearer " ++ key,
        "-H", "Content-Type: application/json",
        "--data", data]}
  readJson out.stdout

def gptQuery(messages: Json)(n: Nat := 1)
  (temp : JsonNumber := ⟨2, 1⟩)(stopTokens: Array String :=  #[":=", "-/"]) : MetaM Json := do
  let key? ← openAIKey
  let key :=
    match key? with
    | some k => k
    | none => panic! "OPENAI_API_KEY not set"
  let dataJs := Json.mkObj [("model", "gpt-3.5-turbo"), ("messages", messages)
  , ("temperature", Json.num temp), ("n", n), ("max_tokens", 150), ("stop", Json.arr <| stopTokens |>.map Json.str)
  ]
  let data := dataJs.pretty
  trace[Translate.info] "OpenAI query: {data}"
  let out ←  IO.Process.output {
        cmd:= "curl",
        args:= #["https://api.openai.com/v1/chat/completions",
        "-X", "POST",
        "-H", "Authorization: Bearer " ++ key,
        "-H", "Content-Type: application/json",
        "--data", data]}
  trace[Translate.info] "OpenAI response: {out.stdout} (stderr: {out.stderr})"
  readJson out.stdout

def openAIQuery(prompt: String)(n: Nat := 1)
  (temp : JsonNumber := ⟨2, 1⟩)(stopTokens: Array String :=  #[":=", "-/"]) : MetaM Json :=
  codexQuery prompt n temp stopTokens

/-!
Caching, polling etc to avoid repeatedly calling servers
-/

initialize webCacheJson : IO.Ref (HashMap String Json) ← IO.mkRef (HashMap.empty)

initialize pendingJsonQueries : IO.Ref (HashSet String)
    ← IO.mkRef (HashSet.empty)

initialize logCache : IO.Ref (Array String) ← IO.mkRef (#[])

def mkLog{α : Type _}[ToString α](msg: α) : IO Unit := do
  let cache ← logCache.get
  logCache.set (cache.push (toString msg))

def logs (num: Nat) : IO (List String) := do
  let cache ← logCache.get
  return cache.reverse.toList.take num

def showLogs (num: Nat) : IO Unit := do
  let cache ← logCache.get
  let ls := cache.reverse.toList.take num
  for lines in ls do
  for l in lines.splitOn "\n" do
    IO.println l

def getCachedJson? (s: String) : IO (Option Json) := do
  let cache ← webCacheJson.get
  return cache.find? s

def cacheJson (s: String)(js: Json)  : IO Unit := do
  let cache ← webCacheJson.get
  webCacheJson.set (cache.insert s js)
  return ()

partial def pollCacheJson (s : String) : IO Json := do
  let cache ← webCacheJson.get
  match cache.find? s with
  | some jsBlob => return jsBlob
  | none => do
    IO.sleep 200
    pollCacheJson s

/-- check if there is a valid elaboration after translation, autocorrection -/
def hasElab (s: String)(limit : Option Nat := none) : TermElabM Bool := do
    -- (elabThmTrans s).map (fun e => e.toBool)
  let elab? ← polyElabThmTrans s limit
  match elab? with
  | Except.error _ => return Bool.false
  | Except.ok els => return !els.isEmpty

/-- log to file -/
def elabLog (s: String) : IO Unit := do
  let logFile := System.mkFilePath ["results/elab_logs.txt"]
  let h ← IO.FS.Handle.mk logFile IO.FS.Mode.append Bool.false
  h.putStrLn s
  h.putStrLn ""

def fixedPrompts:= #[("If $z_1, \\dots, z_n$ are complex, then $|z_1 + z_2 + \\dots + z_n|\\leq |z_1| + |z_2| + \\dots + |z_n|$.", "(n : ℕ) (f : ℕ → ℂ) :\n abs (∑ i in finset.range n, f i) ≤ ∑ i in finset.range n, abs (f i) :="), ("If x and y are in $\\mathbb{R}^n$, then $|x+y|^2 + |x-y|^2 = 2|x|^2 + 2|y|^2$.", "(n : ℕ) (x y : euclidean_space ℝ (fin n)) :\n ∥x + y∥^2 + ∥x - y∥^2 = 2*∥x∥^2 + 2*∥y∥^2 :="), ("If $x$ is an element of infinite order in $G$, prove that the elements $x^n$, $n\\in\\mathbb{Z}$ are all distinct.", "(G : Type*) [group G] (x : G) (hx : x ≠ 1) (hx_inf : ∀ n : ℕ, x ^ n ≠ 1) : ∀ m n : ℤ, m ≠ n → x ^ m ≠ x ^ n :="), ("Let $X$ be a topological space; let $A$ be a subset of $X$. Suppose that for each $x\\in A$ there is an open set $U$ containing $x$ such that $U\\subset A$. Show that $A$ is open in $X$.", "(X : Type*) [topological_space X]\n (A : set X) (hA : ∀ x ∈ A, ∃ U : set X, is_open U ∧ x ∈ U ∧ U ⊆ A):\n is_open A :=")]

/-- choosing pairs to build a prompt -/
def getPromptPairs(s: String)(numSim : Nat)(numKW: Nat)
    (scoreBound: Float)(matchBound: Nat)
   : TermElabM (Array (String × String) × IO.Process.Output) := do
      let jsData := Json.mkObj [
        ("filename", fileName),
        ("field", "doc_string"),
        ("doc_string", s),
        ("n", numSim),
        ("model_name", "all-mpnet-base-v2")
      ]
      let simJsonOut ←
        IO.Process.output {cmd:= "curl", args:=
          #["-X", "POST", "-H", "Content-type: application/json", "-d", jsData.pretty, s!"{← leanAideIP}/nearest_prompts"]}
      let pairs? ← sentenceSimPairs simJsonOut.stdout "theorem"
      -- IO.println s!"obtained sentence similarity; time : {← IO.monoMsNow}"
      let allPairs : Array (String × String) ←
        match pairs? with
        | Except.error e =>
            throwError e
        | Except.ok pairs => pure pairs
      -- logInfo m!"all pairs: {allPairs}"
      let kwPairs :=
        if numKW >0
        then ←  keywordBasedPrompts docPair s numKW scoreBound matchBound
        else #[]
      -- IO.println s!"obtained keyword pairs; time : {← IO.monoMsNow}"
      let allPairs := (allPairs ++ kwPairs).toList.eraseDups.toArray
      let pairs -- := allPairs --
        ←  allPairs.filterM (fun (_, s) => do
            isElabPrompt s )
      let kwPairs ←  keywordBasedPrompts docPair s
      return (
          (pairs ++ kwPairs).toList.eraseDups.toArray, simJsonOut)

/-- choosing pairs to build a prompt -/
def getPromptPairsGeneral(s: String)(numSim : Nat)(field: String := "doc_string")
    (theoremField : String := "theorem")
   : TermElabM (Array (String × String) × IO.Process.Output) := do
      let jsData := Json.mkObj [
        ("filename", fileName),
        ("field", field),
        (field, s),
        ("n", numSim),
        ("model_name", "all-mpnet-base-v2")
      ]
      let simJsonOut ←
        IO.Process.output {cmd:= "curl", args:=
          #["-X", "POST", "-H", "Content-type: application/json", "-d", jsData.pretty, s!"{← leanAideIP}/nearest_prompts"]}
      let pairs? ← sentenceSimPairs simJsonOut.stdout theoremField
      -- IO.println s!"obtained sentence similarity; time : {← IO.monoMsNow}"
      let allPairs : Array (String × String) ←
        match pairs? with
        | Except.error e =>
            throwError e

        | Except.ok pairs => pure pairs
      -- logInfo m!"all pairs: {allPairs}"
      return (
          allPairs.toList.eraseDups.toArray, simJsonOut)

/-- given string to translate, build prompt and query OpenAI; returns JSON response
-/
def getCodeJson (s: String)(numSim : Nat:= 8)(numKW: Nat := 0)(includeFixed: Bool := Bool.false)(queryNum: Nat := 5)(temp : JsonNumber := ⟨2, 1⟩)(scoreBound: Float := 0.2)(matchBound: Nat := 15) : TermElabM Json := do
  match ← getCachedJson? s with
  | some js => return js
  | none =>
    let pending ←  pendingJsonQueries.get
    if pending.contains s then pollCacheJson s
    else
      let pending ←  pendingJsonQueries.get
      pendingJsonQueries.set (pending.insert s)
      -- work starts here; before this was caching, polling etc
      let (pairs, IOOut) ←
        if numSim > 0 then
          getPromptPairs s numSim numKW scoreBound matchBound
        else pure (#[], ⟨0, "", ""⟩)
      let pairs := if includeFixed then pairs ++ fixedPrompts else pairs
      let pairs  := pairs.filter (fun (s, _) => s.length < 100)
      let prompt := GPT.makePrompt s pairs
      trace[Translate.info] m!"prompt: \n{prompt.pretty}"
      -- mkLog prompt
      let fullJson ←
        gptQuery prompt queryNum temp
      let outJson :=
        (fullJson.getObjVal? "choices").toOption.getD (Json.arr #[])
      let pending ←  pendingJsonQueries.get
      pendingJsonQueries.set (pending.erase s)
      if IOOut.exitCode = 0 then cacheJson s outJson
        else throwError m!"Web query error: {IOOut.stderr}"
      return outJson

/-- Given an array of outputs, tries to elaborate them with translation and autocorrection and returns the best choice, throwing an error if nothing elaborates.  -/
def arrayToExpr (output: Array String) : TermElabM Expr := do
  let output := output.toList.eraseDups.toArray
  trace[Translate.info] m!"output:\n{output}"
  -- mkLog output
  let mut elaborated : Array String := Array.empty
  -- translation, autocorrection and filtering by elaboration
  for out in output do
    let ployElab? ← polyElabThmTrans out
    match ployElab? with
      | Except.error _ => pure ()
      | Except.ok es =>
        for (_ , _, s) in es do
            elaborated := elaborated.push s
  if elaborated.isEmpty then do
    -- information with failed logs
    logWarning m!"No valid output from Codex; outputs below"
    for out in output do
      let polyOut ←  polyStrThmTrans out
      for str in polyOut do
        logWarning m!"{str}"
    mkSyntheticSorry (mkSort levelZero)
  else
    -- grouping by trying to prove equality and selecting
    let groupSorted ← groupFuncStrs elaborated
    let topStr := groupSorted[0]![0]!
    let thmExc ← elabFuncTyp topStr
    match thmExc with
    | Except.ok (_, thm) => return thm
    | Except.error s => throwError s

/-- Given an array of outputs, tries to elaborate them with translation and autocorrection and returns the best choice, throwing an error if nothing elaborates.  -/
def arrayToStx (output: Array String) : TermElabM Syntax := do
  let output := output.toList.eraseDups.toArray
  trace[Translate.info] m!"output:\n{output}"
  -- mkLog output
  let mut elaborated : Array String := Array.empty
  -- translation, autocorrection and filtering by elaboration
  for out in output do
    let ployElab? ← polyElabThmTrans out
    match ployElab? with
      | Except.error _ => pure ()
      | Except.ok es =>
        for (_ , _, s) in es do
            elaborated := elaborated.push s
  if elaborated.isEmpty then do
    -- information with failed logs
    logWarning m!"No valid output from Codex; outputs below"
    for out in output do
      let polyOut ←  polyStrThmTrans out
      for str in polyOut do
        logWarning m!"{str}"
    pure Syntax.missing
  else
    -- grouping by trying to prove equality and selecting
    let groupSorted ← groupFuncStrs elaborated
    let topStr := groupSorted[0]![0]!
    let thmExc ← elabFuncTyp topStr
    match thmExc with
    | Except.ok (stx, _) => return stx
    | Except.error s => throwError s

/-- Given an array of outputs, tries to elaborate them with translation and autocorrection and optionally returns the best choice as well as all elaborated terms (used for batch processing, interactive code uses `arrayToExpr` instead)  -/
def arrayToExpr? (output: Array String) : TermElabM (Option (Expr× (Array String))) := do
  -- IO.println s!"arrayToExpr? called with {output.size} outputs"
  let mut elaborated : Array String := Array.empty
  let mut fullElaborated : Array String := Array.empty
  for out in output do
    -- IO.println s!"elaboration called: {out}"
    let ployElab? ← polyElabThmTrans out
    match ployElab? with
      | Except.error _ => pure ()
      | Except.ok es =>
        for (expr, _, s) in es do
          elaborated := elaborated.push s
          if !expr.hasExprMVar then
            fullElaborated := fullElaborated.push s
  if elaborated.isEmpty then
    elabLog "No valid output from Codex; outputs below"
    for out in output do
      let polyOut ←  polyStrThmTrans out
      for str in polyOut do
        elabLog s!"{str}"
    return none
  else
    let priority :=
        if fullElaborated.isEmpty then elaborated else fullElaborated
    let groupSorted ← groupFuncStrs priority
    let topStr := groupSorted[0]![0]!
    let thmExc ← elabFuncTyp topStr
    match thmExc with
    | Except.ok (_, thm) => return some (thm, elaborated)
    | Except.error s =>
        elabLog s!"Second round error : {s}"
        return none

def greedyArrayToExpr? (output: Array String) : TermElabM (Option Expr) := do
    output.findSomeM? <| fun out => do
      let t? ← elabThmTrans? out
      return t?.map fun (expr, _, _) => expr

/-- reverse translation from `Lean` to natural language -/
def leanToPrompt (thm: String)(numSim : Nat:= 5)(numKW: Nat := 1)(temp : JsonNumber := 0)(scoreBound: Float := 0.2)(matchBound: Nat := 15)(textField : String := "text") : TermElabM String := do
    let (pairs, _) ← getPromptPairs thm numSim numKW scoreBound matchBound
    let prompt := GPT.makeFlipPrompt thm pairs
    -- elabLog prompt
    let fullJson ← gptQuery prompt 1 temp
    let outJson :=
      (fullJson.getObjVal? "choices").toOption.getD (Json.arr #[])
    let out? := (outJson.getArrVal? 0).bind fun js => js.getObjVal? textField
    let outJson :=
        match (out?) with
        | Except.error s => Json.str s!"query for translation failed: {s}"
        | Except.ok js => js
    return outJson.getStr!

/-- reverse translation from `Lean` to natural language -/
@[deprecated leanToPrompt]
def statementToDoc (thm: String)(numSim : Nat:= 5)(temp : JsonNumber := 0) : TermElabM String := do
    let (pairs, _) ← getPromptPairsGeneral thm numSim "statement"
    let prompt := makeFlipStatementsPrompt thm pairs
    -- elabLog prompt
    let fullJson ← openAIQuery prompt 1 temp
    let outJson :=
      (fullJson.getObjVal? "choices").toOption.getD (Json.arr #[])
    let out? := (outJson.getArrVal? 0).bind fun js => js.getObjVal? "text"
    let outJson :=
        match (out?) with
        | Except.error s => Json.str s!"query for translation failed: {s}"
        | Except.ok js => js
    return outJson.getStr!

def egThm := "theorem eg_thm : ∀ n: Nat, ∃ m : Nat, m > n ∧ m % 2 = 0"

def egPairs := getPromptPairsGeneral egThm 5 "statement" "statement"

def egPrompt := do
  let (pairs, _) ← egPairs
  return makeFlipStatementsPrompt egThm pairs

-- #eval egPrompt

-- #eval statementToDoc egThm 5 0

-- #eval leanToPrompt "∀ {p : ℕ} [inst : Fact (Nat.Prime p)], p = 2 ∨ p % 2 = 1"

-- #eval leanToPrompt "∀ {α : Type u} {x : FreeGroup α}, x ≠ 1 → ¬IsOfFinOrder x"

-- #eval leanToPrompt "{  n :  ℕ } ->  Even   (    (   n +  1  ) * n  )"

/-- array of outputs extracted from OpenAI Json -/
def jsonToExprStrArray (json: Json) : TermElabM (Array String) := do
  let outArr : Array String ←
    match json.getArr? with
    | Except.ok arr =>
        let parsedArr : Array String ←
          arr.filterMapM <| fun js =>
            match js.getObjVal? "text" with
              | Except.ok jsstr =>
                match jsstr.getStr? with
                | Except.ok str => pure (some str)
                | Except.error e =>
                  throwError m!"json string expected but got {js}, error: {e}"
              | Except.error _ =>
                throwError m!"no text field"
        pure parsedArr
    | Except.error e => throwError m!"json parsing error: {e}"
  return outArr

/-- array of outputs extracted from Json Array -/
def jsonStringToExprStrArray (jsString: String) : TermElabM (Array String) := do
  try
  let json ← readJson jsString
  let outArr : Array String ←
    match json.getArr? with
    | Except.ok arr =>
        let parsedArr : Array String ←
          arr.filterMapM <| fun js =>
            match js.getStr? with
            | Except.ok str => pure (some str)
            | Except.error e =>
              throwError m!"json string expected but got {js}, error: {e}"
        pure parsedArr
    | Except.error _ => pure #[jsString]
  return outArr
  catch _ =>
    pure #[jsString]

-- #eval jsonStringToExprStrArray "simple"
-- #eval jsonStringToExprStrArray "[\"simple\", \"simple2\"]"

/-- given json returned by open-ai obtain the best translation -/
def jsonToExpr' (json: Json) : TermElabM Expr := do
  let output ← GPT.jsonToExprStrArray json
  arrayToExpr output

/-- translation from a comment-like syntax to a theorem statement -/
elab "//-" cb:commentBody  : term => do
  let s := cb.raw.getAtomVal
  let s := (s.dropRight 2).trim
  -- querying codex
  let js ← getCodeJson  s
  -- filtering, autocorrection and selection
  let e ← jsonToExpr' js
  trace[Translate.info] m!"{e}"
  return e

def uncurriedView(numArgs: Nat)(e: Expr) : MetaM String :=
  match numArgs with
  | 0 => do return " : " ++ (← e.view)
  | k +1 =>
    match e with
    | Expr.forallE n t _ bi => do
      let core := s!"{n.eraseMacroScopes} : {← t.view}"
      let typeString :=s!"{← t.view}"
      let argString := match bi with
      | BinderInfo.implicit => "{"++ core ++ "}"
      | BinderInfo.strictImplicit => "{{ "++ core ++ "}}"
      | BinderInfo.instImplicit =>
        if (`inst).isPrefixOf n then s!"[{typeString}]"
          else s!"[{core}]"
      | BinderInfo.default => s!"({core})"
      let tail : String ←
        withLocalDecl `func BinderInfo.default e fun func =>
          withLocalDecl n bi t fun arg => do
            let fx := mkAppN func #[arg]
            let newType ← inferType fx
            uncurriedView k newType
      return " " ++ argString ++ tail
    | _ => do return " : " ++ (← e.view)

elab "uncurry2" e:term : term => do
  let e ← Term.elabTerm e none
  let e ← uncurriedView 2 e
  return mkStrLit e

universe u

def translateViewM (s: String) : TermElabM String := do
  let js ← getCodeJson  s
  let output ← GPT.jsonToExprStrArray js
  trace[Translate.info] m!"{output}"
  let e? ← arrayToExpr? output
  match e? with
  | some (e, _) => do
    e.view
  | none => do
    let stx ← output.findSomeM? <| fun s => do
      let exp ←  identMappedFunStx s
      return exp.toOption
    return stx.getD "False"

/-- view of string in core; to be run with Snapshot.runCore
-/
def translateViewCore (s: String) : CoreM String :=
  (translateViewM s).run'.run'


import Do.Mut

/-! # Early Return -/

open Lean

/- Disable the automatic monadic lifting feature described in the paper.
   We want to make it clear that we do not depend on it. -/
set_option autoLift false

def runCatch [Monad m] (x : ExceptT α m α) : m α :=
  ExceptT.run x >>= fun
    | Except.ok x => pure x
    | Except.error e => pure e

/-- Count syntax nodes satisfying `p`. -/
partial def Lean.Syntax.count (stx : Syntax) (p : Syntax → Bool) : Nat :=
  stx.getArgs.foldl (fun n arg => n + arg.count p) (if p stx then 1 else 0)

syntax "return" term : stmt

syntax "return" : expander

macro_rules
  | `(do' $s) => do  -- (1')
    -- optimization: fall back to original rule (1) if now `return` statement was expanded
    let s' ← expandStmt (← `(stmt| expand! return in $s))
    if s'.raw.count (· matches `(stmt| return $_)) == s.raw.count (· matches `(stmt| return $_)) then
      `(d! $s)
    else
      `(ExceptCpsT.runCatch (d! $s'))

macro_rules
  | `(stmt| expand! return in return $e) => `(stmt| throw $e)          -- (R1)
  | `(stmt| expand! return in $e:term) => `(stmt| ExceptCpsT.lift $e)  -- (R2)

variable [Monad m]
variable (ma ma' : m α)
variable (b : Bool)

example [LawfulMonad m] :
    (do' let x ← ma;
         return x)
    = ma
:= by simp

example : Id.run
    (do' let x := 1; return x)
    = 1
:= rfl

example [LawfulMonad m] :
     (do' if b then {
            let x ← ma;
            return x
          };
          ma')
     =
     (if b then ma else ma')
:= by cases b <;> simp

example [LawfulMonad m] :
    (do' let y ←
           if b then {
             let x ← ma;
             return x
           } else {
             ma'
           };
         pure y)
    =
    (if b then ma else ma')
:= by cases b <;> simp


import LeanCodePrompts.Translate
import Lean
import Lean.Meta
open Lean Meta Elab

/-- extract prompt pairs from JSON response to local server -/
def sentenceSimTriples(s: String) : MetaM  <| Except String (Array (String × String × String)) := do
  let json ← readJson (s)
  -- logInfo "obtained json"
  match json.getArr? with
  | Except.ok jsonArr => do
    let pairs ←  jsonArr.mapM fun json => do
      let docstring : String ←
        match (json.getObjVal? "doc_string") with
        | Except.error e => throwError s!"Error {e} while getting doc_string"
        | Except.ok js =>
          match js.getStr? with
          | Except.error e => throwError s!"Error {e} while processing {js} as string"
          | Except.ok s => pure s
      let args ←  match (json.getObjVal? "args") with
        | Except.error e => throwError s!"Error {e} while getting theorem"
        | Except.ok js =>
          match js.getStr? with
          | Except.error e => throwError s!"Error {e} while processing {js} as string"
          | Except.ok s => pure s
      let type ←  match (json.getObjVal? "type") with
        | Except.error e => throwError s!"Error {e} while getting theorem"
        | Except.ok js =>
          match js.getStr? with
          | Except.error e => throwError s!"Error {e} while processing {js} as string"
          | Except.ok s => pure s
      return (docstring, args, type)
    return Except.ok pairs
  | Except.error e => return Except.error e

/-- choosing pairs to build a prompt -/
def getPromptTriples(s: String)(numSim : Nat)
   : TermElabM (Array (String × String × String) × IO.Process.Output) := do
      let jsData := Json.mkObj [
        ("filename", "data/safe_prompts.json"),
        ("field", "doc_string"),
        ("doc_string", s),
        ("n", numSim),
        ("model_name", "all-mpnet-base-v2")
      ]
      let simJsonOut ←
        IO.Process.output {cmd:= "curl", args:=
          #["-X", "POST", "-H", "Content-type: application/json", "-d", jsData.pretty, s!"{← leanAideIP}/nearest_prompts"]}
      let triples? ← sentenceSimTriples simJsonOut.stdout
      let allTriples := triples?.toOption.getD #[]
        -- ←  allPairs.filterM (fun (_, s) => do
        --     isElabPrompt s )
      return (
          allTriples.toList.eraseDups.toArray, simJsonOut)

def sysContinuationPrompt := "You are a coding assistant who translates from natural language to Lean Theorem Prover code following examples. Follow EXACTLY the syntax in the examples given. You will continue the sequence of examples"

def continuationPrompt (egs: String) : Json :=
  Json.arr <| #[GPT.message "system" sysContinuationPrompt, GPT.message "user" egs]

/-- make prompt for continuing statements-/
def makeThmsPrompt(pairs: Array (String × String))(context: String := "") : String :=
pairs.foldr (fun  (_, thm) acc =>
        -- acc ++ "/-- " ++ ds ++" -/\ntheorem" ++ thm ++ "\n" ++ "\n"
s!"theorem {thm} :=

{acc}"
          ) s!""

/-- make prompt for continuing statements with docs-/
def makeDocsThmsPrompt(pairs: Array (String × String)) : String :=
pairs.foldr (fun  (ds, thm) acc =>
s!"/-- {ds} -/
theorem {thm} := sorry

{acc}") s!"
/--"

/-- make prompt for continuing statements with docs-/
def makeSectionPrompt(triples: Array (String × String × String))
    (context: String) : String :=
triples.foldr (fun  (ds, args, type) acc =>
s!"section
variable {args}
/-- {ds} -/
theorem : {type} := sorry
end

{acc}") s!"section
variable {context}
/-- "

def getContinuationExprs (s: String)(numSim : Nat:= 10)(numKW: Nat := 1)(includeFixed: Bool := Bool.false)(queryNum: Nat := 20)(temp : JsonNumber := ⟨8, 1⟩)(scoreBound: Float := 0.2)(matchBound: Nat := 15) : TermElabM <| Array String := do
      -- work starts here; before this was caching, polling etc
    let (pairs, IOOut) ←
      if numSim > 0 then
        getPromptPairs s numSim numKW scoreBound matchBound
      else pure (#[], ⟨0, "", ""⟩)
    let pairs := if includeFixed then pairs ++ fixedPrompts else pairs
    let prompt := continuationPrompt (makeThmsPrompt pairs)
    trace[Translate.info] m!"prompt: \n{prompt}"
    mkLog prompt
    let fullJson ← gptQuery prompt queryNum temp
    let outJson :=
      (fullJson.getObjVal? "choices").toOption.getD (Json.arr #[])
    let pending ←  pendingJsonQueries.get
    pendingJsonQueries.set (pending.erase s)
    if IOOut.exitCode = 0 then cacheJson s outJson
      else throwError m!"Web query error: {IOOut.stderr}"
    GPT.jsonToExprStrArray outJson

def getDocContinuationExprs (s: String)(numSim : Nat:= 10)(numKW: Nat := 1)(includeFixed: Bool := Bool.false)(queryNum: Nat := 8)(temp : JsonNumber := ⟨8, 1⟩)(scoreBound: Float := 0.2)(matchBound: Nat := 15) : TermElabM <| Array String := do
      -- work starts here; before this was caching, polling etc
    let (pairs, IOOut) ←
      if numSim > 0 then
        getPromptPairs s numSim numKW scoreBound matchBound
      else pure (#[], ⟨0, "", ""⟩)
    let pairs := if includeFixed then pairs ++ fixedPrompts else pairs
    let promptPairs := pairs.map (fun (doc, thm) => ("State a theorem with docstring", s!"/-- {doc} -/\ntheorem {thm}"))
    let prompt := GPT.makePrompt "State a theorem" promptPairs
    trace[Translate.info] m!"prompt: \n{prompt}"
    mkLog prompt
    let fullJson ← gptQuery prompt queryNum temp #[":="]
    let outJson :=
      (fullJson.getObjVal? "choices").toOption.getD (Json.arr #[])
    let pending ←  pendingJsonQueries.get
    pendingJsonQueries.set (pending.erase s)
    if IOOut.exitCode = 0 then cacheJson s outJson
      else throwError m!"Web query error: {IOOut.stderr}"
    let completions ← GPT.jsonToExprStrArray outJson
    let padded := completions.map (fun c => "/-- " ++ c)
    return padded

def getSectionContinuationExprs (s: String)(context: String)(numSim : Nat:= 10)(queryNum: Nat := 8)(temp : JsonNumber := ⟨8, 1⟩) : TermElabM <| Array String := do
      -- work starts here; before this was caching, polling etc
    let (triples, IOOut) ←
        getPromptTriples s numSim
    let promptPairs := triples.map (fun (doc, args, thm) => ("State a theorem with docstring in context", s!"variable {args}\n/-- {doc} -/\ntheorem {thm}"))
    let prompt := GPT.makePrompt "State a theorem" promptPairs
    trace[Translate.info] m!"prompt: \n{prompt}"
    mkLog prompt
    let fullJson ← gptQuery prompt queryNum temp #[":="]
    let outJson :=
      (fullJson.getObjVal? "choices").toOption.getD (Json.arr #[])
    let pending ←  pendingJsonQueries.get
    pendingJsonQueries.set (pending.erase s)
    if IOOut.exitCode = 0 then cacheJson s outJson
      else throwError m!"Web query error: {IOOut.stderr}"
    let completions ← GPT.jsonToExprStrArray outJson
    let padded := completions.map (fun c =>
    s!"{context}
/-- " ++ c)
    return padded

def showContinuationExprs (s: String)(context: String := "")(numSim : Nat:= 10)(numKW: Nat := 1)(includeFixed: Bool := Bool.false)(queryNum: Nat := 8)(temp : JsonNumber := ⟨8, 1⟩)(scoreBound: Float := 0.2)(matchBound: Nat := 15) : TermElabM <| Array (String × (List String)) := do
  let exprs ←
    getContinuationExprs s numSim numKW includeFixed queryNum temp scoreBound matchBound
  exprs.mapM (fun s => do
    let exps? ← polyElabThmTrans (context ++ " " ++ s)
    let exps := exps?.toOption.getD []
    return (s!"{s} := sorry",exps.map (fun (_, s) => s.2))
  )

def showDocContinuationExprs (s: String)(numSim : Nat:= 10)(numKW: Nat := 1)(includeFixed: Bool := Bool.false)(queryNum: Nat := 20)(temp : JsonNumber := ⟨8, 1⟩)(scoreBound: Float := 0.2)(matchBound: Nat := 15) : TermElabM <| Array (String × (List String)) := do
  let exprs ←
    getDocContinuationExprs s numSim numKW includeFixed queryNum temp scoreBound matchBound
  exprs.mapM (fun s => do
    let exps? ← polyElabThmTrans (s)
    let exps := exps?.toOption.getD []
    return (s, exps.map (fun (_, s) => s.2))
  )

def showSectionContinuationExprs (s: String)(context: String := "")(numSim : Nat:= 10)(queryNum: Nat := 16)(temp : JsonNumber := ⟨8, 1⟩) : TermElabM <| Array (String × (List String)) := do
  let exprs ←
    getSectionContinuationExprs s context numSim  queryNum temp
  exprs.mapM (fun s => do
    let exps? ← polyElabThmTrans (s)
    let exps := exps?.toOption.getD []
    return (s, exps.map (fun (_, s) => s.2))
  )


def f : IO Nat := do
  IO.println "hello"
  IO.getStdin
  return 10

def f1 : ExceptT String (StateT Nat Id) Nat := do
  modify (· + 1)
  get

def f2 (x : Nat) : ExceptT String (StateT Nat Id) Nat := do
  modify (· + x)
  get

def g1 : ExceptT String (StateT Nat Id) Unit := do
  let x : String ← f1
  return ()

def g2 : ExceptT String (StateT Nat Id) Unit := do
  let x : String ← f2 10
  return ()

def g3 : ExceptT String (StateT Nat Id) String := do
  let x ← f2
  f1

example : Nat := Id.run do
  let mut n : Nat := 0
  (n, _) := (false, false)
  n


/-
Copyright (c) 2022 Alex J. Best. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Alex J. Best
-/
import Lean.Parser.Term
import Lean.Parser.Do
import Lean.Elab.Command
import Mathlib.Data.KVMap

/-!
# The `unset_option` command

This file defines an `unset_option` user command, which unsets user configurable
options.
For example inputing `set_option blah 7` and then `unset_option blah`
returns the user to the default state before any `set_option` command is called.
This is helpful when the user does not know the default value of the option or it
is cleaner not to write it explicitly, or for some options where the default
behaviour is different from any user set value.
-/

namespace Lean.Elab

variable [Monad m] [MonadOptions m] [MonadExceptOf Exception m] [MonadRef m]
variable [AddErrorMessageContext m] [MonadLiftT (EIO Exception) m] [MonadInfoTree m]

/-- unset the option specified by id -/
def elabUnsetOption (id : Syntax) : m Options := do
  -- We include the first argument (the keyword) for position information in case `id` is `missing`.
  addCompletionInfo <| CompletionInfo.option (← getRef)
  unsetOption id.getId.eraseMacroScopes
where
  /-- unset the given option name -/
  unsetOption (optionName : Name) : m Options := return (← getOptions).erase optionName

namespace Command

/-- Unset a user option -/
elab (name := unsetOption) "unset_option " opt:ident : command => do
  let options ← Elab.elabUnsetOption opt
  modify fun s ↦ { s with maxRecDepth := maxRecDepth.get options }
  modifyScope fun scope ↦ { scope with opts := options }

end Command
end Lean.Elab


/-
Copyright (c) 2018 Chris Hughes. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Chris Hughes, Abhimanyu Pallavi Sudhir
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.algebra.geom_sum
import Mathlib.data.nat.choose.sum
import Mathlib.data.complex.basic
import Mathlib.PostPort

universes u_1 u_2 u_3

namespace Mathlib

/-!
# Exponential, trigonometric and hyperbolic trigonometric functions

This file contains the definitions of the real and complex exponential, sine, cosine, tangent,
hyperbolic sine, hyperbolic cosine, and hyperbolic tangent functions.

-/

theorem forall_ge_le_of_forall_le_succ {α : Type u_1} [preorder α] (f : ℕ → α) {m : ℕ} (h : ∀ (n : ℕ), n ≥ m → f (Nat.succ n) ≤ f n) {l : ℕ} (k : ℕ) (H : k ≥ m) : k ≤ l → f l ≤ f k := sorry

theorem is_cau_of_decreasing_bounded {α : Type u_1} [linear_ordered_field α] [archimedean α] (f : ℕ → α) {a : α} {m : ℕ} (ham : ∀ (n : ℕ), n ≥ m → abs (f n) ≤ a) (hnm : ∀ (n : ℕ), n ≥ m → f (Nat.succ n) ≤ f n) : is_cau_seq abs f := sorry

theorem is_cau_of_mono_bounded {α : Type u_1} [linear_ordered_field α] [archimedean α] (f : ℕ → α) {a : α} {m : ℕ} (ham : ∀ (n : ℕ), n ≥ m → abs (f n) ≤ a) (hnm : ∀ (n : ℕ), n ≥ m → f n ≤ f (Nat.succ n)) : is_cau_seq abs f := sorry

theorem is_cau_series_of_abv_le_cau {α : Type u_1} {β : Type u_2} [ring β] [linear_ordered_field α] {abv : β → α} [is_absolute_value abv] {f : ℕ → β} {g : ℕ → α} (n : ℕ) : (∀ (m : ℕ), n ≤ m → abv (f m) ≤ g m) →
  (is_cau_seq abs fun (n : ℕ) => finset.sum (finset.range n) fun (i : ℕ) => g i) →
    is_cau_seq abv fun (n : ℕ) => finset.sum (finset.range n) fun (i : ℕ) => f i := sorry

theorem is_cau_series_of_abv_cau {α : Type u_1} {β : Type u_2} [ring β] [linear_ordered_field α] {abv : β → α} [is_absolute_value abv] {f : ℕ → β} : (is_cau_seq abs fun (m : ℕ) => finset.sum (finset.range m) fun (n : ℕ) => abv (f n)) →
  is_cau_seq abv fun (m : ℕ) => finset.sum (finset.range m) fun (n : ℕ) => f n :=
  is_cau_series_of_abv_le_cau 0 fun (n : ℕ) (h : 0 ≤ n) => le_refl (abv (f n))

theorem is_cau_geo_series {α : Type u_1} [linear_ordered_field α] [archimedean α] {β : Type u_2} [field β] {abv : β → α} [is_absolute_value abv] (x : β) (hx1 : abv x < 1) : is_cau_seq abv fun (n : ℕ) => finset.sum (finset.range n) fun (m : ℕ) => x ^ m := sorry

theorem is_cau_geo_series_const {α : Type u_1} [linear_ordered_field α] [archimedean α] (a : α) {x : α} (hx1 : abs x < 1) : is_cau_seq abs fun (m : ℕ) => finset.sum (finset.range m) fun (n : ℕ) => a * x ^ n := sorry

theorem series_ratio_test {α : Type u_1} {β : Type u_2} [ring β] [linear_ordered_field α] [archimedean α] {abv : β → α} [is_absolute_value abv] {f : ℕ → β} (n : ℕ) (r : α) (hr0 : 0 ≤ r) (hr1 : r < 1) (h : ∀ (m : ℕ), n ≤ m → abv (f (Nat.succ m)) ≤ r * abv (f m)) : is_cau_seq abv fun (m : ℕ) => finset.sum (finset.range m) fun (n : ℕ) => f n := sorry

theorem sum_range_diag_flip {α : Type u_1} [add_comm_monoid α] (n : ℕ) (f : ℕ → ℕ → α) : (finset.sum (finset.range n) fun (m : ℕ) => finset.sum (finset.range (m + 1)) fun (k : ℕ) => f k (m - k)) =
  finset.sum (finset.range n) fun (m : ℕ) => finset.sum (finset.range (n - m)) fun (k : ℕ) => f m k := sorry

theorem sum_range_sub_sum_range {α : Type u_1} [add_comm_group α] {f : ℕ → α} {n : ℕ} {m : ℕ} (hnm : n ≤ m) : ((finset.sum (finset.range m) fun (k : ℕ) => f k) - finset.sum (finset.range n) fun (k : ℕ) => f k) =
  finset.sum (finset.filter (fun (k : ℕ) => n ≤ k) (finset.range m)) fun (k : ℕ) => f k := sorry

theorem abv_sum_le_sum_abv {α : Type u_1} {β : Type u_2} [ring β] [linear_ordered_field α] {abv : β → α} [is_absolute_value abv] {γ : Type u_3} (f : γ → β) (s : finset γ) : abv (finset.sum s fun (k : γ) => f k) ≤ finset.sum s fun (k : γ) => abv (f k) := sorry

theorem cauchy_product {α : Type u_1} {β : Type u_2} [ring β] [linear_ordered_field α] {abv : β → α} [is_absolute_value abv] {a : ℕ → β} {b : ℕ → β} (ha : is_cau_seq abs fun (m : ℕ) => finset.sum (finset.range m) fun (n : ℕ) => abv (a n)) (hb : is_cau_seq abv fun (m : ℕ) => finset.sum (finset.range m) fun (n : ℕ) => b n) (ε : α) (ε0 : 0 < ε) : ∃ (i : ℕ),
  ∀ (j : ℕ),
    j ≥ i →
      abv
          (((finset.sum (finset.range j) fun (k : ℕ) => a k) * finset.sum (finset.range j) fun (n : ℕ) => b n) -
            finset.sum (finset.range j)
              fun (n : ℕ) => finset.sum (finset.range (n + 1)) fun (m : ℕ) => a m * b (n - m)) <
        ε := sorry

namespace complex

theorem is_cau_abs_exp (z : ℂ) : is_cau_seq abs fun (n : ℕ) => finset.sum (finset.range n) fun (m : ℕ) => abs (z ^ m / ↑(nat.factorial m)) := sorry

theorem is_cau_exp (z : ℂ) : is_cau_seq abs fun (n : ℕ) => finset.sum (finset.range n) fun (m : ℕ) => z ^ m / ↑(nat.factorial m) :=
  is_cau_series_of_abv_cau (is_cau_abs_exp z)

/-- The Cauchy sequence consisting of partial sums of the Taylor series of
the complex exponential function -/
def exp' (z : ℂ) : cau_seq ℂ abs :=
  { val := fun (n : ℕ) => finset.sum (finset.range n) fun (m : ℕ) => z ^ m / ↑(nat.factorial m),
    property := is_cau_exp z }

/-- The complex exponential function, defined via its Taylor series -/
def exp (z : ℂ) : ℂ :=
  cau_seq.lim (exp' z)

/-- The complex sine function, defined via `exp` -/
def sin (z : ℂ) : ℂ :=
  (exp (-z * I) - exp (z * I)) * I / bit0 1

/-- The complex cosine function, defined via `exp` -/
def cos (z : ℂ) : ℂ :=
  (exp (z * I) + exp (-z * I)) / bit0 1

/-- The complex tangent function, defined as `sin z / cos z` -/
def tan (z : ℂ) : ℂ :=
  sin z / cos z

/-- The complex hyperbolic sine function, defined via `exp` -/
def sinh (z : ℂ) : ℂ :=
  (exp z - exp (-z)) / bit0 1

/-- The complex hyperbolic cosine function, defined via `exp` -/
def cosh (z : ℂ) : ℂ :=
  (exp z + exp (-z)) / bit0 1

/-- The complex hyperbolic tangent function, defined as `sinh z / cosh z` -/
def tanh (z : ℂ) : ℂ :=
  sinh z / cosh z

end complex

namespace real

/-- The real exponential function, defined as the real part of the complex exponential -/
def exp (x : ℝ) : ℝ :=
  complex.re (complex.exp ↑x)

/-- The real sine function, defined as the real part of the complex sine -/
def sin (x : ℝ) : ℝ :=
  complex.re (complex.sin ↑x)

/-- The real cosine function, defined as the real part of the complex cosine -/
def cos (x : ℝ) : ℝ :=
  complex.re (complex.cos ↑x)

/-- The real tangent function, defined as the real part of the complex tangent -/
def tan (x : ℝ) : ℝ :=
  complex.re (complex.tan ↑x)

/-- The real hypebolic sine function, defined as the real part of the complex hyperbolic sine -/
def sinh (x : ℝ) : ℝ :=
  complex.re (complex.sinh ↑x)

/-- The real hypebolic cosine function, defined as the real part of the complex hyperbolic cosine -/
def cosh (x : ℝ) : ℝ :=
  complex.re (complex.cosh ↑x)

/-- The real hypebolic tangent function, defined as the real part of
the complex hyperbolic tangent -/
def tanh (x : ℝ) : ℝ :=
  complex.re (complex.tanh ↑x)

end real

namespace complex

@[simp] theorem exp_zero : exp 0 = 1 := sorry

theorem exp_add (x : ℂ) (y : ℂ) : exp (x + y) = exp x * exp y := sorry

theorem exp_list_sum (l : List ℂ) : exp (list.sum l) = list.prod (list.map exp l) :=
  monoid_hom.map_list_prod (monoid_hom.mk exp exp_zero exp_add) l

theorem exp_multiset_sum (s : multiset ℂ) : exp (multiset.sum s) = multiset.prod (multiset.map exp s) :=
  monoid_hom.map_multiset_prod (monoid_hom.mk exp exp_zero exp_add) s

theorem exp_sum {α : Type u_1} (s : finset α) (f : α → ℂ) : exp (finset.sum s fun (x : α) => f x) = finset.prod s fun (x : α) => exp (f x) :=
  monoid_hom.map_prod (monoid_hom.mk exp exp_zero exp_add) f s

theorem exp_nat_mul (x : ℂ) (n : ℕ) : exp (↑n * x) = exp x ^ n := sorry

theorem exp_ne_zero (x : ℂ) : exp x ≠ 0 := sorry

theorem exp_neg (x : ℂ) : exp (-x) = (exp x⁻¹) := sorry

theorem exp_sub (x : ℂ) (y : ℂ) : exp (x - y) = exp x / exp y := sorry

@[simp] theorem exp_conj (x : ℂ) : exp (coe_fn conj x) = coe_fn conj (exp x) := sorry

@[simp] theorem of_real_exp_of_real_re (x : ℝ) : ↑(re (exp ↑x)) = exp ↑x :=
  iff.mp eq_conj_iff_re
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn conj (exp ↑x) = exp ↑x)) (Eq.symm (exp_conj ↑x))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (exp (coe_fn conj ↑x) = exp ↑x)) (conj_of_real x))) (Eq.refl (exp ↑x))))

@[simp] theorem of_real_exp (x : ℝ) : ↑(real.exp x) = exp ↑x :=
  of_real_exp_of_real_re x

@[simp] theorem exp_of_real_im (x : ℝ) : im (exp ↑x) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (im (exp ↑x) = 0)) (Eq.symm (of_real_exp_of_real_re x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (im ↑(re (exp ↑x)) = 0)) (of_real_im (re (exp ↑x))))) (Eq.refl 0))

theorem exp_of_real_re (x : ℝ) : re (exp ↑x) = real.exp x :=
  rfl

theorem two_sinh (x : ℂ) : bit0 1 * sinh x = exp x - exp (-x) :=
  mul_div_cancel' (exp x - exp (-x)) two_ne_zero'

theorem two_cosh (x : ℂ) : bit0 1 * cosh x = exp x + exp (-x) :=
  mul_div_cancel' (exp x + exp (-x)) two_ne_zero'

@[simp] theorem sinh_zero : sinh 0 = 0 := sorry

@[simp] theorem sinh_neg (x : ℂ) : sinh (-x) = -sinh x := sorry

theorem sinh_add (x : ℂ) (y : ℂ) : sinh (x + y) = sinh x * cosh y + cosh x * sinh y := sorry

@[simp] theorem cosh_zero : cosh 0 = 1 := sorry

@[simp] theorem cosh_neg (x : ℂ) : cosh (-x) = cosh x := sorry

theorem cosh_add (x : ℂ) (y : ℂ) : cosh (x + y) = cosh x * cosh y + sinh x * sinh y := sorry

theorem sinh_sub (x : ℂ) (y : ℂ) : sinh (x - y) = sinh x * cosh y - cosh x * sinh y := sorry

theorem cosh_sub (x : ℂ) (y : ℂ) : cosh (x - y) = cosh x * cosh y - sinh x * sinh y := sorry

theorem sinh_conj (x : ℂ) : sinh (coe_fn conj x) = coe_fn conj (sinh x) := sorry

@[simp] theorem of_real_sinh_of_real_re (x : ℝ) : ↑(re (sinh ↑x)) = sinh ↑x :=
  iff.mp eq_conj_iff_re
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn conj (sinh ↑x) = sinh ↑x)) (Eq.symm (sinh_conj ↑x))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (sinh (coe_fn conj ↑x) = sinh ↑x)) (conj_of_real x))) (Eq.refl (sinh ↑x))))

@[simp] theorem of_real_sinh (x : ℝ) : ↑(real.sinh x) = sinh ↑x :=
  of_real_sinh_of_real_re x

@[simp] theorem sinh_of_real_im (x : ℝ) : im (sinh ↑x) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (im (sinh ↑x) = 0)) (Eq.symm (of_real_sinh_of_real_re x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (im ↑(re (sinh ↑x)) = 0)) (of_real_im (re (sinh ↑x))))) (Eq.refl 0))

theorem sinh_of_real_re (x : ℝ) : re (sinh ↑x) = real.sinh x :=
  rfl

theorem cosh_conj (x : ℂ) : cosh (coe_fn conj x) = coe_fn conj (cosh x) := sorry

@[simp] theorem of_real_cosh_of_real_re (x : ℝ) : ↑(re (cosh ↑x)) = cosh ↑x :=
  iff.mp eq_conj_iff_re
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn conj (cosh ↑x) = cosh ↑x)) (Eq.symm (cosh_conj ↑x))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (cosh (coe_fn conj ↑x) = cosh ↑x)) (conj_of_real x))) (Eq.refl (cosh ↑x))))

@[simp] theorem of_real_cosh (x : ℝ) : ↑(real.cosh x) = cosh ↑x :=
  of_real_cosh_of_real_re x

@[simp] theorem cosh_of_real_im (x : ℝ) : im (cosh ↑x) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (im (cosh ↑x) = 0)) (Eq.symm (of_real_cosh_of_real_re x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (im ↑(re (cosh ↑x)) = 0)) (of_real_im (re (cosh ↑x))))) (Eq.refl 0))

theorem cosh_of_real_re (x : ℝ) : re (cosh ↑x) = real.cosh x :=
  rfl

theorem tanh_eq_sinh_div_cosh (x : ℂ) : tanh x = sinh x / cosh x :=
  rfl

@[simp] theorem tanh_zero : tanh 0 = 0 := sorry

@[simp] theorem tanh_neg (x : ℂ) : tanh (-x) = -tanh x := sorry

theorem tanh_conj (x : ℂ) : tanh (coe_fn conj x) = coe_fn conj (tanh x) := sorry

@[simp] theorem of_real_tanh_of_real_re (x : ℝ) : ↑(re (tanh ↑x)) = tanh ↑x :=
  iff.mp eq_conj_iff_re
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn conj (tanh ↑x) = tanh ↑x)) (Eq.symm (tanh_conj ↑x))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (tanh (coe_fn conj ↑x) = tanh ↑x)) (conj_of_real x))) (Eq.refl (tanh ↑x))))

@[simp] theorem of_real_tanh (x : ℝ) : ↑(real.tanh x) = tanh ↑x :=
  of_real_tanh_of_real_re x

@[simp] theorem tanh_of_real_im (x : ℝ) : im (tanh ↑x) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (im (tanh ↑x) = 0)) (Eq.symm (of_real_tanh_of_real_re x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (im ↑(re (tanh ↑x)) = 0)) (of_real_im (re (tanh ↑x))))) (Eq.refl 0))

theorem tanh_of_real_re (x : ℝ) : re (tanh ↑x) = real.tanh x :=
  rfl

theorem cosh_add_sinh (x : ℂ) : cosh x + sinh x = exp x := sorry

theorem sinh_add_cosh (x : ℂ) : sinh x + cosh x = exp x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (sinh x + cosh x = exp x)) (add_comm (sinh x) (cosh x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (cosh x + sinh x = exp x)) (cosh_add_sinh x))) (Eq.refl (exp x)))

theorem cosh_sub_sinh (x : ℂ) : cosh x - sinh x = exp (-x) := sorry

theorem cosh_sq_sub_sinh_sq (x : ℂ) : cosh x ^ bit0 1 - sinh x ^ bit0 1 = 1 := sorry

theorem cosh_square (x : ℂ) : cosh x ^ bit0 1 = sinh x ^ bit0 1 + 1 := sorry

theorem sinh_square (x : ℂ) : sinh x ^ bit0 1 = cosh x ^ bit0 1 - 1 := sorry

theorem cosh_two_mul (x : ℂ) : cosh (bit0 1 * x) = cosh x ^ bit0 1 + sinh x ^ bit0 1 := sorry

theorem sinh_two_mul (x : ℂ) : sinh (bit0 1 * x) = bit0 1 * sinh x * cosh x := sorry

theorem cosh_three_mul (x : ℂ) : cosh (bit1 1 * x) = bit0 (bit0 1) * cosh x ^ bit1 1 - bit1 1 * cosh x := sorry

theorem sinh_three_mul (x : ℂ) : sinh (bit1 1 * x) = bit0 (bit0 1) * sinh x ^ bit1 1 + bit1 1 * sinh x := sorry

@[simp] theorem sin_zero : sin 0 = 0 := sorry

@[simp] theorem sin_neg (x : ℂ) : sin (-x) = -sin x := sorry

theorem two_sin (x : ℂ) : bit0 1 * sin x = (exp (-x * I) - exp (x * I)) * I :=
  mul_div_cancel' ((exp (-x * I) - exp (x * I)) * I) two_ne_zero'

theorem two_cos (x : ℂ) : bit0 1 * cos x = exp (x * I) + exp (-x * I) :=
  mul_div_cancel' (exp (x * I) + exp (-x * I)) two_ne_zero'

theorem sinh_mul_I (x : ℂ) : sinh (x * I) = sin x * I := sorry

theorem cosh_mul_I (x : ℂ) : cosh (x * I) = cos x := sorry

theorem tanh_mul_I (x : ℂ) : tanh (x * I) = tan x * I := sorry

theorem cos_mul_I (x : ℂ) : cos (x * I) = cosh x := sorry

theorem sin_mul_I (x : ℂ) : sin (x * I) = sinh x * I := sorry

theorem tan_mul_I (x : ℂ) : tan (x * I) = tanh x * I := sorry

theorem sin_add (x : ℂ) (y : ℂ) : sin (x + y) = sin x * cos y + cos x * sin y := sorry

@[simp] theorem cos_zero : cos 0 = 1 := sorry

@[simp] theorem cos_neg (x : ℂ) : cos (-x) = cos x := sorry

theorem cos_add (x : ℂ) (y : ℂ) : cos (x + y) = cos x * cos y - sin x * sin y := sorry

theorem sin_sub (x : ℂ) (y : ℂ) : sin (x - y) = sin x * cos y - cos x * sin y := sorry

theorem cos_sub (x : ℂ) (y : ℂ) : cos (x - y) = cos x * cos y + sin x * sin y := sorry

theorem sin_add_mul_I (x : ℂ) (y : ℂ) : sin (x + y * I) = sin x * cosh y + cos x * sinh y * I := sorry

theorem sin_eq (z : ℂ) : sin z = sin ↑(re z) * cosh ↑(im z) + cos ↑(re z) * sinh ↑(im z) * I := sorry

theorem cos_add_mul_I (x : ℂ) (y : ℂ) : cos (x + y * I) = cos x * cosh y - sin x * sinh y * I := sorry

theorem cos_eq (z : ℂ) : cos z = cos ↑(re z) * cosh ↑(im z) - sin ↑(re z) * sinh ↑(im z) * I := sorry

theorem sin_sub_sin (x : ℂ) (y : ℂ) : sin x - sin y = bit0 1 * sin ((x - y) / bit0 1) * cos ((x + y) / bit0 1) := sorry

theorem cos_sub_cos (x : ℂ) (y : ℂ) : cos x - cos y = -bit0 1 * sin ((x + y) / bit0 1) * sin ((x - y) / bit0 1) := sorry

theorem cos_add_cos (x : ℂ) (y : ℂ) : cos x + cos y = bit0 1 * cos ((x + y) / bit0 1) * cos ((x - y) / bit0 1) := sorry

theorem sin_conj (x : ℂ) : sin (coe_fn conj x) = coe_fn conj (sin x) := sorry

@[simp] theorem of_real_sin_of_real_re (x : ℝ) : ↑(re (sin ↑x)) = sin ↑x :=
  iff.mp eq_conj_iff_re
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn conj (sin ↑x) = sin ↑x)) (Eq.symm (sin_conj ↑x))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (sin (coe_fn conj ↑x) = sin ↑x)) (conj_of_real x))) (Eq.refl (sin ↑x))))

@[simp] theorem of_real_sin (x : ℝ) : ↑(real.sin x) = sin ↑x :=
  of_real_sin_of_real_re x

@[simp] theorem sin_of_real_im (x : ℝ) : im (sin ↑x) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (im (sin ↑x) = 0)) (Eq.symm (of_real_sin_of_real_re x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (im ↑(re (sin ↑x)) = 0)) (of_real_im (re (sin ↑x))))) (Eq.refl 0))

theorem sin_of_real_re (x : ℝ) : re (sin ↑x) = real.sin x :=
  rfl

theorem cos_conj (x : ℂ) : cos (coe_fn conj x) = coe_fn conj (cos x) := sorry

@[simp] theorem of_real_cos_of_real_re (x : ℝ) : ↑(re (cos ↑x)) = cos ↑x :=
  iff.mp eq_conj_iff_re
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn conj (cos ↑x) = cos ↑x)) (Eq.symm (cos_conj ↑x))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (cos (coe_fn conj ↑x) = cos ↑x)) (conj_of_real x))) (Eq.refl (cos ↑x))))

@[simp] theorem of_real_cos (x : ℝ) : ↑(real.cos x) = cos ↑x :=
  of_real_cos_of_real_re x

@[simp] theorem cos_of_real_im (x : ℝ) : im (cos ↑x) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (im (cos ↑x) = 0)) (Eq.symm (of_real_cos_of_real_re x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (im ↑(re (cos ↑x)) = 0)) (of_real_im (re (cos ↑x))))) (Eq.refl 0))

theorem cos_of_real_re (x : ℝ) : re (cos ↑x) = real.cos x :=
  rfl

@[simp] theorem tan_zero : tan 0 = 0 := sorry

theorem tan_eq_sin_div_cos (x : ℂ) : tan x = sin x / cos x :=
  rfl

theorem tan_mul_cos {x : ℂ} (hx : cos x ≠ 0) : tan x * cos x = sin x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (tan x * cos x = sin x)) (tan_eq_sin_div_cos x)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (sin x / cos x * cos x = sin x)) (div_mul_cancel (sin x) hx))) (Eq.refl (sin x)))

@[simp] theorem tan_neg (x : ℂ) : tan (-x) = -tan x := sorry

theorem tan_conj (x : ℂ) : tan (coe_fn conj x) = coe_fn conj (tan x) := sorry

@[simp] theorem of_real_tan_of_real_re (x : ℝ) : ↑(re (tan ↑x)) = tan ↑x :=
  iff.mp eq_conj_iff_re
    (eq.mpr (id (Eq._oldrec (Eq.refl (coe_fn conj (tan ↑x) = tan ↑x)) (Eq.symm (tan_conj ↑x))))
      (eq.mpr (id (Eq._oldrec (Eq.refl (tan (coe_fn conj ↑x) = tan ↑x)) (conj_of_real x))) (Eq.refl (tan ↑x))))

@[simp] theorem of_real_tan (x : ℝ) : ↑(real.tan x) = tan ↑x :=
  of_real_tan_of_real_re x

@[simp] theorem tan_of_real_im (x : ℝ) : im (tan ↑x) = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (im (tan ↑x) = 0)) (Eq.symm (of_real_tan_of_real_re x))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (im ↑(re (tan ↑x)) = 0)) (of_real_im (re (tan ↑x))))) (Eq.refl 0))

theorem tan_of_real_re (x : ℝ) : re (tan ↑x) = real.tan x :=
  rfl

theorem cos_add_sin_I (x : ℂ) : cos x + sin x * I = exp (x * I) := sorry

theorem cos_sub_sin_I (x : ℂ) : cos x - sin x * I = exp (-x * I) := sorry

@[simp] theorem sin_sq_add_cos_sq (x : ℂ) : sin x ^ bit0 1 + cos x ^ bit0 1 = 1 := sorry

@[simp] theorem cos_sq_add_sin_sq (x : ℂ) : cos x ^ bit0 1 + sin x ^ bit0 1 = 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (cos x ^ bit0 1 + sin x ^ bit0 1 = 1)) (add_comm (cos x ^ bit0 1) (sin x ^ bit0 1))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (sin x ^ bit0 1 + cos x ^ bit0 1 = 1)) (sin_sq_add_cos_sq x))) (Eq.refl 1))

theorem cos_two_mul' (x : ℂ) : cos (bit0 1 * x) = cos x ^ bit0 1 - sin x ^ bit0 1 := sorry

theorem cos_two_mul (x : ℂ) : cos (bit0 1 * x) = bit0 1 * cos x ^ bit0 1 - 1 := sorry

theorem sin_two_mul (x : ℂ) : sin (bit0 1 * x) = bit0 1 * sin x * cos x := sorry

theorem cos_square (x : ℂ) : cos x ^ bit0 1 = 1 / bit0 1 + cos (bit0 1 * x) / bit0 1 := sorry

theorem cos_square' (x : ℂ) : cos x ^ bit0 1 = 1 - sin x ^ bit0 1 := sorry

theorem sin_square (x : ℂ) : sin x ^ bit0 1 = 1 - cos x ^ bit0 1 := sorry

theorem inv_one_add_tan_sq {x : ℂ} (hx : cos x ≠ 0) : 1 + tan x ^ bit0 1⁻¹ = cos x ^ bit0 1 := sorry

theorem tan_sq_div_one_add_tan_sq {x : ℂ} (hx : cos x ≠ 0) : tan x ^ bit0 1 / (1 + tan x ^ bit0 1) = sin x ^ bit0 1 := sorry

theorem cos_three_mul (x : ℂ) : cos (bit1 1 * x) = bit0 (bit0 1) * cos x ^ bit1 1 - bit1 1 * cos x := sorry

theorem sin_three_mul (x : ℂ) : sin (bit1 1 * x) = bit1 1 * sin x - bit0 (bit0 1) * sin x ^ bit1 1 := sorry

theorem exp_mul_I (x : ℂ) : exp (x * I) = cos x + sin x * I :=
  Eq.symm (cos_add_sin_I x)

theorem exp_add_mul_I (x : ℂ) (y : ℂ) : exp (x + y * I) = exp x * (cos y + sin y * I) :=
  eq.mpr (id (Eq._oldrec (Eq.refl (exp (x + y * I) = exp x * (cos y + sin y * I))) (exp_add x (y * I))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp x * exp (y * I) = exp x * (cos y + sin y * I))) (exp_mul_I y)))
      (Eq.refl (exp x * (cos y + sin y * I))))

theorem exp_eq_exp_re_mul_sin_add_cos (x : ℂ) : exp x = exp ↑(re x) * (cos ↑(im x) + sin ↑(im x) * I) := sorry

/-- De Moivre's formula -/
theorem cos_add_sin_mul_I_pow (n : ℕ) (z : ℂ) : (cos z + sin z * I) ^ n = cos (↑n * z) + sin (↑n * z) * I := sorry

end complex

namespace real

@[simp] theorem exp_zero : exp 0 = 1 := sorry

theorem exp_add (x : ℝ) (y : ℝ) : exp (x + y) = exp x * exp y := sorry

theorem exp_list_sum (l : List ℝ) : exp (list.sum l) = list.prod (list.map exp l) :=
  monoid_hom.map_list_prod (monoid_hom.mk exp exp_zero exp_add) l

theorem exp_multiset_sum (s : multiset ℝ) : exp (multiset.sum s) = multiset.prod (multiset.map exp s) :=
  monoid_hom.map_multiset_prod (monoid_hom.mk exp exp_zero exp_add) s

theorem exp_sum {α : Type u_1} (s : finset α) (f : α → ℝ) : exp (finset.sum s fun (x : α) => f x) = finset.prod s fun (x : α) => exp (f x) :=
  monoid_hom.map_prod (monoid_hom.mk exp exp_zero exp_add) f s

theorem exp_nat_mul (x : ℝ) (n : ℕ) : exp (↑n * x) = exp x ^ n := sorry

theorem exp_ne_zero (x : ℝ) : exp x ≠ 0 := sorry

theorem exp_neg (x : ℝ) : exp (-x) = (exp x⁻¹) := sorry

theorem exp_sub (x : ℝ) (y : ℝ) : exp (x - y) = exp x / exp y := sorry

@[simp] theorem sin_zero : sin 0 = 0 := sorry

@[simp] theorem sin_neg (x : ℝ) : sin (-x) = -sin x := sorry

theorem sin_add (x : ℝ) (y : ℝ) : sin (x + y) = sin x * cos y + cos x * sin y := sorry

@[simp] theorem cos_zero : cos 0 = 1 := sorry

@[simp] theorem cos_neg (x : ℝ) : cos (-x) = cos x := sorry

theorem cos_add (x : ℝ) (y : ℝ) : cos (x + y) = cos x * cos y - sin x * sin y := sorry

theorem sin_sub (x : ℝ) (y : ℝ) : sin (x - y) = sin x * cos y - cos x * sin y := sorry

theorem cos_sub (x : ℝ) (y : ℝ) : cos (x - y) = cos x * cos y + sin x * sin y := sorry

theorem sin_sub_sin (x : ℝ) (y : ℝ) : sin x - sin y = bit0 1 * sin ((x - y) / bit0 1) * cos ((x + y) / bit0 1) := sorry

theorem cos_sub_cos (x : ℝ) (y : ℝ) : cos x - cos y = -bit0 1 * sin ((x + y) / bit0 1) * sin ((x - y) / bit0 1) := sorry

theorem cos_add_cos (x : ℝ) (y : ℝ) : cos x + cos y = bit0 1 * cos ((x + y) / bit0 1) * cos ((x - y) / bit0 1) := sorry

theorem tan_eq_sin_div_cos (x : ℝ) : tan x = sin x / cos x := sorry

theorem tan_mul_cos {x : ℝ} (hx : cos x ≠ 0) : tan x * cos x = sin x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (tan x * cos x = sin x)) (tan_eq_sin_div_cos x)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (sin x / cos x * cos x = sin x)) (div_mul_cancel (sin x) hx))) (Eq.refl (sin x)))

@[simp] theorem tan_zero : tan 0 = 0 := sorry

@[simp] theorem tan_neg (x : ℝ) : tan (-x) = -tan x := sorry

@[simp] theorem sin_sq_add_cos_sq (x : ℝ) : sin x ^ bit0 1 + cos x ^ bit0 1 = 1 := sorry

@[simp] theorem cos_sq_add_sin_sq (x : ℝ) : cos x ^ bit0 1 + sin x ^ bit0 1 = 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (cos x ^ bit0 1 + sin x ^ bit0 1 = 1)) (add_comm (cos x ^ bit0 1) (sin x ^ bit0 1))))
    (eq.mpr (id (Eq._oldrec (Eq.refl (sin x ^ bit0 1 + cos x ^ bit0 1 = 1)) (sin_sq_add_cos_sq x))) (Eq.refl 1))

theorem sin_sq_le_one (x : ℝ) : sin x ^ bit0 1 ≤ 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (sin x ^ bit0 1 ≤ 1)) (Eq.symm (sin_sq_add_cos_sq x))))
    (le_add_of_nonneg_right (pow_two_nonneg (cos x)))

theorem cos_sq_le_one (x : ℝ) : cos x ^ bit0 1 ≤ 1 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (cos x ^ bit0 1 ≤ 1)) (Eq.symm (sin_sq_add_cos_sq x))))
    (le_add_of_nonneg_left (pow_two_nonneg (sin x)))

theorem abs_sin_le_one (x : ℝ) : abs (sin x) ≤ 1 := sorry

theorem abs_cos_le_one (x : ℝ) : abs (cos x) ≤ 1 := sorry

theorem sin_le_one (x : ℝ) : sin x ≤ 1 :=
  and.right (iff.mp abs_le (abs_sin_le_one x))

theorem cos_le_one (x : ℝ) : cos x ≤ 1 :=
  and.right (iff.mp abs_le (abs_cos_le_one x))

theorem neg_one_le_sin (x : ℝ) : -1 ≤ sin x :=
  and.left (iff.mp abs_le (abs_sin_le_one x))

theorem neg_one_le_cos (x : ℝ) : -1 ≤ cos x :=
  and.left (iff.mp abs_le (abs_cos_le_one x))

theorem cos_two_mul (x : ℝ) : cos (bit0 1 * x) = bit0 1 * cos x ^ bit0 1 - 1 := sorry

theorem cos_two_mul' (x : ℝ) : cos (bit0 1 * x) = cos x ^ bit0 1 - sin x ^ bit0 1 := sorry

theorem sin_two_mul (x : ℝ) : sin (bit0 1 * x) = bit0 1 * sin x * cos x := sorry

theorem cos_square (x : ℝ) : cos x ^ bit0 1 = 1 / bit0 1 + cos (bit0 1 * x) / bit0 1 := sorry

theorem cos_square' (x : ℝ) : cos x ^ bit0 1 = 1 - sin x ^ bit0 1 := sorry

theorem sin_square (x : ℝ) : sin x ^ bit0 1 = 1 - cos x ^ bit0 1 :=
  iff.mpr eq_sub_iff_add_eq (sin_sq_add_cos_sq x)

theorem inv_one_add_tan_sq {x : ℝ} (hx : cos x ≠ 0) : 1 + tan x ^ bit0 1⁻¹ = cos x ^ bit0 1 := sorry

theorem tan_sq_div_one_add_tan_sq {x : ℝ} (hx : cos x ≠ 0) : tan x ^ bit0 1 / (1 + tan x ^ bit0 1) = sin x ^ bit0 1 := sorry

theorem inv_sqrt_one_add_tan_sq {x : ℝ} (hx : 0 < cos x) : sqrt (1 + tan x ^ bit0 1)⁻¹ = cos x := sorry

theorem tan_div_sqrt_one_add_tan_sq {x : ℝ} (hx : 0 < cos x) : tan x / sqrt (1 + tan x ^ bit0 1) = sin x := sorry

theorem cos_three_mul (x : ℝ) : cos (bit1 1 * x) = bit0 (bit0 1) * cos x ^ bit1 1 - bit1 1 * cos x := sorry

theorem sin_three_mul (x : ℝ) : sin (bit1 1 * x) = bit1 1 * sin x - bit0 (bit0 1) * sin x ^ bit1 1 := sorry

/-- The definition of `sinh` in terms of `exp`. -/
theorem sinh_eq (x : ℝ) : sinh x = (exp x - exp (-x)) / bit0 1 := sorry

@[simp] theorem sinh_zero : sinh 0 = 0 := sorry

@[simp] theorem sinh_neg (x : ℝ) : sinh (-x) = -sinh x := sorry

theorem sinh_add (x : ℝ) (y : ℝ) : sinh (x + y) = sinh x * cosh y + cosh x * sinh y := sorry

/-- The definition of `cosh` in terms of `exp`. -/
theorem cosh_eq (x : ℝ) : cosh x = (exp x + exp (-x)) / bit0 1 := sorry

@[simp] theorem cosh_zero : cosh 0 = 1 := sorry

@[simp] theorem cosh_neg (x : ℝ) : cosh (-x) = cosh x := sorry

theorem cosh_add (x : ℝ) (y : ℝ) : cosh (x + y) = cosh x * cosh y + sinh x * sinh y := sorry

theorem sinh_sub (x : ℝ) (y : ℝ) : sinh (x - y) = sinh x * cosh y - cosh x * sinh y := sorry

theorem cosh_sub (x : ℝ) (y : ℝ) : cosh (x - y) = cosh x * cosh y - sinh x * sinh y := sorry

theorem tanh_eq_sinh_div_cosh (x : ℝ) : tanh x = sinh x / cosh x := sorry

@[simp] theorem tanh_zero : tanh 0 = 0 := sorry

@[simp] theorem tanh_neg (x : ℝ) : tanh (-x) = -tanh x := sorry

theorem cosh_add_sinh (x : ℝ) : cosh x + sinh x = exp x := sorry

theorem sinh_add_cosh (x : ℝ) : sinh x + cosh x = exp x := sorry

theorem cosh_sq_sub_sinh_sq (x : ℝ) : cosh x ^ bit0 1 - sinh x ^ bit0 1 = 1 := sorry

theorem cosh_square (x : ℝ) : cosh x ^ bit0 1 = sinh x ^ bit0 1 + 1 := sorry

theorem sinh_square (x : ℝ) : sinh x ^ bit0 1 = cosh x ^ bit0 1 - 1 := sorry

theorem cosh_two_mul (x : ℝ) : cosh (bit0 1 * x) = cosh x ^ bit0 1 + sinh x ^ bit0 1 := sorry

theorem sinh_two_mul (x : ℝ) : sinh (bit0 1 * x) = bit0 1 * sinh x * cosh x := sorry

theorem cosh_three_mul (x : ℝ) : cosh (bit1 1 * x) = bit0 (bit0 1) * cosh x ^ bit1 1 - bit1 1 * cosh x := sorry

theorem sinh_three_mul (x : ℝ) : sinh (bit1 1 * x) = bit0 (bit0 1) * sinh x ^ bit1 1 + bit1 1 * sinh x := sorry

/- TODO make this private and prove ∀ x -/

theorem add_one_le_exp_of_nonneg {x : ℝ} (hx : 0 ≤ x) : x + 1 ≤ exp x := sorry

theorem one_le_exp {x : ℝ} (hx : 0 ≤ x) : 1 ≤ exp x := sorry

theorem exp_pos (x : ℝ) : 0 < exp x := sorry

@[simp] theorem abs_exp (x : ℝ) : abs (exp x) = exp x :=
  abs_of_pos (exp_pos x)

theorem exp_strict_mono : strict_mono exp := sorry

theorem exp_monotone {x : ℝ} {y : ℝ} : x ≤ y → exp x ≤ exp y :=
  strict_mono.monotone exp_strict_mono

@[simp] theorem exp_lt_exp {x : ℝ} {y : ℝ} : exp x < exp y ↔ x < y :=
  strict_mono.lt_iff_lt exp_strict_mono

@[simp] theorem exp_le_exp {x : ℝ} {y : ℝ} : exp x ≤ exp y ↔ x ≤ y :=
  strict_mono.le_iff_le exp_strict_mono

theorem exp_injective : function.injective exp :=
  strict_mono.injective exp_strict_mono

@[simp] theorem exp_eq_exp {x : ℝ} {y : ℝ} : exp x = exp y ↔ x = y :=
  function.injective.eq_iff exp_injective

@[simp] theorem exp_eq_one_iff (x : ℝ) : exp x = 1 ↔ x = 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (exp x = 1 ↔ x = 0)) (Eq.symm exp_zero)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp x = exp 0 ↔ x = 0)) (propext (function.injective.eq_iff exp_injective))))
      (iff.refl (x = 0)))

@[simp] theorem one_lt_exp_iff {x : ℝ} : 1 < exp x ↔ 0 < x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (1 < exp x ↔ 0 < x)) (Eq.symm exp_zero)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp 0 < exp x ↔ 0 < x)) (propext exp_lt_exp))) (iff.refl (0 < x)))

@[simp] theorem exp_lt_one_iff {x : ℝ} : exp x < 1 ↔ x < 0 :=
  eq.mpr (id (Eq._oldrec (Eq.refl (exp x < 1 ↔ x < 0)) (Eq.symm exp_zero)))
    (eq.mpr (id (Eq._oldrec (Eq.refl (exp x < exp 0 ↔ x < 0)) (propext exp_lt_exp))) (iff.refl (x < 0)))

@[simp] theorem exp_le_one_iff {x : ℝ} : exp x ≤ 1 ↔ x ≤ 0 :=
  exp_zero ▸ exp_le_exp

@[simp] theorem one_le_exp_iff {x : ℝ} : 1 ≤ exp x ↔ 0 ≤ x :=
  exp_zero ▸ exp_le_exp

/-- `real.cosh` is always positive -/
theorem cosh_pos (x : ℝ) : 0 < cosh x :=
  Eq.symm (cosh_eq x) ▸ half_pos (add_pos (exp_pos x) (exp_pos (-x)))

end real

namespace complex

theorem sum_div_factorial_le {α : Type u_1} [linear_ordered_field α] (n : ℕ) (j : ℕ) (hn : 0 < n) : (finset.sum (finset.filter (fun (k : ℕ) => n ≤ k) (finset.range j)) fun (m : ℕ) => 1 / ↑(nat.factorial m)) ≤
  ↑(Nat.succ n) * (↑(nat.factorial n) * ↑n⁻¹) := sorry

theorem exp_bound {x : ℂ} (hx : abs x ≤ 1) {n : ℕ} (hn : 0 < n) : abs (exp x - finset.sum (finset.range n) fun (m : ℕ) => x ^ m / ↑(nat.factorial m)) ≤
  abs x ^ n * (↑(Nat.succ n) * (↑(nat.factorial n) * ↑n⁻¹)) := sorry

theorem abs_exp_sub_one_le {x : ℂ} (hx : abs x ≤ 1) : abs (exp x - 1) ≤ bit0 1 * abs x := sorry

theorem abs_exp_sub_one_sub_id_le {x : ℂ} (hx : abs x ≤ 1) : abs (exp x - 1 - x) ≤ abs x ^ bit0 1 := sorry

end complex

namespace real

theorem exp_bound {x : ℝ} (hx : abs x ≤ 1) {n : ℕ} (hn : 0 < n) : abs (exp x - finset.sum (finset.range n) fun (m : ℕ) => x ^ m / ↑(nat.factorial m)) ≤
  abs x ^ n * (↑(Nat.succ n) / (↑(nat.factorial n) * ↑n)) := sorry

/-- A finite initial segment of the exponential series, followed by an arbitrary tail.
For fixed `n` this is just a linear map wrt `r`, and each map is a simple linear function
of the previous (see `exp_near_succ`), with `exp_near n x r ⟶ exp x` as `n ⟶ ∞`,
for any `r`. -/
def exp_near (n : ℕ) (x : ℝ) (r : ℝ) : ℝ :=
  (finset.sum (finset.range n) fun (m : ℕ) => x ^ m / ↑(nat.factorial m)) + x ^ n / ↑(nat.factorial n) * r

@[simp] theorem exp_near_zero (x : ℝ) (r : ℝ) : exp_near 0 x r = r := sorry

@[simp] theorem exp_near_succ (n : ℕ) (x : ℝ) (r : ℝ) : exp_near (n + 1) x r = exp_near n x (1 + x / (↑n + 1) * r) := sorry

theorem exp_near_sub (n : ℕ) (x : ℝ) (r₁ : ℝ) (r₂ : ℝ) : exp_near n x r₁ - exp_near n x r₂ = x ^ n / ↑(nat.factorial n) * (r₁ - r₂) := sorry

theorem exp_approx_end (n : ℕ) (m : ℕ) (x : ℝ) (e₁ : n + 1 = m) (h : abs x ≤ 1) : abs (exp x - exp_near m x 0) ≤ abs x ^ m / ↑(nat.factorial m) * ((↑m + 1) / ↑m) := sorry

theorem exp_approx_succ {n : ℕ} {x : ℝ} {a₁ : ℝ} {b₁ : ℝ} (m : ℕ) (e₁ : n + 1 = m) (a₂ : ℝ) (b₂ : ℝ) (e : abs (1 + x / ↑m * a₂ - a₁) ≤ b₁ - abs x / ↑m * b₂) (h : abs (exp x - exp_near m x a₂) ≤ abs x ^ m / ↑(nat.factorial m) * b₂) : abs (exp x - exp_near n x a₁) ≤ abs x ^ n / ↑(nat.factorial n) * b₁ := sorry

theorem exp_approx_end' {n : ℕ} {x : ℝ} {a : ℝ} {b : ℝ} (m : ℕ) (e₁ : n + 1 = m) (rm : ℝ) (er : ↑m = rm) (h : abs x ≤ 1) (e : abs (1 - a) ≤ b - abs x / rm * ((rm + 1) / rm)) : abs (exp x - exp_near n x a) ≤ abs x ^ n / ↑(nat.factorial n) * b := sorry

theorem exp_1_approx_succ_eq {n : ℕ} {a₁ : ℝ} {b₁ : ℝ} {m : ℕ} (en : n + 1 = m) {rm : ℝ} (er : ↑m = rm) (h : abs (exp 1 - exp_near m 1 ((a₁ - 1) * rm)) ≤ abs 1 ^ m / ↑(nat.factorial m) * (b₁ * rm)) : abs (exp 1 - exp_near n 1 a₁) ≤ abs 1 ^ n / ↑(nat.factorial n) * b₁ := sorry

theorem exp_approx_start (x : ℝ) (a : ℝ) (b : ℝ) (h : abs (exp x - exp_near 0 x a) ≤ abs x ^ 0 / ↑(nat.factorial 0) * b) : abs (exp x - a) ≤ b := sorry

theorem cos_bound {x : ℝ} (hx : abs x ≤ 1) : abs (cos x - (1 - x ^ bit0 1 / bit0 1)) ≤
  abs x ^ bit0 (bit0 1) * (bit1 (bit0 1) / bit0 (bit0 (bit0 (bit0 (bit0 (bit1 1)))))) := sorry

theorem sin_bound {x : ℝ} (hx : abs x ≤ 1) : abs (sin x - (x - x ^ bit1 1 / bit0 (bit1 1))) ≤
  abs x ^ bit0 (bit0 1) * (bit1 (bit0 1) / bit0 (bit0 (bit0 (bit0 (bit0 (bit1 1)))))) := sorry

theorem cos_pos_of_le_one {x : ℝ} (hx : abs x ≤ 1) : 0 < cos x := sorry

theorem sin_pos_of_pos_of_le_one {x : ℝ} (hx0 : 0 < x) (hx : x ≤ 1) : 0 < sin x := sorry

theorem sin_pos_of_pos_of_le_two {x : ℝ} (hx0 : 0 < x) (hx : x ≤ bit0 1) : 0 < sin x := sorry

theorem cos_one_le : cos 1 ≤ bit0 1 / bit1 1 := sorry

theorem cos_one_pos : 0 < cos 1 := sorry

theorem cos_two_neg : cos (bit0 1) < 0 := sorry

end real

namespace complex

theorem abs_cos_add_sin_mul_I (x : ℝ) : abs (cos ↑x + sin ↑x * I) = 1 := sorry

theorem abs_exp_eq_iff_re_eq {x : ℂ} {y : ℂ} : abs (exp x) = abs (exp y) ↔ re x = re y := sorry

@[simp] theorem abs_exp_of_real (x : ℝ) : abs (exp ↑x) = real.exp x :=
  eq.mpr (id (Eq._oldrec (Eq.refl (abs (exp ↑x) = real.exp x)) (Eq.symm (of_real_exp x))))
    (abs_of_nonneg (le_of_lt (real.exp_pos x)))