/-
Copyright (c) 2019 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn
-/
import tactic.core
import data.sum

/-!
# simps attribute

This file defines the `@[simps]` attribute, to automatically generate simp-lemmas
reducing a definition when projections are applied to it.

## Implementation Notes

There are three attributes being defined here
* `@[simps]` is the attribute for objects of a structure or instances of a class. It will
  automatically generate simplification lemmas for each projection of the object/instance that
  contains data. See the doc strings for `simps_attr` and `simps_cfg` for more details and
  configuration options.
* `@[_simps_str]` is automatically added to structures that have been used in `@[simps]` at least
  once. This attribute contains the data of the projections used for this structure by all following
  invocations of `@[simps]`.
* `@[notation_class]` should be added to all classes that define notation, like `has_mul` and
  `has_zero`. This specifies that the projections that `@[simps]` used are the projections from
  these notation classes instead of the projections of the superclasses.
  Example: if `has_mul` is tagged with `@[notation_class]` then the projection used for `semigroup`
  will be `λ α hα, @has_mul.mul α (@semigroup.to_has_mul α hα)` instead of `@semigroup.mul`.

## Tags

structures, projections, simp, simplifier, generates declarations
-/

open tactic expr option sum

setup_tactic_parser
declare_trace simps.verbose
declare_trace simps.debug

/--
The `@[_simps_str]` attribute specifies the preferred projections of the given structure,
used by the `@[simps]` attribute.
- This will usually be tagged by the `@[simps]` tactic.
- You can also generate this with the command `initialize_simps_projections`.
- To change the default value, see Note [custom simps projection].
- You are strongly discouraged to add this attribute manually.
- The first argument is the list of names of the universe variables used in the structure
- The second argument is a list that consists of
  - a custom name for each projection of the structure
  - an expressions for each projections of the structure (definitionally equal to the
    corresponding projection). These expressions can contain the universe parameters specified
    in the first argument).
  - a list of natural numbers, which is the projection number(s) that have to be applied to the
    expression. For example the list `[0, 1]` corresponds to applying the first projection of the
    structure, and then the second projection of the resulting structure (this assumes that the
    target of the first projection is a structure with at least two projections).
    The composition of these projections is required to be definitionally equal to the provided
    expression.
  - A boolean specifying whether simp-lemmas are generated for this projection by default.
-/
@[user_attribute] meta def simps_str_attr :
  user_attribute unit (list name × list (name × expr × list ℕ × bool)) :=
{ name := `_simps_str,
  descr := "An attribute specifying the projection of the given structure.",
  parser := do e ← texpr, eval_pexpr _ e }

/--
  The `@[notation_class]` attribute specifies that this is a notation class,
  and this notation should be used instead of projections by @[simps].
  * The first argument `tt` for notation classes and `ff` for classes applied to the structure,
    like `has_coe_to_sort` and `has_coe_to_fun`
  * The second argument is the name of the projection (by default it is the first projection
    of the structure)
-/
@[user_attribute] meta def notation_class_attr : user_attribute unit (bool × option name) :=
{ name := `notation_class,
  descr := "An attribute specifying that this is a notation class. Used by @[simps].",
  parser := prod.mk <$> (option.is_none <$> (tk "*")?) <*> ident? }

attribute [notation_class] has_zero has_one has_add has_mul has_inv has_neg has_sub has_div has_dvd
  has_mod has_le has_lt has_append has_andthen has_union has_inter has_sdiff has_equiv has_subset
  has_ssubset has_emptyc has_insert has_singleton has_sep has_mem has_pow

attribute [notation_class* coe_sort] has_coe_to_sort
attribute [notation_class* coe_fn] has_coe_to_fun

/-- Returns the projection information of a structure. -/
meta def projections_info (l : list (name × expr × list ℕ × bool)) (pref : string) (str : name) :
  tactic format := do
  ⟨defaults, nondefaults⟩ ← return $ l.partition_map $
    λ s, if s.2.2.2 then inl s else inr s,
  to_print ← defaults.mmap $ λ s, to_string <$> pformat!"Projection {s.1}: {s.2.1}",
  let print2 := string.join $ (nondefaults.map (λ nm : _ × _, to_string nm.1)).intersperse ", ",
  let to_print := to_print ++ if nondefaults = [] then [] else
    ["No lemmas are generated for the projections: " ++ print2 ++ "."],
  let to_print := string.join $ to_print.intersperse "\n        > ",
  return format!"[simps] > {pref} {str}:\n        > {to_print}"

/-- Auxilliary function of `get_composite_of_projections`. -/
meta def get_composite_of_projections_aux : Π (str : name) (proj : string) (x : expr)
  (pos : list ℕ) (args : list expr), tactic (expr × list ℕ) | str proj x pos args := do
  e ← get_env,
  projs ← e.structure_fields str,
  let proj_info := projs.map_with_index $ λ n p, (λ x, (x, n, p)) <$> proj.get_rest ("_" ++ p.last),
  when (proj_info.filter_map id = []) $
    fail!"Failed to find constructor {proj.popn 1} in structure {str}.",
  (proj_rest, index, proj_nm) ← return (proj_info.filter_map id).ilast,
  str_d ← e.get str,
  let proj_e : expr := const (str ++ proj_nm) str_d.univ_levels,
  proj_d ← e.get (str ++ proj_nm),
  type ← infer_type x,
  let params := get_app_args type,
  let univs := proj_d.univ_params.zip type.get_app_fn.univ_levels,
  let new_x := (proj_e.instantiate_univ_params univs).mk_app $ params ++ [x],
  let new_pos := pos ++ [index],
  if proj_rest.is_empty then return (new_x.lambdas args, new_pos) else do
    type ← infer_type new_x,
    (type_args, tgt) ← open_pis_whnf type,
    let new_str := tgt.get_app_fn.const_name,
    get_composite_of_projections_aux new_str proj_rest new_x new_pos (args ++ type_args)

/-- Given a structure `str` and a projection `proj`, that could be multiple nested projections
  (separated by `_`), returns an expression that is the composition of these projections and a
  list of natural numbers, that are the projection numbers of the applied projections. -/
meta def get_composite_of_projections (str : name) (proj : string) : tactic (expr × list ℕ) := do
  e ← get_env,
  str_d ← e.get str,
  let str_e : expr := const str str_d.univ_levels,
  type ← infer_type str_e,
  (type_args, tgt) ← open_pis_whnf type,
  let str_ap := str_e.mk_app type_args,
  x ← mk_local' `x binder_info.default str_ap,
  get_composite_of_projections_aux str ("_" ++ proj) x [] $ type_args ++ [x]

/--
  Get the projections used by `simps` associated to a given structure `str`.

  The returned information is also stored in a parameter of the attribute `@[_simps_str]`, which
  is given to `str`. If `str` already has this attribute, the information is read from this
  attribute instead. See the documentation for this attribute for the data this tactic returns.

  The returned universe levels are the universe levels of the structure. For the projections there
  are three cases
  * If the declaration `{structure_name}.simps.{projection_name}` has been declared, then the value
    of this declaration is used (after checking that it is definitionally equal to the actual
    projection. If you rename the projection name, the declaration should have the *new* projection
    name.
  * You can also declare a custom projection that is a composite of multiple projections.
  * Otherwise, for every class with the `notation_class` attribute, and the structure has an
    instance of that notation class, then the projection of that notation class is used for the
    projection that is definitionally equal to it (if there is such a projection).
    This means in practice that coercions to function types and sorts will be used instead of
    a projection, if this coercion is definitionally equal to a projection. Furthermore, for
    notation classes like `has_mul` and `has_zero` those projections are used instead of the
    corresponding projection.
    Projections for coercions and notation classes are not automatically generated if they are
    composites of multiple projections (for example when you use `extend` without the
    `old_structure_cmd`).
  * Otherwise, the projection of the structure is chosen.
    For example: ``simps_get_raw_projections env `prod`` gives the default projections
```
  ([u, v], [prod.fst.{u v}, prod.snd.{u v}])
```
    while ``simps_get_raw_projections env `equiv`` gives
```
  ([u_1, u_2], [λ α β, coe_fn, λ {α β} (e : α ≃ β), ⇑(e.symm), left_inv, right_inv])
```
    after declaring the coercion from `equiv` to function and adding the declaration
```
  def equiv.simps.inv_fun {α β} (e : α ≃ β) : β → α := e.symm
```

  Optionally, this command accepts three optional arguments:
  * If `trace_if_exists` the command will always generate a trace message when the structure already
    has the attribute `@[_simps_str]`.
  * The `rules` argument accepts a list of pairs `sum.inl (old_name, new_name)`. This is used to
    change the projection name `old_name` to the custom projection name `new_name`. Example:
    for the structure `equiv` the projection `to_fun` could be renamed `apply`. This name will be
    used for parsing and generating projection names. This argument is ignored if the structure
    already has an existing attribute. If an element of `rules` is of the form `sum.inr name`, this
    means that the projection `name` will not be applied by default.
  * if `trc` is true, this tactic will trace information.
-/
-- if performance becomes a problem, possible heuristic: use the names of the projections to
-- skip all classes that don't have the corresponding field.
meta def simps_get_raw_projections (e : environment) (str : name) (trace_if_exists : bool := ff)
  (rules : list (name × name ⊕ name) := []) (trc := ff) :
  tactic (list name × list (name × expr × list ℕ × bool)) := do
  let trc := trc || is_trace_enabled_for `simps.verbose,
  has_attr ← has_attribute' `_simps_str str,
  if has_attr then do
    data ← simps_str_attr.get_param str,
    -- We always print the projections when they already exists and are called by
    -- `initialize_simps_projections`.
    when (trace_if_exists || is_trace_enabled_for `simps.verbose) $ projections_info data.2
      "Already found projection information for structure" str >>= trace,
    return data
  else do
    when trc trace!"[simps] > generating projection information for structure {str}.",
    when_tracing `simps.debug trace!"[simps] > Applying the rules {rules}.",
    d_str ← e.get str,
    let raw_univs := d_str.univ_params,
    let raw_levels := level.param <$> raw_univs,
    /- Figure out projections, including renamings. The information for a projection is (before we
    figure out the `expr` of the projection:
    `(original name, given name, is default)`.
    The first projections are always the actual projections of the structure, but `rules` could
    specify custom projections that are compositions of multiple projections. -/
    projs ← e.structure_fields str,
    let projs := projs.map_with_index (λ n nm, (nm, nm, tt)),
    let projs : list (name × name × bool) := rules.foldl (λ projs rule,
      match rule with
      | (inl (old_nm, new_nm)) := if old_nm ∈ projs.map (λ x, x.2.1) then
         projs.map $ λ proj,
          if proj.2.1 = old_nm then (proj.1, new_nm, proj.2.2) else proj else
        projs ++ [(old_nm, new_nm, tt)]
      | (inr nm) := if nm ∈ projs.map (λ x, x.2.1) then
        projs.map $ λ proj,
          if proj.2.1 = nm then (proj.1, proj.2.1, ff) else proj else
        projs ++ [(nm, nm, ff)]
      end) projs,
    when_tracing `simps.debug trace!"[simps] > Projection info after applying the rules: {projs}.",
    /- Define the raw expressions for the projections, by default as the projections
    (as an expression), but this can be overriden by the user. -/
    raw_exprs_and_nrs ← projs.mmap $ λ ⟨orig_nm, new_nm, _⟩, do {
      (raw_expr, nrs) ← get_composite_of_projections str orig_nm.last,
      custom_proj ← do {
        decl ← e.get (str ++ `simps ++ new_nm.last),
        let custom_proj := decl.value.instantiate_univ_params $ decl.univ_params.zip raw_levels,
        when trc trace!
          "[simps] > found custom projection for {new_nm}:\n        > {custom_proj}",
        return custom_proj } <|> return raw_expr,
      is_def_eq custom_proj raw_expr <|>
        -- if the type of the expression is different, we show a different error message, because
        -- that is more likely going to be helpful.
        do {
          custom_proj_type ← infer_type custom_proj,
          raw_expr_type ← infer_type raw_expr,
          b ← succeeds (is_def_eq custom_proj_type raw_expr_type),
          if b then fail!"Invalid custom projection:\n  {custom_proj}
Expression is not definitionally equal to {raw_expr}."
          else fail!"Invalid custom projection:\n  {custom_proj}
Expression has different type than {str ++ orig_nm}. Given type:\n  {custom_proj_type}
Expected type:\n  {raw_expr_type}" },
      return (custom_proj, nrs) },
    let raw_exprs := raw_exprs_and_nrs.map prod.fst,
    /- Check for other coercions and type-class arguments to use as projections instead. -/
    (args, _) ← open_pis d_str.type,
    let e_str := (expr.const str raw_levels).mk_app args,
    automatic_projs ← attribute.get_instances `notation_class,
    raw_exprs ← automatic_projs.mfoldl (λ (raw_exprs : list expr) class_nm, do {
      (is_class, proj_nm) ← notation_class_attr.get_param class_nm,
      proj_nm ← proj_nm <|> (e.structure_fields_full class_nm).map list.head,
      /- For this class, find the projection. `raw_expr` is the projection found applied to `args`,
        and `lambda_raw_expr` has the arguments `args` abstracted. -/
      (raw_expr, lambda_raw_expr) ← if is_class then (do
        guard $ args.length = 1,
        let e_inst_type := (const class_nm raw_levels).mk_app args,
        (hyp, e_inst) ← try_for 1000 (mk_conditional_instance e_str e_inst_type),
        raw_expr ← mk_mapp proj_nm [args.head, e_inst],
        clear hyp,
        -- Note: `expr.bind_lambda` doesn't give the correct type
        raw_expr_lambda ← lambdas [hyp] raw_expr,
        return (raw_expr, raw_expr_lambda.lambdas args))
      else (do
        e_inst_type ← to_expr ((const class_nm []).app (pexpr.of_expr e_str)),
        e_inst ← try_for 1000 (mk_instance e_inst_type),
        raw_expr ← mk_mapp proj_nm [e_str, e_inst],
        return (raw_expr, raw_expr.lambdas args)),
      raw_expr_whnf ← whnf raw_expr,
      let relevant_proj := raw_expr_whnf.binding_body.get_app_fn.const_name,
      /- Use this as projection, if the function reduces to a projection, and this projection has
        not been overrriden by the user. -/
      guard $ projs.any $
        λ x, x.1 = relevant_proj.last ∧ ¬ e.contains (str ++ `simps ++ x.2.1.last),
      let pos := projs.find_index (λ x, x.1 = relevant_proj.last),
      when trc trace!
        "        > using {proj_nm} instead of the default projection {relevant_proj.last}.",
      when_tracing `simps.debug trace!"[simps] > The raw projection is:\n  {lambda_raw_expr}",
      return $ raw_exprs.update_nth pos lambda_raw_expr } <|> return raw_exprs) raw_exprs,
    let positions := raw_exprs_and_nrs.map prod.snd,
    let defaults := projs.map (λ x, x.2.2),
    let proj_names := projs.map (λ x, x.2.1),
    let projs := proj_names.zip $ raw_exprs.zip $ positions.zip defaults,
    /- make all proof non-default. -/
    projs ← projs.mmap $ λ proj,
      is_proof proj.2.1 >>= λ b, return $ if b then (proj.1, proj.2.1, proj.2.2.1, ff) else proj,
    when trc $ projections_info projs "generated projections for" str >>= trace,
    simps_str_attr.set str (raw_univs, projs) tt,
    when_tracing `simps.debug trace!
      "[simps] > Generated raw projection data: \n{(raw_univs, projs)}",
    return (raw_univs, projs)

/-- Parse a rule for `initialize_simps_projections`. It is either `<name>→<name>` or `-<name>`.-/
meta def simps_parse_rule : parser (name × name ⊕ name) :=
(λ x y, inl (x, y)) <$> ident <*> (tk "->" >> ident) <|> inr <$> (tk "-" >> ident)

/--
  You can specify custom projections for the `@[simps]` attribute.
  To do this for the projection `my_structure.original_projection` by adding a declaration
  `my_structure.simps.my_projection` that is definitionally equal to
  `my_structure.original_projection` but has the projection in the desired (simp-normal) form.
  Then you can call
  ```
  initialize_simps_projections (original_projection → my_projection, ...)
  ```
  to register this projection.

  Running `initialize_simps_projections` without arguments is not necessary, it has the same effect
  if you just add `@[simps]` to a declaration.

  If you do anything to change the default projections, make sure to call either `@[simps]` or
  `initialize_simps_projections` in the same file as the structure declaration. Otherwise, you might
  have a file that imports the structure, but not your custom projections.
-/
library_note "custom simps projection"

/-- Specify simps projections, see Note [custom simps projection].
  * You can specify custom names by writing e.g.
    `initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply)`.
  * You can disable a projection by default by running
    `initialize_simps_projections equiv (-inv_fun)`
    This will ensure that no simp lemmas are generated for this projection,
    unless this projection is explicitly specified by the user.
  * Run `initialize_simps_projections?` (or set `trace.simps.verbose`)
    to see the generated projections. -/
@[user_command] meta def initialize_simps_projections_cmd
  (_ : parse $ tk "initialize_simps_projections") : parser unit := do
  env ← get_env,
  trc ← is_some <$> (tk "?")?,
  ns ← (prod.mk <$> ident <*> (tk "(" >> sep_by (tk ",") simps_parse_rule <* tk ")")?)*,
  ns.mmap' $ λ data, do
    nm ← resolve_constant data.1,
    simps_get_raw_projections env nm tt (data.2.get_or_else []) trc

/--
  Configuration options for the `@[simps]` attribute.
  * `attrs` specifies the list of attributes given to the generated lemmas. Default: ``[`simp]``.
    The attributes can be either basic attributes, or user attributes without parameters.
    There are two attributes which `simps` might add itself:
    * If ``[`simp]`` is in the list, then ``[`_refl_lemma]`` is added automatically if appropriate.
    * If the definition is marked with `@[to_additive ...]` then all generated lemmas are marked
      with `@[to_additive]`
  * `short_name` gives the generated lemmas a shorter name. This only has an effect when multiple
    projections are applied in a lemma. When this is `ff` (default) all projection names will be
    appended to the definition name to form the lemma name, and when this is `tt`, only the
    last projection name will be appended.
  * if `simp_rhs` is `tt` then the right-hand-side of the generated lemmas will be put in
    simp-normal form. More precisely: `dsimp, simp` will be called on all these expressions.
    See note [dsimp, simp].
  * `type_md` specifies how aggressively definitions are unfolded in the type of expressions
    for the purposes of finding out whether the type is a function type.
    Default: `instances`. This will unfold coercion instances (so that a coercion to a function type
    is recognized as a function type), but not declarations like `set`.
  * `rhs_md` specifies how aggressively definition in the declaration are unfolded for the purposes
    of finding out whether it is a constructor.
    Default: `none`
    Exception: `@[simps]` will automatically add the options
    `{rhs_md := semireducible, simp_rhs := tt}` if the given definition is not a constructor with
    the given reducibility setting for `rhs_md`.
  * If `fully_applied` is `ff` then the generated simp-lemmas will be between non-fully applied
    terms, i.e. equalities between functions. This does not restrict the recursive behavior of
    `@[simps]`, so only the "final" projection will be non-fully applied.
    However, it can be used in combination with explicit field names, to get a partially applied
    intermediate projection.
  * The option `not_recursive` contains the list of names of types for which `@[simps]` doesn't
    recursively apply projections. For example, given an equivalence `α × β ≃ β × α` one usually
    wants to only apply the projections for `equiv`, and not also those for `×`. This option is
    only relevant if no explicit projection names are given as argument to `@[simps]`.
  * The option `trace` is set to `tt` when you write `@[simps?]`. In this case, the attribute will
    print all generated lemmas. It is almost the same as setting the option `trace.simps.verbose`,
    except that it doesn't print information about the found projections.
-/
@[derive [has_reflect, inhabited]] structure simps_cfg :=
(attrs         := [`simp])
(short_name    := ff)
(simp_rhs      := ff)
(type_md       := transparency.instances)
(rhs_md        := transparency.none)
(fully_applied := tt)
(not_recursive := [`prod, `pprod])
(trace         := ff)

/-- A common configuration for `@[simps]`: generate equalities between functions instead equalities
  between fully applied expressions. -/
def as_fn : simps_cfg := {fully_applied := ff}
/-- A common configuration for `@[simps]`: don't tag the generated lemmas with `@[simp]`. -/
def lemmas_only : simps_cfg := {attrs := []}

/--
  Get the projections of a structure used by `@[simps]` applied to the appropriate arguments.
  Returns a list of quintuples
  ```
  (projection expression, given projection name, corresponding right-hand-side, projection numbers,
    used by default)
  ```
  one for each projection. The given projection name is the name for the projection used by the user
  used to generate (and parse) projection names. For example, in the structure

  Example 1: ``simps_get_projection_exprs env `(α × β) `(⟨x, y⟩)`` will give the output
  ```
    [(`(@prod.fst.{u v} α β), `fst, `(x), [0], tt),
     (`(@prod.snd.{u v} α β), `snd, `(y), [1], tt)]
  ```

  Example 2: ``simps_get_projection_exprs env `(α ≃ α) `(⟨id, id, λ _, rfl, λ _, rfl⟩)``
  will give the output
  ```
    [(`(@equiv.to_fun.{u u} α α), `apply, `(id), [0], tt),
     (`(@equiv.inv_fun.{u u} α α), `symm_apply, `(id), [1], tt),
     ...,
     ...]
  ```
  The last two fields of the list correspond to the propositional fields of the structure,
  and are rarely/never used.
-/
-- This function does not use `tactic.mk_app` or `tactic.mk_mapp`, because the given arguments
-- might not uniquely specify the universe levels yet.
meta def simps_get_projection_exprs (e : environment) (tgt : expr)
  (rhs : expr) (cfg : simps_cfg) : tactic $ list $ expr × name × expr × list ℕ × bool := do
  let params := get_app_args tgt, -- the parameters of the structure
  (params.zip $ (get_app_args rhs).take params.length).mmap' (λ ⟨a, b⟩, is_def_eq a b)
    <|> fail "unreachable code (1)",
  let str := tgt.get_app_fn.const_name,
  let rhs_args := (get_app_args rhs).drop params.length, -- the fields of the object
  (raw_univs, proj_data) ← simps_get_raw_projections e str ff [] cfg.trace,
  let univs := raw_univs.zip tgt.get_app_fn.univ_levels,
  let projs := proj_data.map prod.fst,
  let raw_exprs := proj_data.map $ λ p, p.2.1,
  let nrs_and_default := proj_data.map $ λ p, p.2.2,
  let proj_exprs := raw_exprs.map $
    λ raw_expr, (raw_expr.instantiate_univ_params univs).instantiate_lambdas_or_apps params,
  let rhs_exprs := nrs_and_default.map $ λ x, rhs_args.inth x.1.head,
  return $ proj_exprs.zip $ projs.zip $ rhs_exprs.zip $ nrs_and_default.map (λ x, (x.1.tail, x.2))

/-- Add a lemma with `nm` stating that `lhs = rhs`. `type` is the type of both `lhs` and `rhs`,
  `args` is the list of local constants occurring, and `univs` is the list of universe variables.
  If `add_simp` then we make the resulting lemma a simp-lemma. -/
meta def simps_add_projection (nm : name) (type lhs rhs : expr) (args : list expr)
  (univs : list name) (cfg : simps_cfg) : tactic unit := do
  when_tracing `simps.debug trace!
    "[simps] > Planning to add the equality\n        > {lhs} = ({rhs} : {type})",
  -- simplify `rhs` if `cfg.simp_rhs` is true
  (rhs, prf) ← do { guard cfg.simp_rhs,
    rhs' ← rhs.dsimp {fail_if_unchanged := ff},
    when_tracing `simps.debug $ when (rhs ≠ rhs') trace!
      "[simps] > `dsimp` simplified rhs to\n        > {rhs'}",
    (rhsprf1, rhsprf2, ns) ← rhs'.simp {fail_if_unchanged := ff},
    when_tracing `simps.debug $ when (rhs' ≠ rhsprf1) trace!
      "[simps] > `simp` simplified rhs to\n        > {rhsprf1}",
    return (prod.mk rhsprf1 rhsprf2) }
    <|> prod.mk rhs <$> mk_app `eq.refl [type, lhs],
  eq_ap ← mk_mapp `eq $ [type, lhs, rhs].map some,
  decl_name ← get_unused_decl_name nm,
  let decl_type := eq_ap.pis args,
  let decl_value := prf.lambdas args,
  let decl := declaration.thm decl_name univs decl_type (pure decl_value),
  when cfg.trace trace!
    "[simps] > adding projection {decl_name}:\n        > {decl_type}",
  decorate_error ("failed to add projection lemma " ++ decl_name.to_string ++ ". Nested error:") $
    add_decl decl,
  b ← succeeds $ is_def_eq lhs rhs,
  when (b ∧ `simp ∈ cfg.attrs) (set_basic_attribute `_refl_lemma decl_name tt),
  cfg.attrs.mmap' $ λ nm, set_attribute nm decl_name tt

/-- Derive lemmas specifying the projections of the declaration.
  If `todo` is non-empty, it will generate exactly the names in `todo`.
  `to_apply` is non-empty after a custom projection that is a composition of multiple projections
  was just used. In that case we need to apply these projections before we continue changing lhs. -/
meta def simps_add_projections : Π (e : environment) (nm : name) (suffix : string)
  (type lhs rhs : expr) (args : list expr) (univs : list name) (must_be_str : bool)
  (cfg : simps_cfg) (todo : list string) (to_apply : list ℕ), tactic unit
| e nm suffix type lhs rhs args univs must_be_str cfg todo to_apply := do
  -- we don't want to unfold non-reducible definitions (like `set`) to apply more arguments
  when_tracing `simps.debug trace!
    "[simps] > Trying to add simp-lemmas for\n        > {lhs}
[simps] > Type of the expression before normalizing: {type}",
  (type_args, tgt) ← open_pis_whnf type cfg.type_md,
  when_tracing `simps.debug trace!"[simps] > Type after removing pi's: {tgt}",
  tgt ← whnf tgt,
  when_tracing `simps.debug trace!"[simps] > Type after reduction: {tgt}",
  let new_args := args ++ type_args,
  let lhs_ap := lhs.mk_app type_args,
  let rhs_ap := rhs.instantiate_lambdas_or_apps type_args,
  let str := tgt.get_app_fn.const_name,
  let new_nm := nm.append_suffix suffix,
  /- We want to generate the current projection if it is in `todo` -/
  let todo_next := todo.filter (≠ ""),
  /- Don't recursively continue if `str` is not a structure or if the structure is in
    `not_recursive`. -/
  if e.is_structure str ∧ ¬(todo = [] ∧ str ∈ cfg.not_recursive ∧ ¬must_be_str) then do
    [intro] ← return $ e.constructors_of str | fail "unreachable code (3)",
    rhs_whnf ← whnf rhs_ap cfg.rhs_md,
    (rhs_ap, todo_now) ← -- `todo_now` means that we still have to generate the current simp lemma
      if h : ¬ is_constant_of rhs_ap.get_app_fn intro ∧
        is_constant_of rhs_whnf.get_app_fn intro then
      /- If this was a desired projection, we want to apply it before taking the whnf.
        However, if the current field is an eta-expansion (see below), we first want
        to eta-reduce it and only then construct the projection.
        This makes the flow of this function messy. -/
      when ("" ∈ todo ∧ to_apply = []) (if cfg.fully_applied then
        simps_add_projection new_nm tgt lhs_ap rhs_ap new_args univs cfg else
        simps_add_projection new_nm type lhs rhs args univs cfg) >>
      return (rhs_whnf, ff) else
      return (rhs_ap, "" ∈ todo ∧ to_apply = []),
    if is_constant_of (get_app_fn rhs_ap) intro then do -- if the value is a constructor application
      tuples ← simps_get_projection_exprs e tgt rhs_ap cfg,
      when_tracing `simps.debug trace!"[simps] > Raw projection information:\n  {tuples}",
      let projs := tuples.map $ λ x, x.2.1,
      eta ← rhs_ap.is_eta_expansion, -- check whether `rhs_ap` is an eta-expansion
      let rhs_ap := eta.lhoare rhs_ap, -- eta-reduce `rhs_ap`
      /- As a special case, we want to automatically generate the current projection if `rhs_ap`
        was an eta-expansion. Also, when this was a desired projection, we need to generate the
        current projection if we haven't done it above. -/
      when (todo_now ∨ (todo = [] ∧ eta.is_some ∧ to_apply = [])) $
        if cfg.fully_applied then
          simps_add_projection new_nm tgt lhs_ap rhs_ap new_args univs cfg else
          simps_add_projection new_nm type lhs rhs args univs cfg,
      /- If we are in the middle of a composite projection. -/
      when (to_apply ≠ []) $ do {
        (proj_expr, proj, new_rhs, proj_nrs, is_default) ← return $ tuples.inth to_apply.head,
        new_type ← infer_type new_rhs,
        simps_add_projections e nm suffix new_type lhs new_rhs new_args univs ff cfg todo
          to_apply.tail },
      /- We stop if no further projection is specified or if we just reduced an eta-expansion and we
      automatically choose projections -/
      when ¬(to_apply ≠ [] ∨ todo = [""] ∨ (eta.is_some ∧ todo = [])) $ do
        let todo := if to_apply = [] then todo_next else todo,
        -- check whether all elements in `todo` have a projection as prefix
        guard (todo.all $ λ x, projs.any $ λ proj, ("_" ++ proj.last).is_prefix_of x) <|>
          let x := (todo.find $ λ x, projs.all $ λ proj, ¬ ("_" ++ proj.last).is_prefix_of x).iget,
          simp_lemma := nm.append_suffix $ suffix ++ x,
          needed_proj := (x.split_on '_').tail.head in
          fail!
"Invalid simp-lemma {simp_lemma}. Structure {str} does not have projection {needed_proj}.
The known projections are:
  {projs}
You can also see this information by running
  `initialize_simps_projections? {str}`.
Note: these projection names might not correspond to the projection names of the structure.",
        tuples.mmap_with_index' $ λ proj_nr ⟨proj_expr, proj, new_rhs, proj_nrs, is_default⟩, do
          new_type ← infer_type new_rhs,
          let new_todo :=
            todo.filter_map $ λ x, x.get_rest ("_" ++ proj.last),
          -- we only continue with this field if it is non-propositional or mentioned in todo
          when ((is_default ∧ todo = []) ∨ new_todo ≠ []) $ do
            let new_lhs := proj_expr.instantiate_lambdas_or_apps [lhs_ap],
            let new_suffix := if cfg.short_name then "_" ++ proj.last else
              suffix ++ "_" ++ proj.last,
            when_tracing `simps.debug trace!"[simps] > Recursively add projections for: {new_lhs}",
            simps_add_projections e nm new_suffix new_type new_lhs new_rhs new_args univs
              ff cfg new_todo proj_nrs
    -- if I'm about to run into an error, try to set the transparency for `rhs_md` higher.
    else if cfg.rhs_md = transparency.none ∧ (must_be_str ∨ todo_next ≠ [] ∨ to_apply ≠ []) then do
      when cfg.trace trace!
        "[simps] > The given definition is not a constructor application:
        >   {rhs_ap}
        > Retrying with the options {{ rhs_md := semireducible, simp_rhs := tt}.",
      simps_add_projections e nm suffix type lhs rhs args univs must_be_str
        { rhs_md := semireducible, simp_rhs := tt, ..cfg} todo to_apply
    else do
      when (to_apply ≠ []) $
        fail!"Invalid simp-lemma {nm}.
The given definition is not a constructor application:\n  {rhs_ap}",
      when must_be_str $
        fail!"Invalid `simps` attribute. The body is not a constructor application:\n  {rhs_ap}",
      when (todo_next ≠ []) $
        fail!"Invalid simp-lemma {nm.append_suffix $ suffix ++ todo_next.head}.
The given definition is not a constructor application:\n  {rhs_ap}",
      if cfg.fully_applied then
        simps_add_projection new_nm tgt lhs_ap rhs_ap new_args univs cfg else
        simps_add_projection new_nm type lhs rhs args univs cfg
  else do
    when must_be_str $
      fail!"Invalid `simps` attribute. Target {str} is not a structure",
    when (todo_next ≠ [] ∧ str ∉ cfg.not_recursive) $
        let first_todo := todo_next.head in
        fail!"Invalid simp-lemma {nm.append_suffix $ suffix ++ first_todo}.
Projection {(first_todo.split_on '_').tail.head} doesn't exist, because target is not a structure.",
    if cfg.fully_applied then
      simps_add_projection new_nm tgt lhs_ap rhs_ap new_args univs cfg else
      simps_add_projection new_nm type lhs rhs args univs cfg

/-- `simps_tac` derives simp-lemmas for all (nested) non-Prop projections of the declaration.
  If `todo` is non-empty, it will generate exactly the names in `todo`.
  If `short_nm` is true, the generated names will only use the last projection name.
  If `trc` is true, trace as if `trace.simps.verbose` is true. -/
meta def simps_tac (nm : name) (cfg : simps_cfg := {}) (todo : list string := []) (trc := ff) :
  tactic unit :=
do
  e ← get_env,
  d ← e.get nm,
  let lhs : expr := const d.to_name (d.univ_params.map level.param),
  let todo := todo.erase_dup.map $ λ proj, "_" ++ proj,
  b ← has_attribute' `to_additive nm,
  let cfg := if b then { attrs := cfg.attrs ++ [`to_additive], ..cfg } else cfg,
  let cfg := { trace := cfg.trace || is_trace_enabled_for `simps.verbose || trc, ..cfg },
  when (cfg.trace ∧ `to_additive ∈ cfg.attrs)
    trace!"[simps] > @[to_additive] will be added to all generated lemmas.",
  simps_add_projections e nm "" d.type lhs d.value [] d.univ_params tt cfg todo []

/-- The parser for the `@[simps]` attribute. -/
meta def simps_parser : parser (bool × list string × simps_cfg) := do
/- note: we don't check whether the user has written a nonsense namespace in an argument. -/
prod.mk <$> is_some <$> (tk "?")? <*>
  (prod.mk <$> many (name.last <$> ident) <*>
  (do some e ← parser.pexpr? | return {}, eval_pexpr simps_cfg e))

/--
The `@[simps]` attribute automatically derives lemmas specifying the projections of this
declaration.

Example:
```lean
@[simps] def foo : ℕ × ℤ := (1, 2)
```
derives two simp-lemmas:
```lean
@[simp] lemma foo_fst : foo.fst = 1
@[simp] lemma foo_snd : foo.snd = 2
```

* It does not derive simp-lemmas for the prop-valued projections.
* It will automatically reduce newly created beta-redexes, but will not unfold any definitions.
* If the structure has a coercion to either sorts or functions, and this is defined to be one
  of the projections, then this coercion will be used instead of the projection.
* If the structure is a class that has an instance to a notation class, like `has_mul`, then this
  notation is used instead of the corresponding projection.
* You can specify custom projections, by giving a declaration with name
  `{structure_name}.simps.{projection_name}`. See Note [custom simps projection].

  Example:
  ```lean
  def equiv.simps.inv_fun (e : α ≃ β) : β → α := e.symm
  @[simps] def equiv.trans (e₁ : α ≃ β) (e₂ : β ≃ γ) : α ≃ γ :=
  ⟨e₂ ∘ e₁, e₁.symm ∘ e₂.symm⟩
  ```
  generates
  ```
  @[simp] lemma equiv.trans_to_fun : ∀ {α β γ} (e₁ e₂) (a : α), ⇑(e₁.trans e₂) a = (⇑e₂ ∘ ⇑e₁) a
  @[simp] lemma equiv.trans_inv_fun : ∀ {α β γ} (e₁ e₂) (a : γ),
    ⇑((e₁.trans e₂).symm) a = (⇑(e₁.symm) ∘ ⇑(e₂.symm)) a
  ```

* You can specify custom projection names, by specifying the new projection names using
  `initialize_simps_projections`.
  Example: `initialize_simps_projections equiv (to_fun → apply, inv_fun → symm_apply)`.

* If one of the fields itself is a structure, this command will recursively create
  simp-lemmas for all fields in that structure.
  * Exception: by default it will not recursively create simp-lemmas for fields in the structures
    `prod` and `pprod`. Give explicit projection names to override this behavior.

  Example:
  ```lean
  structure my_prod (α β : Type*) := (fst : α) (snd : β)
  @[simps] def foo : prod ℕ ℕ × my_prod ℕ ℕ := ⟨⟨1, 2⟩, 3, 4⟩
  ```
  generates
  ```lean
  @[simp] lemma foo_fst : foo.fst = (1, 2)
  @[simp] lemma foo_snd_fst : foo.snd.fst = 3
  @[simp] lemma foo_snd_snd : foo.snd.snd = 4
  ```

* You can use `@[simps proj1 proj2 ...]` to only generate the projection lemmas for the specified
  projections.
* Recursive projection names can be specified using `proj1_proj2_proj3`.
  This will create a lemma of the form `foo.proj1.proj2.proj3 = ...`.

  Example:
  ```lean
  structure my_prod (α β : Type*) := (fst : α) (snd : β)
  @[simps fst fst_fst snd] def foo : prod ℕ ℕ × my_prod ℕ ℕ := ⟨⟨1, 2⟩, 3, 4⟩
  ```
  generates
  ```lean
  @[simp] lemma foo_fst : foo.fst = (1, 2)
  @[simp] lemma foo_fst_fst : foo.fst.fst = 1
  @[simp] lemma foo_snd : foo.snd = {fst := 3, snd := 4}
  ```
* If one of the values is an eta-expanded structure, we will eta-reduce this structure.

  Example:
  ```lean
  structure equiv_plus_data (α β) extends α ≃ β := (data : bool)
  @[simps] def bar {α} : equiv_plus_data α α := { data := tt, ..equiv.refl α }
  ```
  generates the following, even though Lean inserts an eta-expanded version of `equiv.refl α` in the
  definition of `bar`:
  ```lean
  @[simp] lemma bar_to_equiv : ∀ {α : Sort u_1}, bar.to_equiv = equiv.refl α
  @[simp] lemma bar_data : ∀ {α : Sort u_1}, bar.data = tt
  ```
* For configuration options, see the doc string of `simps_cfg`.
* The precise syntax is `('simps' ident* e)`, where `e` is an expression of type `simps_cfg`.
* `@[simps]` reduces let-expressions where necessary.
* If one of the fields is a partially applied constructor, we will eta-expand it
  (this likely never happens).
* When option `trace.simps.verbose` is true, `simps` will print the projections it finds and the
  lemmas it generates. The same can be achieved by using `@[simps?]`, except that in this case it
  will not print projection information.
* Use `@[to_additive, simps]` to apply both `to_additive` and `simps` to a definition, making sure
  that `simps` comes after `to_additive`. This will also generate the additive versions of all
  simp-lemmas. Note however, that the additive versions of the simp-lemmas always use the default
  name generated by `to_additive`, even if a custom name is given for the additive version of the
  definition.
  -/

@[user_attribute] meta def simps_attr : user_attribute unit (bool × list string × simps_cfg) :=
{ name := `simps,
  descr := "Automatically derive lemmas specifying the projections of this declaration.",
  parser := simps_parser,
  after_set := some $
    λ n _ persistent, do
      guard persistent <|> fail "`simps` currently cannot be used as a local attribute",
      (trc, todo, cfg) ← simps_attr.get_param n,
      simps_tac n cfg todo trc }

add_tactic_doc
{ name                     := "simps",
  category                 := doc_category.attr,
  decl_names               := [`simps_attr],
  tags                     := ["simplification"] }


import Std.Tactic.NoMatch

example : False → α := fun.
example : False → α := by intro.
example : ¬ False := fun.
example : ¬ False := by intro.
example : ¬ ¬ 0 = 0 := fun.

example (h : False) : α := match h with.

example (h : Nat → False) : Nat := match h 1 with.

def ComplicatedEmpty : Bool → Type
  | false => Empty
  | true => PEmpty

example (h : ComplicatedEmpty b) : α := match b, h with.
example (h : Nat → ComplicatedEmpty b) : α := match b, h 1 with.


/-
Copyright (c) 2018 Simon Hudon. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Simon Hudon, Sean Leather
-/
import algebra.free_monoid
import algebra.opposites
import control.traversable.instances
import control.traversable.lemmas
import category_theory.category
import category_theory.endomorphism
import category_theory.types
import category_theory.category.Kleisli

/-!

# List folds generalized to `traversable`

Informally, we can think of `foldl` as a special case of `traverse` where we do not care about the
reconstructed data structure and, in a state monad, we care about the final state.

The obvious way to define `foldl` would be to use the state monad but it
is nicer to reason about a more abstract interface with `fold_map` as a
primitive and `fold_map_hom` as a defining property.

```
def fold_map {α ω} [has_one ω] [has_mul ω] (f : α → ω) : t α → ω := ...

lemma fold_map_hom (α β)
  [monoid α] [monoid β] (f : α → β) [is_monoid_hom f]
  (g : γ → α) (x : t γ) :
  f (fold_map g x) = fold_map (f ∘ g) x :=
...
```

`fold_map` uses a monoid ω to accumulate a value for every element of
a data structure and `fold_map_hom` uses a monoid homomorphism to
substitute the monoid used by `fold_map`. The two are sufficient to
define `foldl`, `foldr` and `to_list`. `to_list` permits the
formulation of specifications in terms of operations on lists.

Each fold function can be defined using a specialized
monoid. `to_list` uses a free monoid represented as a list with
concatenation while `foldl` uses endofunctions together with function
composition.

The definition through monoids uses `traverse` together with the
applicative functor `const m` (where `m` is the monoid). As an
implementation, `const` guarantees that no resource is spent on
reconstructing the structure during traversal.

A special class could be defined for `foldable`, similarly to Haskell,
but the author cannot think of instances of `foldable` that are not also
`traversable`.
-/

universes u v

open ulift category_theory opposite

namespace monoid
variables {m : Type u → Type u} [monad m]
variables {α β : Type u}
/--
For a list, foldl f x [y₀,y₁] reduces as follows:

```
calc  foldl f x [y₀,y₁]
    = foldl f (f x y₀) [y₁]      : rfl
... = foldl f (f (f x y₀) y₁) [] : rfl
... = f (f x y₀) y₁              : rfl
```
with
```
f : α → β → α
x : α
[y₀,y₁] : list β
```

We can view the above as a composition of functions:
```
... = f (f x y₀) y₁              : rfl
... = flip f y₁ (flip f y₀ x)    : rfl
... = (flip f y₁ ∘ flip f y₀) x  : rfl
```

We can use traverse and const to construct this composition:
```
calc   const.run (traverse (λ y, const.mk' (flip f y)) [y₀,y₁]) x
     = const.run ((::) <$> const.mk' (flip f y₀) <*> traverse (λ y, const.mk' (flip f y)) [y₁]) x
...  = const.run ((::) <$> const.mk' (flip f y₀) <*>
         ( (::) <$> const.mk' (flip f y₁) <*> traverse (λ y, const.mk' (flip f y)) [] )) x
...  = const.run ((::) <$> const.mk' (flip f y₀) <*>
         ( (::) <$> const.mk' (flip f y₁) <*> pure [] )) x
...  = const.run ( ((::) <$> const.mk' (flip f y₁) <*> pure []) ∘
         ((::) <$> const.mk' (flip f y₀)) ) x
...  = const.run ( const.mk' (flip f y₁) ∘ const.mk' (flip f y₀) ) x
...  = const.run ( flip f y₁ ∘ flip f y₀ ) x
...  = f (f x y₀) y₁
```

And this is how `const` turns a monoid into an applicative functor and
how the monoid of endofunctions define `foldl`.
-/
@[reducible] def foldl (α : Type u) : Type u := (End α)ᵒᵖ
def foldl.mk (f : α → α) : foldl α := op f
def foldl.get (x : foldl α) : α → α := unop x
def foldl.of_free_monoid (f : β → α → β) (xs : free_monoid α) : monoid.foldl β :=
op $ flip (list.foldl f) xs

@[reducible] def foldr (α : Type u) : Type u := End α
def foldr.mk (f : α → α) : foldr α := f
def foldr.get (x : foldr α) : α → α := x
def foldr.of_free_monoid (f : α → β → β) (xs : free_monoid α) : monoid.foldr β :=
flip (list.foldr f) xs

@[reducible] def mfoldl (m : Type u → Type u) [monad m] (α : Type u) : Type u :=
opposite $ End $ Kleisli.mk m α
def mfoldl.mk (f : α → m α) : mfoldl m α := op f
def mfoldl.get (x : mfoldl m α) : α → m α := unop x
def mfoldl.of_free_monoid (f : β → α → m β) (xs : free_monoid α) : monoid.mfoldl m β :=
op $ flip (list.mfoldl f) xs

@[reducible] def mfoldr (m : Type u → Type u) [monad m] (α : Type u) : Type u :=
End $ Kleisli.mk m α
def mfoldr.mk (f : α → m α) : mfoldr m α := f
def mfoldr.get (x : mfoldr m α) : α → m α := x
def mfoldr.of_free_monoid (f : α → β → m β) (xs : free_monoid α) : monoid.mfoldr m β :=
flip (list.mfoldr f) xs

end monoid

namespace traversable
open monoid functor

section defs
variables {α β : Type u} {t : Type u → Type u} [traversable t]

def fold_map {α ω} [has_one ω] [has_mul ω] (f : α → ω) : t α → ω :=
traverse (const.mk' ∘ f)

def foldl (f : α → β → α) (x : α) (xs : t β) : α :=
(fold_map (foldl.mk ∘ flip f) xs).get x

def foldr (f : α → β → β) (x : β) (xs : t α) : β :=
(fold_map (foldr.mk ∘ f) xs).get x

/--
Conceptually, `to_list` collects all the elements of a collection
in a list. This idea is formalized by

  `lemma to_list_spec (x : t α) : to_list x = fold_map free_monoid.mk x`.

The definition of `to_list` is based on `foldl` and `list.cons` for
speed. It is faster than using `fold_map free_monoid.mk` because, by
using `foldl` and `list.cons`, each insertion is done in constant
time. As a consequence, `to_list` performs in linear.

On the other hand, `fold_map free_monoid.mk` creates a singleton list
around each element and concatenates all the resulting lists. In
`xs ++ ys`, concatenation takes a time proportional to `length xs`. Since
the order in which concatenation is evaluated is unspecified, nothing
prevents each element of the traversable to be appended at the end
`xs ++ [x]` which would yield a `O(n²)` run time. -/
def to_list : t α → list α :=
list.reverse ∘ foldl (flip list.cons) []

def length (xs : t α) : ℕ :=
down $ foldl (λ l _, up $ l.down + 1) (up 0) xs

variables {m : Type u → Type u} [monad m]

def mfoldl (f : α → β → m α) (x : α) (xs : t β) : m α :=
(fold_map (mfoldl.mk ∘ flip f) xs).get x

def mfoldr (f : α → β → m β) (x : β) (xs : t α) : m β :=
(fold_map (mfoldr.mk ∘ f) xs).get x

end defs

section applicative_transformation
variables {α β γ : Type u}

open function (hiding const) is_monoid_hom

def map_fold [monoid α] [monoid β] (f : α → β) [is_monoid_hom f] :
  applicative_transformation (const α) (const β) :=
{ app := λ x, f,
  preserves_seq'  := by { intros, simp only [map_mul f, (<*>)], },
  preserves_pure' := by { intros, simp only [map_one f, pure] } }

def free.mk : α → free_monoid α := list.ret

def free.map (f : α → β) : free_monoid α → free_monoid β := list.map f

lemma free.map_eq_map (f : α → β) (xs : list α) :
  f <$> xs = free.map f xs := rfl

instance (f : α → β) : is_monoid_hom (free.map f) :=
{ map_mul := λ x y,
    by simp only [free.map, free_monoid.mul_def, list.map_append, free_add_monoid.add_def],
  map_one := by simp only [free.map, free_monoid.one_def, list.map, free_add_monoid.zero_def] }

instance fold_foldl (f : β → α → β) :
  is_monoid_hom (foldl.of_free_monoid f) :=
{ map_one := rfl,
  map_mul := by intros; simp only [free_monoid.mul_def, foldl.of_free_monoid, flip, unop_op,
    list.foldl_append, op_inj_iff]; refl }

lemma foldl.unop_of_free_monoid  (f : β → α → β) (xs : free_monoid α) (a : β) :
  unop (foldl.of_free_monoid f xs) a = list.foldl f a xs := rfl

instance fold_foldr (f : α → β → β) :
  is_monoid_hom (foldr.of_free_monoid f) :=
{ map_one := rfl,
  map_mul :=
    begin
      intros,
      simp only [free_monoid.mul_def, foldr.of_free_monoid, list.foldr_append, flip],
      refl
    end }

variables (m : Type u → Type u) [monad m] [is_lawful_monad m]

@[simp]
lemma mfoldl.unop_of_free_monoid  (f : β → α → m β) (xs : free_monoid α) (a : β) :
  unop (mfoldl.of_free_monoid f xs) a = list.mfoldl f a xs := rfl

instance fold_mfoldl (f : β → α → m β) :
  is_monoid_hom (mfoldl.of_free_monoid f) :=
{ map_one := rfl,
  map_mul := by intros; apply unop_injective; ext; apply list.mfoldl_append }

instance fold_mfoldr (f : α → β → m β) :
  is_monoid_hom (mfoldr.of_free_monoid f) :=
{ map_one := rfl,
  map_mul := by intros; ext; apply list.mfoldr_append }

variables {t : Type u → Type u} [traversable t] [is_lawful_traversable t]
open is_lawful_traversable

lemma fold_map_hom
  [monoid α] [monoid β] (f : α → β) [is_monoid_hom f]
  (g : γ → α) (x : t γ) :
  f (fold_map g x) = fold_map (f ∘ g) x :=
calc  f (fold_map g x)
    = f (traverse (const.mk' ∘ g) x)  : rfl
... = (map_fold f).app _ (traverse (const.mk' ∘ g) x) : rfl
... = traverse ((map_fold f).app _ ∘ (const.mk' ∘ g)) x : naturality (map_fold f) _ _
... = fold_map (f ∘ g) x : rfl

lemma fold_map_hom_free
  [monoid β] (f : free_monoid α → β) [is_monoid_hom f] (x : t α) :
  f (fold_map free.mk x) = fold_map (f ∘ free.mk) x :=
fold_map_hom _ _ x

variable {m}

lemma fold_mfoldl_cons (f : α → β → m α) (x : β) (y : α) :
  list.mfoldl f y (free.mk x) = f y x :=
by simp only [free.mk, list.ret, list.mfoldl, bind_pure]

lemma fold_mfoldr_cons (f : β → α → m α) (x : β) (y : α) :
  list.mfoldr f y (free.mk x) = f x y :=
by simp only [free.mk, list.ret, list.mfoldr, pure_bind]

end applicative_transformation

section equalities
open is_lawful_traversable list (cons)
variables {α β γ : Type u}
variables {t : Type u → Type u} [traversable t] [is_lawful_traversable t]

@[simp]
lemma foldl.of_free_monoid_comp_free_mk (f : α → β → α) :
  foldl.of_free_monoid f ∘ free.mk = foldl.mk ∘ flip f := rfl

@[simp]
lemma foldr.of_free_monoid_comp_free_mk (f : β → α → α) :
  foldr.of_free_monoid f ∘ free.mk = foldr.mk ∘ f := rfl

@[simp]
lemma mfoldl.of_free_monoid_comp_free_mk {m} [monad m] [is_lawful_monad m] (f : α → β → m α) :
  mfoldl.of_free_monoid f ∘ free.mk = mfoldl.mk ∘ flip f :=
by ext; simp only [(∘), mfoldl.of_free_monoid, mfoldl.mk, flip, fold_mfoldl_cons]; refl

@[simp]
lemma mfoldr.of_free_monoid_comp_free_mk {m} [monad m] [is_lawful_monad m] (f : β → α → m α) :
  mfoldr.of_free_monoid f ∘ free.mk = mfoldr.mk ∘ f :=
by { ext, simp only [(∘), mfoldr.of_free_monoid, mfoldr.mk, flip, fold_mfoldr_cons] }

lemma to_list_spec (xs : t α) :
  to_list xs = (fold_map free.mk xs : free_monoid _) :=
eq.symm $
calc  fold_map free.mk xs
    = (fold_map free.mk xs).reverse.reverse : by simp only [list.reverse_reverse]
... = (list.foldr cons [] (fold_map free.mk xs).reverse).reverse
                 : by simp only [list.foldr_eta]
... = (unop (foldl.of_free_monoid (flip cons) (fold_map free.mk xs)) []).reverse
                 : by simp only [flip,list.foldr_reverse,foldl.of_free_monoid, unop_op]
... = to_list xs : begin
                     rw fold_map_hom_free (foldl.of_free_monoid (flip cons)),
                     simp only [to_list, foldl, list.reverse_inj, foldl.get,
                       foldl.of_free_monoid_comp_free_mk],
                     all_goals { apply_instance }
                   end

lemma fold_map_map [monoid γ]  (f : α → β) (g : β → γ) (xs : t α) :
  fold_map g (f <$> xs) = fold_map (g ∘ f) xs :=
by simp only [fold_map,traverse_map]

lemma foldl_to_list (f : α → β → α) (xs : t β) (x : α) :
  foldl f x xs = list.foldl f x (to_list xs) :=
begin
  rw ← foldl.unop_of_free_monoid,
  simp only [foldl, to_list_spec, fold_map_hom_free (foldl.of_free_monoid f),
    foldl.of_free_monoid_comp_free_mk, foldl.get]
end

lemma foldr_to_list (f : α → β → β) (xs : t α) (x : β) :
  foldr f x xs = list.foldr f x (to_list xs) :=
begin
  change _ = foldr.of_free_monoid _ _ _,
  simp only [foldr, to_list_spec, fold_map_hom_free (foldr.of_free_monoid f),
    foldr.of_free_monoid_comp_free_mk, foldr.get]
end

lemma to_list_map (f : α → β) (xs : t α) :
  to_list (f <$> xs) = f <$> to_list xs :=
by simp only [to_list_spec,free.map_eq_map,fold_map_hom (free.map f), fold_map_map]; refl

@[simp] theorem foldl_map (g : β → γ) (f : α → γ → α) (a : α) (l : t β) :
  foldl f a (g <$> l) = foldl (λ x y, f x (g y)) a l :=
by simp only [foldl, fold_map_map, (∘), flip]

@[simp] theorem foldr_map (g : β → γ) (f : γ → α → α) (a : α) (l : t β) :
  foldr f a (g <$> l) = foldr (f ∘ g) a l :=
by simp only [foldr, fold_map_map, (∘), flip]

@[simp] theorem to_list_eq_self {xs : list α} : to_list xs = xs :=
begin
  simp only [to_list_spec, fold_map, traverse],
  induction xs,
  case list.nil { refl },
  case list.cons : _ _ ih { unfold list.traverse list.ret, rw ih, refl }
end

theorem length_to_list {xs : t α} : length xs = list.length (to_list xs) :=
begin
  unfold length,
  rw foldl_to_list,
  generalize : to_list xs = ys,
  let f := λ (n : ℕ) (a : α), n + 1,
  transitivity list.foldl f 0 ys,
  { generalize : 0 = n,
    induction ys with _ _ ih generalizing n,
    { simp only [list.foldl_nil] },
    { simp only [list.foldl, ih (n+1)] } },
  { induction ys with _ tl ih,
    { simp only [list.length, list.foldl_nil] },
    { simp only [list.foldl, list.length],
      rw [← ih],
      exact tl.foldl_hom (λx, x+1) f f 0 (λ n x, rfl) } }
end

variables {m : Type u → Type u} [monad m] [is_lawful_monad m]

section
local attribute [semireducible] opposite
lemma mfoldl_to_list {f : α → β → m α} {x : α} {xs : t β} :
  mfoldl f x xs = list.mfoldl f x (to_list xs) :=
begin
  change _ = unop (mfoldl.of_free_monoid f (to_list xs)) x,
  simp only [mfoldl, to_list_spec, fold_map_hom_free (mfoldl.of_free_monoid f),
    mfoldl.of_free_monoid_comp_free_mk, mfoldl.get]
end
end

lemma mfoldr_to_list (f : α → β → m β) (x : β) (xs : t α) :
  mfoldr f x xs = list.mfoldr f x (to_list xs) :=
begin
  change _ = mfoldr.of_free_monoid f (to_list xs) x,
  simp only [mfoldr, to_list_spec, fold_map_hom_free (mfoldr.of_free_monoid f),
    mfoldr.of_free_monoid_comp_free_mk, mfoldr.get]
end

@[simp] theorem mfoldl_map (g : β → γ) (f : α → γ → m α) (a : α) (l : t β) :
  mfoldl f a (g <$> l) = mfoldl (λ x y, f x (g y)) a l :=
by simp only [mfoldl, fold_map_map, (∘), flip]

@[simp] theorem mfoldr_map (g : β → γ) (f : γ → α → m α) (a : α) (l : t β) :
  mfoldr f a (g <$> l) = mfoldr (f ∘ g) a l :=
by simp only [mfoldr, fold_map_map, (∘), flip]

end equalities

end traversable


/-
Copyright (c) 2018 Mario Carneiro. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Author: Mario Carneiro
-/
import Mathlib.PrePort
import Mathlib.Lean3Lib.init.default
import Mathlib.computability.primrec
import Mathlib.data.nat.psub
import Mathlib.data.pfun
import Mathlib.PostPort

universes u_1 u_2 u_3 u_4 u_5

namespace Mathlib

/-!
# The partial recursive functions

The partial recursive functions are defined similarly to the primitive
recursive functions, but now all functions are partial, implemented
using the `roption` monad, and there is an additional operation, called
μ-recursion, which performs unbounded minimization.

## References

* [Mario Carneiro, *Formalizing computability theory via partial recursive functions*][carneiro2019]
-/

namespace nat

def rfind_x (p : ℕ →. Bool) (H : ∃ (n : ℕ), tt ∈ p n ∧ ∀ (k : ℕ), k < n → roption.dom (p k)) : Subtype fun (n : ℕ) => tt ∈ p n ∧ ∀ (m : ℕ), m < n → false ∈ p m :=
  (fun
      (this :
      (k : ℕ) → (∀ (n : ℕ), n < k → false ∈ p n) → Subtype fun (n : ℕ) => tt ∈ p n ∧ ∀ (m : ℕ), m < n → false ∈ p m) =>
      this 0 sorry)
    (well_founded.fix (wf_lbp p H)
      fun (m : ℕ)
        (IH :
        (y : ℕ) →
          lbp p y m → (∀ (n : ℕ), n < y → false ∈ p n) → Subtype fun (n : ℕ) => tt ∈ p n ∧ ∀ (m : ℕ), m < n → false ∈ p m)
        (al : ∀ (n : ℕ), n < m → false ∈ p n) =>
        (fun (_x : Bool) (e : roption.get (p m) sorry = _x) =>
            bool.cases_on _x (fun (e : roption.get (p m) sorry = false) => IH (m + 1) sorry sorry)
              (fun (e : roption.get (p m) sorry = tt) => { val := m, property := sorry }) e)
          (roption.get (p m) sorry) sorry)

def rfind (p : ℕ →. Bool) : roption ℕ :=
  roption.mk (∃ (n : ℕ), tt ∈ p n ∧ ∀ (k : ℕ), k < n → roption.dom (p k))
    fun (h : ∃ (n : ℕ), tt ∈ p n ∧ ∀ (k : ℕ), k < n → roption.dom (p k)) => subtype.val (rfind_x p h)

theorem rfind_spec {p : ℕ →. Bool} {n : ℕ} (h : n ∈ rfind p) : tt ∈ p n :=
  Exists.snd h ▸ and.left (subtype.property (rfind_x p (Exists.fst h)))

theorem rfind_min {p : ℕ →. Bool} {n : ℕ} (h : n ∈ rfind p) {m : ℕ} : m < n → false ∈ p m :=
  Exists.snd h ▸ and.right (subtype.property (rfind_x p (Exists.fst h)))

@[simp] theorem rfind_dom {p : ℕ →. Bool} : roption.dom (rfind p) ↔ ∃ (n : ℕ), tt ∈ p n ∧ ∀ {m : ℕ}, m < n → roption.dom (p m) :=
  iff.rfl

theorem rfind_dom' {p : ℕ →. Bool} : roption.dom (rfind p) ↔ ∃ (n : ℕ), tt ∈ p n ∧ ∀ {m : ℕ}, m ≤ n → roption.dom (p m) := sorry

@[simp] theorem mem_rfind {p : ℕ →. Bool} {n : ℕ} : n ∈ rfind p ↔ tt ∈ p n ∧ ∀ {m : ℕ}, m < n → false ∈ p m := sorry

theorem rfind_min' {p : ℕ → Bool} {m : ℕ} (pm : ↥(p m)) : ∃ (n : ℕ), ∃ (H : n ∈ rfind ↑p), n ≤ m := sorry

theorem rfind_zero_none (p : ℕ →. Bool) (p0 : p 0 = roption.none) : rfind p = roption.none := sorry

def rfind_opt {α : Type u_1} (f : ℕ → Option α) : roption α :=
  roption.bind (rfind ↑fun (n : ℕ) => option.is_some (f n)) fun (n : ℕ) => ↑(f n)

theorem rfind_opt_spec {α : Type u_1} {f : ℕ → Option α} {a : α} (h : a ∈ rfind_opt f) : ∃ (n : ℕ), a ∈ f n := sorry

theorem rfind_opt_dom {α : Type u_1} {f : ℕ → Option α} : roption.dom (rfind_opt f) ↔ ∃ (n : ℕ), ∃ (a : α), a ∈ f n := sorry

theorem rfind_opt_mono {α : Type u_1} {f : ℕ → Option α} (H : ∀ {a : α} {m n : ℕ}, m ≤ n → a ∈ f m → a ∈ f n) {a : α} : a ∈ rfind_opt f ↔ ∃ (n : ℕ), a ∈ f n := sorry

inductive partrec : (ℕ →. ℕ) → Prop
where
| zero : partrec (pure 0)
| succ : partrec ↑Nat.succ
| left : partrec ↑fun (n : ℕ) => prod.fst (unpair n)
| right : partrec ↑fun (n : ℕ) => prod.snd (unpair n)
| pair : ∀ {f g : ℕ →. ℕ}, partrec f → partrec g → partrec fun (n : ℕ) => mkpair <$> f n <*> g n
| comp : ∀ {f g : ℕ →. ℕ}, partrec f → partrec g → partrec fun (n : ℕ) => g n >>= f
| prec : ∀ {f g : ℕ →. ℕ},
  partrec f →
    partrec g →
      partrec
        (unpaired
          fun (a n : ℕ) =>
            elim (f a)
              (fun (y : ℕ) (IH : roption ℕ) =>
                do
                  let i ← IH
                  g (mkpair a (mkpair y i)))
              n)
| rfind : ∀ {f : ℕ →. ℕ},
  partrec f → partrec fun (a : ℕ) => rfind fun (n : ℕ) => (fun (m : ℕ) => to_bool (m = 0)) <$> f (mkpair a n)

namespace partrec

theorem of_eq {f : ℕ →. ℕ} {g : ℕ →. ℕ} (hf : partrec f) (H : ∀ (n : ℕ), f n = g n) : partrec g :=
  funext H ▸ hf

theorem of_eq_tot {f : ℕ →. ℕ} {g : ℕ → ℕ} (hf : partrec f) (H : ∀ (n : ℕ), g n ∈ f n) : partrec ↑g :=
  of_eq hf fun (n : ℕ) => iff.mpr roption.eq_some_iff (H n)

theorem of_primrec {f : ℕ → ℕ} (hf : primrec f) : partrec ↑f := sorry

protected theorem some : partrec roption.some :=
  of_primrec primrec.id

theorem none : partrec fun (n : ℕ) => roption.none := sorry

theorem prec' {f : ℕ →. ℕ} {g : ℕ →. ℕ} {h : ℕ →. ℕ} (hf : partrec f) (hg : partrec g) (hh : partrec h) : partrec
  fun (a : ℕ) =>
    roption.bind (f a)
      fun (n : ℕ) =>
        elim (g a)
          (fun (y : ℕ) (IH : roption ℕ) =>
            do
              let i ← IH
              h (mkpair a (mkpair y i)))
          n := sorry

theorem ppred : partrec fun (n : ℕ) => ↑(ppred n) := sorry

end partrec

end nat

def partrec {α : Type u_1} {σ : Type u_2} [primcodable α] [primcodable σ] (f : α →. σ) :=
  nat.partrec fun (n : ℕ) => roption.bind ↑(encodable.decode α n) fun (a : α) => roption.map encodable.encode (f a)

def partrec₂ {α : Type u_1} {β : Type u_2} {σ : Type u_3} [primcodable α] [primcodable β] [primcodable σ] (f : α → β →. σ) :=
  partrec fun (p : α × β) => f (prod.fst p) (prod.snd p)

def computable {α : Type u_1} {σ : Type u_2} [primcodable α] [primcodable σ] (f : α → σ) :=
  partrec ↑f

def computable₂ {α : Type u_1} {β : Type u_2} {σ : Type u_3} [primcodable α] [primcodable β] [primcodable σ] (f : α → β → σ) :=
  computable fun (p : α × β) => f (prod.fst p) (prod.snd p)

theorem primrec.to_comp {α : Type u_1} {σ : Type u_2} [primcodable α] [primcodable σ] {f : α → σ} (hf : primrec f) : computable f := sorry

theorem primrec₂.to_comp {α : Type u_1} {β : Type u_2} {σ : Type u_3} [primcodable α] [primcodable β] [primcodable σ] {f : α → β → σ} (hf : primrec₂ f) : computable₂ f :=
  primrec.to_comp hf

theorem computable.part {α : Type u_1} {σ : Type u_2} [primcodable α] [primcodable σ] {f : α → σ} (hf : computable f) : partrec ↑f :=
  hf

theorem computable₂.part {α : Type u_1} {β : Type u_2} {σ : Type u_3} [primcodable α] [primcodable β] [primcodable σ] {f : α → β → σ} (hf : computable₂ f) : partrec₂ fun (a : α) => ↑(f a) :=
  hf

namespace computable

theorem of_eq {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α → σ} {g : α → σ} (hf : computable f) (H : ∀ (n : α), f n = g n) : computable g :=
  funext H ▸ hf

theorem const {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] (s : σ) : computable fun (a : α) => s :=
  primrec.to_comp (primrec.const s)

theorem of_option {α : Type u_1} {β : Type u_2} [primcodable α] [primcodable β] {f : α → Option β} (hf : computable f) : partrec fun (a : α) => ↑(f a) := sorry

theorem to₂ {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : α × β → σ} (hf : computable f) : computable₂ fun (a : α) (b : β) => f (a, b) :=
  of_eq hf
    fun (_x : α × β) =>
      (fun (_a : α × β) => prod.cases_on _a fun (fst : α) (snd : β) => idRhs (f (fst, snd) = f (fst, snd)) rfl) _x

protected theorem id {α : Type u_1} [primcodable α] : computable id :=
  primrec.to_comp primrec.id

theorem fst {α : Type u_1} {β : Type u_2} [primcodable α] [primcodable β] : computable prod.fst :=
  primrec.to_comp primrec.fst

theorem snd {α : Type u_1} {β : Type u_2} [primcodable α] [primcodable β] : computable prod.snd :=
  primrec.to_comp primrec.snd

theorem pair {α : Type u_1} {β : Type u_2} {γ : Type u_3} [primcodable α] [primcodable β] [primcodable γ] {f : α → β} {g : α → γ} (hf : computable f) (hg : computable g) : computable fun (a : α) => (f a, g a) := sorry

theorem unpair : computable nat.unpair :=
  primrec.to_comp primrec.unpair

theorem succ : computable Nat.succ :=
  primrec.to_comp primrec.succ

theorem pred : computable Nat.pred :=
  primrec.to_comp primrec.pred

theorem nat_bodd : computable nat.bodd :=
  primrec.to_comp primrec.nat_bodd

theorem nat_div2 : computable nat.div2 :=
  primrec.to_comp primrec.nat_div2

theorem sum_inl {α : Type u_1} {β : Type u_2} [primcodable α] [primcodable β] : computable sum.inl :=
  primrec.to_comp primrec.sum_inl

theorem sum_inr {α : Type u_1} {β : Type u_2} [primcodable α] [primcodable β] : computable sum.inr :=
  primrec.to_comp primrec.sum_inr

theorem list_cons {α : Type u_1} [primcodable α] : computable₂ List.cons :=
  primrec₂.to_comp primrec.list_cons

theorem list_reverse {α : Type u_1} [primcodable α] : computable list.reverse :=
  primrec.to_comp primrec.list_reverse

theorem list_nth {α : Type u_1} [primcodable α] : computable₂ list.nth :=
  primrec₂.to_comp primrec.list_nth

theorem list_append {α : Type u_1} [primcodable α] : computable₂ append :=
  primrec₂.to_comp primrec.list_append

theorem list_concat {α : Type u_1} [primcodable α] : computable₂ fun (l : List α) (a : α) => l ++ [a] :=
  primrec₂.to_comp primrec.list_concat

theorem list_length {α : Type u_1} [primcodable α] : computable list.length :=
  primrec.to_comp primrec.list_length

theorem vector_cons {α : Type u_1} [primcodable α] {n : ℕ} : computable₂ vector.cons :=
  primrec₂.to_comp primrec.vector_cons

theorem vector_to_list {α : Type u_1} [primcodable α] {n : ℕ} : computable vector.to_list :=
  primrec.to_comp primrec.vector_to_list

theorem vector_length {α : Type u_1} [primcodable α] {n : ℕ} : computable vector.length :=
  primrec.to_comp primrec.vector_length

theorem vector_head {α : Type u_1} [primcodable α] {n : ℕ} : computable vector.head :=
  primrec.to_comp primrec.vector_head

theorem vector_tail {α : Type u_1} [primcodable α] {n : ℕ} : computable vector.tail :=
  primrec.to_comp primrec.vector_tail

theorem vector_nth {α : Type u_1} [primcodable α] {n : ℕ} : computable₂ vector.nth :=
  primrec₂.to_comp primrec.vector_nth

theorem vector_nth' {α : Type u_1} [primcodable α] {n : ℕ} : computable vector.nth :=
  primrec.to_comp primrec.vector_nth'

theorem vector_of_fn' {α : Type u_1} [primcodable α] {n : ℕ} : computable vector.of_fn :=
  primrec.to_comp primrec.vector_of_fn'

theorem fin_app {σ : Type u_4} [primcodable σ] {n : ℕ} : computable₂ id :=
  primrec₂.to_comp primrec.fin_app

protected theorem encode {α : Type u_1} [primcodable α] : computable encodable.encode :=
  primrec.to_comp primrec.encode

protected theorem decode {α : Type u_1} [primcodable α] : computable (encodable.decode α) :=
  primrec.to_comp primrec.decode

protected theorem of_nat (α : Type u_1) [denumerable α] : computable (denumerable.of_nat α) :=
  primrec.to_comp (primrec.of_nat α)

theorem encode_iff {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α → σ} : (computable fun (a : α) => encodable.encode (f a)) ↔ computable f :=
  iff.rfl

theorem option_some {α : Type u_1} [primcodable α] : computable some :=
  primrec.to_comp primrec.option_some

end computable

namespace partrec

theorem of_eq {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α →. σ} {g : α →. σ} (hf : partrec f) (H : ∀ (n : α), f n = g n) : partrec g :=
  funext H ▸ hf

theorem of_eq_tot {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α →. σ} {g : α → σ} (hf : partrec f) (H : ∀ (n : α), g n ∈ f n) : computable g :=
  of_eq hf fun (a : α) => iff.mpr roption.eq_some_iff (H a)

theorem none {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] : partrec fun (a : α) => roption.none := sorry

protected theorem some {α : Type u_1} [primcodable α] : partrec roption.some :=
  computable.id

theorem const' {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] (s : roption σ) : partrec fun (a : α) => s :=
  of_eq (computable.of_option (computable.const (roption.to_option s))) fun (a : α) => roption.of_to_option s

protected theorem bind {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : α →. β} {g : α → β →. σ} (hf : partrec f) (hg : partrec₂ g) : partrec fun (a : α) => roption.bind (f a) (g a) := sorry

theorem map {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : α →. β} {g : α → β → σ} (hf : partrec f) (hg : computable₂ g) : partrec fun (a : α) => roption.map (g a) (f a) := sorry

theorem to₂ {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : α × β →. σ} (hf : partrec f) : partrec₂ fun (a : α) (b : β) => f (a, b) :=
  of_eq hf
    fun (_x : α × β) =>
      (fun (_a : α × β) => prod.cases_on _a fun (fst : α) (snd : β) => idRhs (f (fst, snd) = f (fst, snd)) rfl) _x

theorem nat_elim {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α → ℕ} {g : α →. σ} {h : α → ℕ × σ →. σ} (hf : computable f) (hg : partrec g) (hh : partrec₂ h) : partrec fun (a : α) => nat.elim (g a) (fun (y : ℕ) (IH : roption σ) => roption.bind IH fun (i : σ) => h a (y, i)) (f a) := sorry

theorem comp {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : β →. σ} {g : α → β} (hf : partrec f) (hg : computable g) : partrec fun (a : α) => f (g a) := sorry

theorem nat_iff {f : ℕ →. ℕ} : partrec f ↔ nat.partrec f := sorry

theorem map_encode_iff {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α →. σ} : (partrec fun (a : α) => roption.map encodable.encode (f a)) ↔ partrec f :=
  iff.rfl

end partrec

namespace partrec₂

theorem unpaired {α : Type u_1} [primcodable α] {f : ℕ → ℕ →. α} : partrec (nat.unpaired f) ↔ partrec₂ f := sorry

theorem unpaired' {f : ℕ → ℕ →. ℕ} : nat.partrec (nat.unpaired f) ↔ partrec₂ f :=
  iff.trans (iff.symm partrec.nat_iff) unpaired

theorem comp {α : Type u_1} {β : Type u_2} {γ : Type u_3} {σ : Type u_5} [primcodable α] [primcodable β] [primcodable γ] [primcodable σ] {f : β → γ →. σ} {g : α → β} {h : α → γ} (hf : partrec₂ f) (hg : computable g) (hh : computable h) : partrec fun (a : α) => f (g a) (h a) :=
  partrec.comp hf (computable.pair hg hh)

theorem comp₂ {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} {σ : Type u_5} [primcodable α] [primcodable β] [primcodable γ] [primcodable δ] [primcodable σ] {f : γ → δ →. σ} {g : α → β → γ} {h : α → β → δ} (hf : partrec₂ f) (hg : computable₂ g) (hh : computable₂ h) : partrec₂ fun (a : α) (b : β) => f (g a b) (h a b) :=
  comp hf hg hh

end partrec₂

namespace computable

theorem comp {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : β → σ} {g : α → β} (hf : computable f) (hg : computable g) : computable fun (a : α) => f (g a) :=
  partrec.comp hf hg

theorem comp₂ {α : Type u_1} {β : Type u_2} {γ : Type u_3} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable γ] [primcodable σ] {f : γ → σ} {g : α → β → γ} (hf : computable f) (hg : computable₂ g) : computable₂ fun (a : α) (b : β) => f (g a b) :=
  comp hf hg

end computable

namespace computable₂

theorem comp {α : Type u_1} {β : Type u_2} {γ : Type u_3} {σ : Type u_5} [primcodable α] [primcodable β] [primcodable γ] [primcodable σ] {f : β → γ → σ} {g : α → β} {h : α → γ} (hf : computable₂ f) (hg : computable g) (hh : computable h) : computable fun (a : α) => f (g a) (h a) :=
  computable.comp hf (computable.pair hg hh)

theorem comp₂ {α : Type u_1} {β : Type u_2} {γ : Type u_3} {δ : Type u_4} {σ : Type u_5} [primcodable α] [primcodable β] [primcodable γ] [primcodable δ] [primcodable σ] {f : γ → δ → σ} {g : α → β → γ} {h : α → β → δ} (hf : computable₂ f) (hg : computable₂ g) (hh : computable₂ h) : computable₂ fun (a : α) (b : β) => f (g a b) (h a b) :=
  comp hf hg hh

end computable₂

namespace partrec

theorem rfind {α : Type u_1} [primcodable α] {p : α → ℕ →. Bool} (hp : partrec₂ p) : partrec fun (a : α) => nat.rfind (p a) := sorry

theorem rfind_opt {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α → ℕ → Option σ} (hf : computable₂ f) : partrec fun (a : α) => nat.rfind_opt (f a) :=
  partrec.bind (rfind (to₂ (computable.part (computable.comp (primrec.to_comp primrec.option_is_some) hf))))
    (computable.of_option hf)

theorem nat_cases_right {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α → ℕ} {g : α → σ} {h : α → ℕ →. σ} (hf : computable f) (hg : computable g) (hh : partrec₂ h) : partrec fun (a : α) => nat.cases (roption.some (g a)) (h a) (f a) := sorry

theorem bind_decode2_iff {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α →. σ} : partrec f ↔
  nat.partrec fun (n : ℕ) => roption.bind ↑(encodable.decode2 α n) fun (a : α) => roption.map encodable.encode (f a) := sorry

theorem vector_m_of_fn {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {n : ℕ} {f : fin n → α →. σ} : (∀ (i : fin n), partrec (f i)) → partrec fun (a : α) => vector.m_of_fn fun (i : fin n) => f i a := sorry

end partrec

@[simp] theorem vector.m_of_fn_roption_some {α : Type u_1} {n : ℕ} (f : fin n → α) : (vector.m_of_fn fun (i : fin n) => roption.some (f i)) = roption.some (vector.of_fn f) :=
  vector.m_of_fn_pure

namespace computable

theorem option_some_iff {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α → σ} : (computable fun (a : α) => some (f a)) ↔ computable f := sorry

theorem bind_decode_iff {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : α → β → Option σ} : (computable₂ fun (a : α) (n : ℕ) => option.bind (encodable.decode β n) (f a)) ↔ computable₂ f := sorry

theorem map_decode_iff {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : α → β → σ} : (computable₂ fun (a : α) (n : ℕ) => option.map (f a) (encodable.decode β n)) ↔ computable₂ f :=
  iff.trans bind_decode_iff option_some_iff

theorem nat_elim {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α → ℕ} {g : α → σ} {h : α → ℕ × σ → σ} (hf : computable f) (hg : computable g) (hh : computable₂ h) : computable fun (a : α) => nat.elim (g a) (fun (y : ℕ) (IH : σ) => h a (y, IH)) (f a) := sorry

theorem nat_cases {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α → ℕ} {g : α → σ} {h : α → ℕ → σ} (hf : computable f) (hg : computable g) (hh : computable₂ h) : computable fun (a : α) => nat.cases (g a) (h a) (f a) :=
  nat_elim hf hg (to₂ (computable₂.comp hh fst (comp fst snd)))

theorem cond {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {c : α → Bool} {f : α → σ} {g : α → σ} (hc : computable c) (hf : computable f) (hg : computable g) : computable fun (a : α) => cond (c a) (f a) (g a) := sorry

theorem option_cases {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {o : α → Option β} {f : α → σ} {g : α → β → σ} (ho : computable o) (hf : computable f) (hg : computable₂ g) : computable fun (a : α) => option.cases_on (o a) (f a) (g a) := sorry

theorem option_bind {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : α → Option β} {g : α → β → Option σ} (hf : computable f) (hg : computable₂ g) : computable fun (a : α) => option.bind (f a) (g a) := sorry

theorem option_map {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {f : α → Option β} {g : α → β → σ} (hf : computable f) (hg : computable₂ g) : computable fun (a : α) => option.map (g a) (f a) :=
  option_bind hf (comp₂ option_some hg)

theorem option_get_or_else {α : Type u_1} {β : Type u_2} [primcodable α] [primcodable β] {f : α → Option β} {g : α → β} (hf : computable f) (hg : computable g) : computable fun (a : α) => option.get_or_else (f a) (g a) := sorry

theorem subtype_mk {α : Type u_1} {β : Type u_2} [primcodable α] [primcodable β] {f : α → β} {p : β → Prop} [decidable_pred p] {h : ∀ (a : α), p (f a)} (hp : primrec_pred p) (hf : computable f) : computable fun (a : α) => { val := f a, property := h a } := sorry

theorem sum_cases {α : Type u_1} {β : Type u_2} {γ : Type u_3} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable γ] [primcodable σ] {f : α → β ⊕ γ} {g : α → β → σ} {h : α → γ → σ} (hf : computable f) (hg : computable₂ g) (hh : computable₂ h) : computable fun (a : α) => sum.cases_on (f a) (g a) (h a) := sorry

theorem nat_strong_rec {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] (f : α → ℕ → σ) {g : α → List σ → Option σ} (hg : computable₂ g) (H : ∀ (a : α) (n : ℕ), g a (list.map (f a) (list.range n)) = some (f a n)) : computable₂ f := sorry

theorem list_of_fn {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {n : ℕ} {f : fin n → α → σ} : (∀ (i : fin n), computable (f i)) → computable fun (a : α) => list.of_fn fun (i : fin n) => f i a := sorry

theorem vector_of_fn {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {n : ℕ} {f : fin n → α → σ} (hf : ∀ (i : fin n), computable (f i)) : computable fun (a : α) => vector.of_fn fun (i : fin n) => f i a := sorry

end computable

namespace partrec

theorem option_some_iff {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α →. σ} : (partrec fun (a : α) => roption.map some (f a)) ↔ partrec f := sorry

theorem option_cases_right {α : Type u_1} {β : Type u_2} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable σ] {o : α → Option β} {f : α → σ} {g : α → β →. σ} (ho : computable o) (hf : computable f) (hg : partrec₂ g) : partrec fun (a : α) => option.cases_on (o a) (roption.some (f a)) (g a) := sorry

theorem sum_cases_right {α : Type u_1} {β : Type u_2} {γ : Type u_3} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable γ] [primcodable σ] {f : α → β ⊕ γ} {g : α → β → σ} {h : α → γ →. σ} (hf : computable f) (hg : computable₂ g) (hh : partrec₂ h) : partrec fun (a : α) => sum.cases_on (f a) (fun (b : β) => roption.some (g a b)) (h a) := sorry

theorem sum_cases_left {α : Type u_1} {β : Type u_2} {γ : Type u_3} {σ : Type u_4} [primcodable α] [primcodable β] [primcodable γ] [primcodable σ] {f : α → β ⊕ γ} {g : α → β →. σ} {h : α → γ → σ} (hf : computable f) (hg : partrec₂ g) (hh : computable₂ h) : partrec fun (a : α) => sum.cases_on (f a) (g a) fun (c : γ) => roption.some (h a c) := sorry

theorem fix {α : Type u_1} {σ : Type u_4} [primcodable α] [primcodable σ] {f : α →. σ ⊕ α} (hf : partrec f) : partrec (pfun.fix f) := sorry


import polyfix
import polysize
import data.list.basic

section foldl
variables {α γ : Type*}

def tails_foldl_step (f : α → list γ → α) (x : list γ × α) : bool × (list γ × α) :=
(x.1.empty, x.1.tail, f x.2 x.1)

variables (f : α → list γ → α) (x : list γ × α)
lemma tails_foldl_step_iter :
  iterator_evaln (tails_foldl_step f) (2 + x.1.length) (ff, x) = some ([], x.1.tails.foldl f x.2) :=
begin
  cases x with l x,
  induction l with hd tl ih generalizing x,
  { simp [tails_foldl_step], }, { simp [tails_foldl_step, ← add_assoc, ih], },
end

variables {f}
lemma tails_foldl_states_suffix {s₀ s : bool × list γ × α} (hs : s ∈ (mk_iterator ↑(tails_foldl_step f) s₀).states) :
  s.2.1 <:+ s₀.2.1 :=
begin
  rcases s₀ with ⟨b₀, l₀, s₀⟩,
  apply execution.step_induction hs; clear hs s,
  { simp, },
  rintros ⟨b, l, s⟩ ⟨b', l', s'⟩ _, simp,
  rintros ih rfl, simp [tails_foldl_step],
  rintros rfl rfl rfl,
  exact l.tail_suffix.trans ih,
end

end foldl

section
open ptree.pencodable (encode decode)
variables {α β γ : Type*} [polycodable α] [polycodable β] [polycodable γ]
  {f : β → α → list γ → α} {l : β → list γ} (acc : β → α)

lemma acc_list_polysize_fun_uniform (hl : polysize_fun l) :
  polysize_fun_uniform (λ (s : β) (y : (mk_iterator ↑(tails_foldl_step (f s)) (ff, l s, acc s)).states), (y : bool × list γ × α).2.1) :=
begin
  rcases hl with ⟨pi, hpi⟩, use pi, intros s x,
  refine trans (encode_sizeof_le_of_infix _) (hpi _),
  exact (tails_foldl_states_suffix (subtype.mem x)).is_infix,
end

lemma tails_foldl_polysize_safe (hinit : polysize_fun l)
  (hf : polysize_fun_safe (λ (s : β × list γ) (x : α), f s.1 x s.2)) :
  polysize_fun_safe (λ (s : β) (y : (mk_iterator ↑(tails_foldl_step (f s)) (ff, l s, acc s)).states), tails_foldl_step (f s) (y : bool × list γ × α).2) :=
begin
  dsimp only [tails_foldl_step] at *,
  apply polysize_fun_safe.pair_left, { apply polysize_uniform_of_fin_range, },
  apply polysize_fun_safe.pair_left,
  { apply polysize_fun_uniform.comp' (polysize_fun_safe.tail _),
    apply acc_list_polysize_fun_uniform _ hinit, },
  change polysize_fun_safe (λ x y, (λ (s : β × list γ) (x : α), f s.1 x s.2) (x, (↑y : bool × list γ × α).2.1) (↑y : bool × list γ × α).2.2),
  apply polysize_fun_safe.comp hf,
  { apply polysize_fun_uniform.pair, { simpa using polysize_fun.id, }, apply acc_list_polysize_fun_uniform _ hinit, },
  refine polysize_fun_safe.comp' (polysize_fun_safe.snd _) _, refine polysize_fun_safe.comp' (polysize_fun_safe.snd _) _,
  use 0, intros, simp [penc_states_encode],
end

end

section list

variables {α β γ : Type*} [polycodable α] [polycodable β] [polycodable γ]
open ptree.pencodable (encode decode)

variable (H_enc : polytime_fun (λ l : ptree, ptree.equiv_list.symm $ l.equiv_list.map (encode ∘ (@decode γ _))))

def inh : inhabited γ := ⟨decode ptree.nil⟩
local attribute [instance] inh

include H_enc
-- List encoding aux; used very frequently, so better have a short name
def lea : polycodable (list γ) :=
{ polytime_decode := by simpa [encode, decode] using H_enc }

lemma polytime_fun.head_aux : polytime_fun (@list.head γ _) :=
by { letI := lea H_enc, rw polytime_fun_iff', use [code.left, polytime_left], intro x, cases x, { simp, refl, }, simp [ptree.pencodable.encode_list_def], }

lemma polytime_fun.tail_aux : polytime_fun (@list.tail γ) :=
⟨code.right, polytime_right,
by { intro x, cases x; simp [encode, decode], }⟩

lemma polytime_fun.cons_aux : polytime_fun₂ (@list.cons γ) :=
⟨code.id, polytime_id,
by { intro x, cases x; simp [encode], }⟩

lemma polytime_fun.is_empty_aux : polytime_fun (@list.empty γ) :=
by { letI := lea H_enc, classical, convert_to polytime_fun (λ l : list γ, (l = [] : bool)), { ext x, cases x; simp, }, polyfun, }

lemma polytime_fun.foldl_tails_aux {f : β → α → list γ → α} {l : β → list γ} {acc : β → α}
  (hf : polytime_fun₃ f) (hl : polytime_fun l) (hacc : polytime_fun acc)
  (hs : polysize_fun_safe (λ (a : β × list γ) (b : α), f a.1 b a.2)) :
  polytime_fun (λ s, (l s).tails.foldl (f s) (acc s)) :=
begin
  letI := lea H_enc,
  suffices : polytime_fun (λ s, (@list.nil γ, (l s).tails.foldl (f s) (acc s))), { exact polytime_fun.snd.comp this, },
  apply polytime_fun.evaln_of_polysize (λ (s : β) (x : list γ × α), tails_foldl_step (f s) x) (λ x, (ff, l x, acc x)) (λ s, 2 + (l s).length),
  { dunfold tails_foldl_step, have := polytime_fun.tail_aux H_enc, have := polytime_fun.is_empty_aux H_enc, polyfun, },
  { polyfun, }, rotate 1,
  { obtain ⟨q, hq⟩ := polysize_of_polytime_fun hl, use 2 + q, intros s, simpa using (len_le_encode_sizeof _).trans (hq _), },
  { intro s, simpa using tails_foldl_step_iter (f s) (l s, acc s), },
  exact tails_foldl_polysize_safe _ (polysize_of_polytime_fun hl) hs,
end

lemma polytime_fun.foldl_aux {f : β → α → γ → α} {l : β → list γ} {acc : β → α}
  (hf : polytime_fun₃ f) (hl : polytime_fun l) (hacc : polytime_fun acc)
  (hs : polysize_fun_safe (λ (a : β × γ) (b : α), f a.1 b a.2)) :
  polytime_fun (λ s, (l s).foldl (f s) (acc s)) :=
begin
  letI := lea H_enc,
  convert_to polytime_fun (λ s, (l s).tails.foldl (λ (a : α) (ls : list γ), if ls.empty then a else f s a ls.head) (acc s)),
  { ext s, generalize : acc s = a, induction l s with hd tl ih generalizing a, { simp, }, simp [ih], },
  apply polytime_fun.foldl_tails_aux H_enc,
  { have := polytime_fun.head_aux H_enc, have := polytime_fun.is_empty_aux H_enc, polyfun, },
  { exact hl, }, { exact hacc, },
  apply polysize_fun_safe.ite, { apply polysize_fun_safe.id, },
  change polysize_fun_safe (λ (x : β × list γ) (y : α), (λ (a : β × γ) (b : α), f a.1 b a.2) (x.1, x.2.head) y),
  apply hs.comp, { have := polytime_fun.head_aux H_enc, simp, apply polysize_of_polytime_fun, polyfun, }, { exact polysize_fun_safe.id, },
end

lemma polytime_fun.reverse_aux : polytime_fun (@list.reverse γ) :=
begin
  letI := lea H_enc,
  convert_to polytime_fun (λ l : list γ, l.foldl (λ acc hd, hd :: acc) []),
  { ext l : 1, rw ← list.foldr_reverse, simp, },
  apply polytime_fun.foldl_aux H_enc, { have := polytime_fun.cons_aux H_enc, polyfun, }, { polyfun, }, { polyfun, },
  apply polysize_fun_safe.comp polysize_fun_safe.cons, { simp, apply polysize_of_polytime_fun, polyfun, }, exact polysize_fun_safe.id,
end

lemma polytime_fun.foldr_aux {f : β → γ → α → α} {l : β → list γ} {acc : β → α}
  (hf : polytime_fun₃ f) (hl : polytime_fun l) (hacc : polytime_fun acc)
  (hs : polysize_fun_safe (λ (a : β × γ) (b : α), f a.1 a.2 b)) :
  polytime_fun (λ s, (l s).foldr (f s) (acc s)) :=
begin
  letI := lea H_enc,
  simp only [← list.foldl_reverse],
  apply polytime_fun.foldl_aux H_enc, { polyfun, }, { have := polytime_fun.reverse_aux H_enc, polyfun, }, { polyfun, },
  exact hs,
end

lemma polytime_fun.map_aux
  (H_enc' : polytime_fun (λ l : ptree, ptree.equiv_list.symm $ l.equiv_list.map (encode ∘ (@decode α _))))
  {f : β → γ → α} {l : β → list γ} (hf : polytime_fun₂ f) (hl : polytime_fun l) :
  polytime_fun (λ s, (l s).map (f s)) :=
begin
  letI := lea H_enc, letI := lea H_enc',
  convert_to polytime_fun (λ s, (l s).foldr (λ hd acc, (f s hd) :: acc) []),
  { ext s : 1, induction l s with hd tl ih, { simp, }, simp [ih], },
  apply polytime_fun.foldr_aux H_enc,
  { have := polytime_fun.cons_aux H_enc', polyfun, }, { exact hl, }, { polyfun, },
  apply polysize_fun_safe.comp polysize_fun_safe.cons, { simp, apply polysize_of_polytime_fun, exact hf, }, { exact polysize_fun_safe.id, },
end

end list

section list_ptree
open ptree.pencodable (encode decode)

def list_ptree_encode : polycodable (list ptree) :=
lea ⟨code.id, polytime_id, by simp [encode, decode]⟩

local attribute [instance] list_ptree_encode

@[polyfun]
lemma polytime_fun.equiv_list : polytime_fun ptree.equiv_list :=
⟨_, polytime_id, by simp [encode]⟩

@[polyfun]
lemma polytime_fun.equiv_list_symm : polytime_fun ptree.equiv_list.symm :=
⟨_, polytime_id, by simp [encode]⟩

lemma H_enc {γ : Type*} [polycodable γ] :
  polytime_fun (λ l : ptree, ptree.equiv_list.symm $ l.equiv_list.map (encode ∘ (@decode γ _))) :=
begin
  refine polytime_fun.equiv_list_symm.comp (polytime_fun.comp _ polytime_fun.equiv_list),
  apply polytime_fun.map_aux, iterate 2 { simpa using polytime_fun.id, },
  { /- TODO: Debug -/ dunfold polytime_fun₂ function.uncurry, apply polytime_fun.comp; polyfun, }, polyfun,
end

end list_ptree

section list
open ptree.pencodable (encode decode)

variables {α β γ : Type*} [polycodable α] [polycodable β] [polycodable γ]
instance : polycodable (list α) :=
lea H_enc

lemma polytime_fun.head : polytime_fun (@list.head γ ⟨decode ptree.nil⟩) :=
polytime_fun.head_aux H_enc

local attribute [polyfun] polytime_fun.head

@[polyfun]
lemma polytime_fun.tail : polytime_fun (@list.tail γ) :=
polytime_fun.tail_aux H_enc

@[polyfun]
lemma polytime_fun.cons : polytime_fun₂ (@list.cons γ) :=
polytime_fun.cons_aux H_enc

@[polyfun]
lemma polytime_fun.is_empty : polytime_fun (@list.empty γ) :=
polytime_fun.is_empty_aux H_enc

@[polyfun]
lemma polytime_fun.head' : polytime_fun (@list.head' γ) :=
begin
  convert_to polytime_fun (λ l : list γ, if l.empty then none else some (@list.head _ ⟨decode ptree.nil⟩ l)),
  { ext l : 1, cases l; simp, }, polyfun,
end

@[polyfun]
lemma polytime_fun.ihead [inhabited γ] : polytime_fun (@list.head γ _) :=
begin
  convert_to polytime_fun (λ l : list γ, l.head'.iget),
  { ext l, cases l; simp, }, polyfun,
end

local attribute [-polyfun] polytime_fun.head

lemma polytime_fun.foldl_tails {f : β → α → list γ → α} {l : β → list γ} {acc : β → α}
  (hf : polytime_fun₃ f) (hl : polytime_fun l) (hacc : polytime_fun acc)
  (hs : polysize_fun_safe (λ (a : β × list γ) (b : α), f a.1 b a.2)) :
  polytime_fun (λ s, (l s).tails.foldl (f s) (acc s)) := polytime_fun.foldl_tails_aux H_enc hf hl hacc hs

@[polyfun]
lemma polytime_fun.foldl {f : β → α → γ → α} {l : β → list γ} {acc : β → α}
  (hf : polytime_fun₃ f) (hl : polytime_fun l) (hacc : polytime_fun acc)
  (hs : polysize_fun_safe (λ (a : β × γ) (b : α), f a.1 b a.2)) :
  polytime_fun (λ s, (l s).foldl (f s) (acc s)) := polytime_fun.foldl_aux H_enc hf hl hacc hs

@[polyfun]
lemma polytime_fun.reverse : polytime_fun (@list.reverse γ) := polytime_fun.reverse_aux H_enc

@[polyfun]
lemma polytime_fun.foldr {f : β → γ → α → α} {l : β → list γ} {acc : β → α}
  (hf : polytime_fun₃ f) (hl : polytime_fun l) (hacc : polytime_fun acc)
  (hs : polysize_fun_safe (λ (a : β × γ) (b : α), f a.1 a.2 b)) :
  polytime_fun (λ s, (l s).foldr (f s) (acc s)) := polytime_fun.foldr_aux H_enc hf hl hacc hs

@[polyfun]
lemma polytime_fun.map
  {f : β → γ → α} {l : β → list γ} (hf : polytime_fun₂ f) (hl : polytime_fun l) :
  polytime_fun (λ s, (l s).map (f s)) := polytime_fun.map_aux H_enc H_enc hf hl

@[polyfun]
lemma polytime_fun.filter {f : β → γ → bool} {l : β → list γ} (hf : polytime_fun₂ f) (hl : polytime_fun l) :
  polytime_fun (λ s, (l s).filter (λ x, f s x)) :=
begin
  convert_to polytime_fun (λ s, (l s).foldr (λ hd acc, if f s hd then hd :: acc else acc) []),
  { ext s : 1, induction l s with hd tl ih, { simp, }, cases H : f s hd; simpa [H], },
  polyfun, apply polysize_fun_safe.ite, { refine polysize_fun_safe.comp polysize_fun_safe.cons _ polysize_fun_safe.id, simp, exact polysize_of_polytime_fun polytime_fun.snd, }, exact polysize_fun_safe.id,
end

@[polyfun]
lemma polytime_fun.append : polytime_fun₂ (λ (l₁ l₂ : list α), l₁ ++ l₂) :=
begin
  convert_to polytime_fun₂ (λ (l₁ l₂ : list α), l₁.foldr list.cons l₂),
  { ext l₁ l₂ : 2, induction l₁ with hd tl ih, { simp, }, { simp [ih], } },
  polyfun, apply polysize_fun_safe.cons.comp, { simp, apply polysize_of_polytime_fun polytime_fun.snd, }, exact polysize_fun_safe.id,
end

@[polyfun]
lemma polytime_fun.tails : polytime_fun (@list.tails α) :=
begin
  convert_to polytime_fun (λ l : list α, (l.tails.foldl (λ acc x, x :: acc) []).reverse),
  { simp, }, apply polytime_fun.comp, { polyfun, }, apply polytime_fun.foldl_tails,
  { polyfun, }, { polyfun, }, { polyfun, }, apply polysize_fun_safe.cons.comp, { simp, apply polysize_of_polytime_fun polytime_fun.snd, }, exact polysize_fun_safe.id,
end

def unary_nat_encode : polycodable ℕ :=
polycodable.of_equiv equiv.list_unit_equiv.symm

local attribute [instance] unary_nat_encode

lemma unary_nat_encode_eq (n : ℕ) : encode n = encode (list.repeat () n) := rfl

@[polyfun]
lemma polytime_fun.unary_length : polytime_fun (@list.length α) :=
begin
  convert_to polytime_fun (λ l : list α, equiv.list_unit_equiv (l.map (λ _, ()))),
  { ext l, simp [equiv.list_unit_equiv], }, polyfun, exact polycodable.of_equiv_polytime_symm _,
end

@[polyfun]
lemma polytime_fun.unary_repeat : polytime_fun₂ (@list.repeat α) :=
begin
  convert_to polytime_fun₂ (λ (x : α) (n : ℕ), (equiv.list_unit_equiv.symm n).map (λ _, x)),
  { ext : 2, simp [equiv.list_unit_equiv], }, polyfun,
end

@[polyfun]
lemma polytime_fun.unary_nat_add : polytime_fun₂ ((+) : ℕ → ℕ → ℕ) :=
begin
  convert_to polytime_fun₂ (λ n m, (list.repeat () n ++ list.repeat () m).length),
  { ext, simp, }, polyfun,
end

lemma _root_.list.drop_succ {α : Type*} (l : list α) (n : ℕ) :
  l.drop (n+1) = l.tail.drop n :=
by { induction l; simp, }

@[polyfun]
lemma polytime_fun.drop : polytime_fun₂ (@list.drop α) :=
begin
  convert_to polytime_fun₂ (λ (n : ℕ) (l : list α), (list.repeat () n).foldr (λ _ (l' : list α), l'.tail) l),
  { ext n l : 2, rw list.foldr_const, simp, induction n generalizing l; simp [list.drop_succ, *], },
  polyfun, exact polysize_fun_safe.tail _,
end

@[polyfun]
lemma polytime_fun.nth : polytime_fun₂ (@list.nth α) :=
begin
  convert_to polytime_fun₂ (λ (l : list α) n, (l.drop n).head'),
  { ext l n : 2, simp [← list.nth_zero, list.nth_drop], },
  polyfun,
end

@[polyfun]
lemma polytime_fun.tsub : polytime_fun₂ (@has_sub.sub ℕ _) :=
begin
  convert_to polytime_fun₂ (λ (m n : ℕ), ((list.repeat () m).drop n).length),
  { ext m n, simp, }, polyfun,
end

@[polyfun]
lemma polytime_fun.last : polytime_fun (@list.last' α) :=
by { convert_to polytime_fun (λ l : list α, l.reverse.head'), { ext l : 1, induction l using list.reverse_rec_on; simp, }, polyfun, }

@[polyfun]
lemma polytime_fun.filter_map {f : β → α → option γ} {l : β → list α}
  (hf : polytime_fun₂ f) (hl : polytime_fun l) :
  polytime_fun (λ s, (l s).filter_map (f s)) :=
begin
  inhabit γ,
  convert_to polytime_fun (λ s, (((l s).map (λ x, f s x)).filter (λ x : option γ, x.is_some)).map option.iget),
  { ext s : 1, induction l s with hd tl ih, { simp, }, cases H : f s hd, { simpa [H], }, { simpa [list.filter_map_cons_some _ _ _ H, H], } },
  polyfun,
end

@[simp] lemma _root_.list.filter_map_none (l : list α) :
  l.filter_map (λ _, (none : option β)) = [] :=
by { induction l; simp [*], }

lemma zip_eq_inits_map [inhabited α] (l₁ : list α) (l₂ : list β) :
  l₁.zip l₂ = l₁.inits.tail.filter_map (λ x, (l₂.nth (x.length - 1)).map (λ y, (x.last'.iget, y))) :=
begin
  induction l₁ with hd tl ih generalizing l₂,
  { simp, },
  cases l₂ with h₂ t₂, { simp, },
  simp [list.filter_map_map, function.comp],
  cases tl, { simp, rw list.filter_map_cons_some, { simp, }, simp, },
  simp, rw list.filter_map_cons_some, swap, { simp, },
  rw ih t₂, simp [list.filter_map_map, function.comp],
end

@[polyfun]
lemma polytime_fun.inits : polytime_fun (@list.inits α) :=
by { convert_to polytime_fun (λ l : list α, (l.reverse.tails.map list.reverse).reverse), { simp [list.tails_reverse], }, polyfun, }

@[polyfun]
lemma polytime_fun.zip : polytime_fun₂ (@list.zip α β) :=
begin
  inhabit α,
  change polytime_fun₂ (λ l₁ l₂, list.zip l₁ l₂), simp_rw zip_eq_inits_map, polyfun,
end

@[polyfun]
lemma polytime_fun.all {f : β → α → bool} {l : β → list α}
  (hf : polytime_fun₂ f) (hl : polytime_fun l) : polytime_fun (λ s, (l s).all (f s)) :=
by { dunfold list.all, polyfun, exact (polysize_uniform_of_fin_range _).to_safe, }

@[polyfun]
lemma polytime_fun.unary_nat_le {f g : β → ℕ} (hf : polytime_fun f) (hg : polytime_fun g) :
  polytime_fun (λ s, ((f s) ≤ (g s) : bool)) :=
by { convert_to polytime_fun (λ s, ((f s) - (g s) = 0 : bool)), { simp, }, polyfun, }

@[polyfun]
lemma polytime_fun.list_join : polytime_fun (@list.join α) :=
begin
  convert_to polytime_fun (λ l : list (list α), l.foldr (++) []),
  { ext l : 1, induction l; simp [*],}, polyfun, apply polysize_fun_safe.append.comp,
  { simp, apply polysize_of_polytime_fun, polyfun, }, { exact polysize_fun_safe.id, }
end

end list

section examples

def list_product {α β} (l₁ : list α) (l₂ : list β) : list (α × β) :=
(l₁.map (λ x, l₂.map (λ y, (x, y)))).join

example {α β : Type*} [polycodable α] [polycodable β] :
  polytime_fun₂ (λ (l₁ : list α) (l₂ : list β), list_product l₁ l₂) :=
by { dunfold list_product, polyfun, }

end examples


/-
  Copyright (c) 2022 Arthur Paulino. All rights reserved.
  Released under Apache 2.0 license as described in the file LICENSE.
  Authors: Arthur Paulino
-/

import FxyLang.Reasoning.Defs

theorem State.stepNComp : (s^[n₁])^[n₂] = s^[n₁ + n₂] := by
  induction n₁ generalizing s with
  | zero => simp [stepN]
  | succ n hi =>
    rw [stepN, @hi s.step]
    have : n.succ + n₂ = (n + n₂).succ := by
      simp only [Nat.add_comm, Nat.add_assoc, Nat.add_left_comm]; rfl
    rw [this, stepN]

theorem State.extendsForward {s : State} (hs : s.extends s₀) :
    s.step.extends s₀ := by
  sorry

theorem State.reachDeterministic' (h : ret c₁ k v₁ ↠ ret c₂ k v₂) :
    v₁ = v₂ ∧ c₁ = c₂ := by
  sorry

theorem State.reachDeterministic (h₁ : s ↠ ret c₁ k v₁) (h₂ : s ↠ ret c₂ k v₂) :
    v₁ = v₂ ∧ c₁ = c₂ := by
  sorry

theorem Determinism (h₁ : ⟦c, p⟧ » ⟦c₁, v₁⟧) (h₂ : ⟦c, p⟧ » ⟦c₂, v₂⟧) :
  v₁ = v₂ ∧ c₁ = c₂ := State.reachDeterministic (h₁ default) (h₂ default)