/-
Copyright (c) 2020 Jannis Limperg. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Jannis Limperg
-/
import meta.rb_map
import tactic.core

/-!
# Tactics About Dependencies

This module provides tactics to compute dependencies and reverse dependencies of
hypotheses. An expression `e` depends on a hypothesis `h` if `e` would not be
valid if `h` were removed from the context. For example, the expression
`e := x > 0` depends on `x`. We say that `x` is a dependency of `e` and that `e`
is a reverse dependency of `x`.

It is sometimes useful to consider *inclusive* dependency: `e` inclusively
depends on `h` iff `e` depends on `h` or `e = h` (so inclusive dependency is the
reflexive closure of regular dependency).

Note that the standard library does not use quite the same terminology:

* `kdependencies`/`kdeps` from the standard library compute reverse
  dependencies, not dependencies.
* `kdepends_on` and functions derived from it ignore local definitions and
  therefore compute a weaker dependency relation (see next section).

## Local Definitions

Determining dependencies of hypotheses is usually straightforward: a hypothesis
`r : R` depends on another hypothesis `d : D` if `d` occurs in `R`. The
implementation is more involved, however, in the presence of local definitions.
Consider this context:

```lean
n m : ℕ
k : ℕ := m
o : ℕ := k
h : o > 0
```

`h` depends on `o`, `k` and `m`, but only the dependency on `o` is syntactically
obvious. `kdepends_on` ignores this complication and claims that `h` does not
depend on `k` or `m`. We do not follow this example but process local
definitions properly. This means that if the context contains a local
definition, we need to compute the syntactic dependencies of `h`, then their
dependencies, and so on.

## Direct Dependencies

If you want to ignore local definitions while computing dependencies, this
module also provides tactics to find the *direct* dependencies of a hypothesis.
These are the hypotheses that syntactically appear in the hypothesis's type (or
value, if the hypothesis is a local definition).
-/

open native
open expr_set (local_set_to_name_set)
open name_set (local_list_to_name_set)

namespace tactic

/-! ### Direct Dependencies -/

/-! #### Checking whether hypotheses directly depend on each other -/

/--
`type_has_local_in_name_set h ns` returns true iff the type of `h` contains a
local constant whose unique name appears in `ns`.
-/
meta def type_has_local_in_name_set (h : expr) (ns : name_set) : tactic bool := do
  h_type ← infer_type h,
  pure $ h_type.has_local_in ns

/--
`type_has_local_in_set h hs` returns true iff the type of `h` contains any of
the local constants `hs`.
-/
meta def type_has_local_in_set (h : expr) (hs : expr_set) : tactic bool :=
type_has_local_in_name_set h $ local_set_to_name_set hs

/--
`type_has_local_in h hs` returns true iff the type of `h` contains any of the
local constants `hs`.
-/
meta def type_has_local_in (h : expr) (hs : list expr) : tactic bool :=
type_has_local_in_name_set h $ local_list_to_name_set hs

/--
`local_def_value_has_local_in_name_set h ns` returns true iff `h` is a local
definition whose value contains a local constant whose unique name appears in
`ns`.
-/
meta def local_def_value_has_local_in_name_set (h : expr) (ns : name_set) :
  tactic bool := do
  (some h_val) ← try_core $ local_def_value h | pure ff,
  pure $ h_val.has_local_in ns

/--
`local_def_value_has_local_in_set h hs` returns true iff `h` is a local
definition whose value contains any of the local constants `hs`.
-/
meta def local_def_value_has_local_in_set (h : expr) (hs : expr_set) :
  tactic bool :=
local_def_value_has_local_in_name_set h $ local_set_to_name_set hs

/--
`local_def_value_has_local_in h hs` returns true iff `h` is a local definition
whose value contains any of the local constants `hs`.
-/
meta def local_def_value_has_local_in (h : expr) (hs : list expr) :
  tactic bool :=
local_def_value_has_local_in_name_set h $ local_list_to_name_set hs

/--
`hyp_directly_depends_on_local_name_set h ns` is true iff the hypothesis `h`
directly depends on a hypothesis whose unique name appears in `ns`.
-/
meta def hyp_directly_depends_on_local_name_set (h : expr) (ns : name_set) :
  tactic bool :=
list.mbor
  [ type_has_local_in_name_set h ns,
    local_def_value_has_local_in_name_set h ns ]

/--
`hyp_directly_depends_on_local_set h hs` is true iff the hypothesis `h` directly
depends on any of the hypotheses `hs`.
-/
meta def hyp_directly_depends_on_local_set (h : expr) (hs : expr_set) :
  tactic bool :=
hyp_directly_depends_on_local_name_set h $ local_set_to_name_set hs

/--
`hyp_directly_depends_on_locals h hs` is true iff the hypothesis `h` directly
depends on any of the hypotheses `hs`.
-/
meta def hyp_directly_depends_on_locals (h : expr) (hs : list expr) :
  tactic bool :=
hyp_directly_depends_on_local_name_set h $ local_list_to_name_set hs

/--
`hyp_directly_depends_on_local_name_set_inclusive h ns` is true iff the
hypothesis `h` directly depends on a hypothesis whose unique name appears in
`ns` or `h`'s name appears in `ns`.
-/
meta def hyp_directly_depends_on_local_name_set_inclusive (h : expr)
  (ns : name_set) : tactic bool :=
list.mbor
  [ pure $ ns.contains h.local_uniq_name
  , hyp_directly_depends_on_local_name_set h ns ]

/--
`hyp_directly_depends_on_local_set_inclusive h ns` is true iff the hypothesis `h`
directly depends on any of the hypotheses `hs` or `h` appears in `hs`.
-/
meta def hyp_directly_depends_on_local_set_inclusive (h : expr) (hs : expr_set) :
  tactic bool :=
hyp_directly_depends_on_local_name_set_inclusive h $ local_set_to_name_set hs

/--
`hyp_directly_depends_on_locals_inclusive h ns` is true iff the hypothesis `h`
directly depends on any of the hypotheses `hs` or `h` appears in `hs`.
-/
meta def hyp_directly_depends_on_locals_inclusive (h : expr) (hs : list expr) :
  tactic bool :=
hyp_directly_depends_on_local_name_set_inclusive h $ local_list_to_name_set hs

/-! #### Computing the direct dependencies of a hypothesis -/

/--
`direct_dependency_set_of_hyp h` is the set of hypotheses that the hypothesis
`h` directly depends on. These are the hypotheses that appear in `h`'s type or
value (if `h` is a local definition).
-/
meta def direct_dependency_set_of_hyp (h : expr) : tactic expr_set := do
  t ← infer_type h,
  let deps := t.list_local_consts',
  (some val) ← try_core $ local_def_value h | pure deps,
  let deps := deps.union val.list_local_consts',
  pure deps

/--
`direct_dependency_name_set_of_hyp h` is the set of unique names of hypotheses
that the hypothesis `h` directly depends on. These are the hypotheses that
appear in `h`'s type or value (if `h` is a local definition).
-/
meta def direct_dependency_name_set_of_hyp (h : expr) : tactic name_set :=
local_set_to_name_set <$> direct_dependency_set_of_hyp h

/--
`direct_dependencies_of_hyp h` is the list of hypotheses that the hypothesis `h`
directly depends on. These are the hypotheses that appear in `h`'s type or value
(if `h` is a local definition). The dependencies are returned in no particular
order.
-/
meta def direct_dependencies_of_hyp (h : expr) : tactic (list expr) :=
rb_set.to_list <$> direct_dependency_set_of_hyp h

/--
`direct_dependency_set_of_hyp_inclusive h` is the set of hypotheses that the
hypothesis `h` directly depends on, plus `h` itself.
-/
meta def direct_dependency_set_of_hyp_inclusive (h : expr) : tactic expr_set := do
  deps ← direct_dependency_set_of_hyp h,
  pure $ deps.insert h

/--
`direct_dependency_name_set_of_hyp_inclusive h` is the set of unique names of
hypotheses that the hypothesis `h` directly depends on, plus `h` itself.
-/
meta def direct_dependency_name_set_of_hyp_inclusive (h : expr) :
  tactic name_set :=
local_set_to_name_set <$> direct_dependency_set_of_hyp_inclusive h

/--
`direct_dependencies_of_hyp_inclusive h` is the list of hypotheses that the
hypothesis `h` directly depends on, plus `h` itself. The dependencies are
returned in no particular order.
-/
meta def direct_dependencies_of_hyp_inclusive (h : expr) : tactic (list expr) :=
rb_set.to_list <$> direct_dependency_set_of_hyp_inclusive h

/-! ### Indirect/Transitive Dependencies -/

/-! #### Checking whether hypotheses depend on each other -/

/--
`hyp_depends_on_local_name_set' cache h ns` is true iff `h` depends on any of
the hypotheses whose unique names appear in `ns`. `cache` must be a set of
hypotheses known *not* to depend (even indirectly) on any of the `ns`. This is
a performance optimisation, so you can give an empty cache. The tactic also
returns an expanded cache with hypotheses which the tactic has encountered.

You probably want to use `tactic.hyp_depends_on_local_name_set` or
`tactic.hyps_depend_on_local_name_set` instead of this tactic.
-/
meta def hyp_depends_on_local_name_set' : expr_set → expr → name_set →
  tactic (bool × expr_set) := λ cache h ns, do
  ff ← pure $ cache.contains h | pure (ff, cache),
  direct_deps ← direct_dependency_set_of_hyp h,
  let has_dep := direct_deps.fold ff (λ d b, b || ns.contains d.local_uniq_name),
  ff ← pure has_dep | pure (tt, cache),
  (has_dep, cache) ← direct_deps.mfold (ff, cache) $ λ d ⟨b, cache⟩,
    if b
      then pure (tt, cache)
      else hyp_depends_on_local_name_set' cache d ns,
  if has_dep
    then pure (tt, cache)
    else pure (ff, cache.insert h)

/--
`hyp_depends_on_local_name_set h ns` is true iff the hypothesis `h` depends on
any of the hypotheses whose unique names appear in `ns`. If you need to check
dependencies of multiple hypotheses, use `tactic.hyps_depend_on_local_name_set`.
-/
meta def hyp_depends_on_local_name_set (h : expr) (ns : name_set) : tactic bool := do
  ctx_has_local_def ← context_upto_hyp_has_local_def h,
  if ctx_has_local_def
    then prod.fst <$> hyp_depends_on_local_name_set' mk_expr_set h ns
    else hyp_directly_depends_on_local_name_set h ns

/--
`hyp_depends_on_local_set h hs` is true iff the hypothesis `h` depends on
any of the hypotheses `hs`. If you need to check dependencies of multiple
hypotheses, use `tactic.hyps_depend_on_local_set`.
-/
meta def hyp_depends_on_local_set (h : expr) (hs : expr_set) : tactic bool :=
hyp_depends_on_local_name_set h $ local_set_to_name_set hs

/--
`hyp_depends_on_locals h hs` is true iff the hypothesis `h` depends on any of
the hypotheses `hs`. If you need to check dependencies of multiple hypotheses,
use `tactic.hyps_depend_on_locals`.
-/
meta def hyp_depends_on_locals (h : expr) (hs : list expr) : tactic bool :=
hyp_depends_on_local_name_set h $ local_list_to_name_set hs

/--
`hyps_depend_on_local_name_set hs ns` returns, for each `h ∈ hs`, whether `h`
depends on a hypothesis whose unique name appears in `ns`. This is the same as
(but more efficient than) calling `tactic.hyp_depends_on_local_name_set` for
every `h ∈ hs`.
-/
meta def hyps_depend_on_local_name_set (hs : list expr) (ns : name_set) :
  tactic (list bool) := do
  ctx_has_local ← context_has_local_def,
  if ctx_has_local
    then
      let go : expr → list bool × expr_set → tactic (list bool × expr_set) :=
      λ h ⟨deps, cache⟩, do
      { (h_dep, cache) ← hyp_depends_on_local_name_set' cache h ns,
        pure (h_dep :: deps, cache) }
      in
      prod.fst <$> hs.mfoldr go ([], mk_expr_map)
    else hs.mmap $ λ h, hyp_directly_depends_on_local_name_set h ns

/--
`hyps_depend_on_local_set hs is` returns, for each `h ∈ hs`, whether `h` depends
on any of the hypotheses `is`. This is the same as (but more efficient than)
calling `tactic.hyp_depends_on_local_set` for every `h ∈ hs`.
-/
meta def hyps_depend_on_local_set (hs : list expr) (is : expr_set) :
  tactic (list bool) :=
hyps_depend_on_local_name_set hs $ local_set_to_name_set is

/--
`hyps_depend_on_locals hs is` returns, for each `h ∈ hs`, whether `h` depends
on any of the hypotheses `is`. This is the same as (but more efficient than)
calling `tactic.hyp_depends_on_locals` for every `h ∈ hs`.
-/
meta def hyps_depend_on_locals (hs is : list expr) : tactic (list bool) :=
hyps_depend_on_local_name_set hs $ local_list_to_name_set is

/--
`hyp_depends_on_local_name_set_inclusive' cache h ns` is true iff the hypothesis
`h` inclusively depends on a hypothesis whose unique name appears in `ns`.
`cache` must be a set of hypotheses known *not* to depend (even indirectly) on
any of the `ns`. This is a performance optimisation, so you can give an empty
cache. The tactic also returns an expanded cache with hypotheses which the
tactic has encountered. Note that the cache records exclusive, not inclusive
dependencies.

You probably want to use `tactic.hyp_depends_on_local_name_set_inclusive` or
`tactic.hyps_depend_on_local_name_set_inclusive` instead of this tactic.
-/
meta def hyp_depends_on_local_name_set_inclusive' (cache : expr_set) (h : expr)
  (ns : name_set) : tactic (bool × expr_set) :=
if ns.contains h.local_uniq_name
  then pure (tt, cache)
  else hyp_depends_on_local_name_set' cache h ns

/--
`hyp_depends_on_local_name_set_inclusive h ns` is true iff the hypothesis `h`
inclusively depends on any of the hypotheses whose unique names appear in `ns`.
If you need to check the dependencies of multiple hypotheses, use
`tactic.hyps_depend_on_local_name_set_inclusive`.
-/
meta def hyp_depends_on_local_name_set_inclusive (h : expr) (ns : name_set) :
  tactic bool :=
list.mbor
  [ pure $ ns.contains h.local_uniq_name,
    hyp_depends_on_local_name_set h ns ]

/--
`hyp_depends_on_local_set_inclusive h hs` is true iff the hypothesis `h`
inclusively depends on any of the hypotheses `hs`. If you need to check
dependencies of multiple hypotheses, use
`tactic.hyps_depend_on_local_set_inclusive`.
-/
meta def hyp_depends_on_local_set_inclusive (h : expr) (hs : expr_set) :
  tactic bool :=
hyp_depends_on_local_name_set_inclusive h $ local_set_to_name_set hs

/--
`hyp_depends_on_locals_inclusive h hs` is true iff the hypothesis `h`
inclusively depends on any of the hypotheses `hs`. If you need to check
dependencies of multiple hypotheses, use
`tactic.hyps_depend_on_locals_inclusive`.
-/
meta def hyp_depends_on_locals_inclusive (h : expr) (hs : list expr) :
  tactic bool :=
hyp_depends_on_local_name_set_inclusive h $ local_list_to_name_set hs

/--
`hyps_depend_on_local_name_set_inclusive hs ns` returns, for each `h ∈ hs`,
whether `h` inclusively depends on a hypothesis whose unique name appears in
`ns`. This is the same as (but more efficient than) calling
`tactic.hyp_depends_on_local_name_set_inclusive` for every `h ∈ hs`.
-/
meta def hyps_depend_on_local_name_set_inclusive (hs : list expr) (ns : name_set) :
  tactic (list bool) := do
  ctx_has_local ← context_has_local_def,
  if ctx_has_local
    then
      let go : expr → list bool × expr_set → tactic (list bool × expr_set) :=
      λ h ⟨deps, cache⟩, do
      { (h_dep, cache) ← hyp_depends_on_local_name_set_inclusive' cache h ns,
        pure (h_dep :: deps, cache) }
      in
      prod.fst <$> hs.mfoldr go ([], mk_expr_map)
    else
      hs.mmap $ λ h, hyp_directly_depends_on_local_name_set_inclusive h ns

/--
`hyps_depend_on_local_set_inclusive hs is` returns, for each `h ∈ hs`, whether
`h` depends inclusively on any of the hypotheses `is`. This is the same as
(but more efficient than) calling `tactic.hyp_depends_on_local_set_inclusive`
for every `h ∈ hs`.
-/
meta def hyps_depend_on_local_set_inclusive (hs : list expr) (is : expr_set) :
  tactic (list bool) :=
hyps_depend_on_local_name_set_inclusive hs $ local_set_to_name_set is

/--
`hyps_depend_on_locals_inclusive hs is` returns, for each `h ∈ hs`, whether `h`
depends inclusively on any of the hypotheses `is`. This is the same as (but more
efficient than) calling `tactic.hyp_depends_on_locals_inclusive` for every
`h ∈ hs`.
-/
meta def hyps_depend_on_locals_inclusive (hs is : list expr) : tactic (list bool) :=
hyps_depend_on_local_name_set_inclusive hs $ local_list_to_name_set is

/-! #### Computing the dependencies of a hypothesis -/

/--
`dependency_set_of_hyp' cache h` is the set of dependencies of the hypothesis
`h`. `cache` is a map from hypotheses to all their dependencies (including
indirect dependencies). This is a performance optimisation, so you can give an
empty cache. The tactic also returns an expanded cache with hypotheses which
the tactic has encountered.

You probably want to use `tactic.dependency_set_of_hyp` or
`tactic.dependency_sets_of_hyps` instead of this tactic.
-/
meta def dependency_set_of_hyp' : expr_map expr_set → expr →
  tactic (expr_set × expr_map expr_set) := λ cache h, do
  match cache.find h with
  | some deps := pure (deps, cache)
  | none := do
    direct_deps ← direct_dependency_set_of_hyp h,
    (deps, cache) ←
      direct_deps.mfold (direct_deps, cache) $ λ h' ⟨deps, cache⟩, do
      { (deps', cache) ← dependency_set_of_hyp' cache h',
        pure (deps.union deps', cache) },
    pure (deps, cache.insert h deps)
  end

/--
`dependency_set_of_hyp h` is the set of dependencies of the hypothesis `h`. If
you need the dependencies of multiple hypotheses, use
`tactic.dependency_sets_of_hyps`.
-/
meta def dependency_set_of_hyp (h : expr) : tactic expr_set := do
  ctx_has_local ← context_upto_hyp_has_local_def h,
  if ctx_has_local
    then prod.fst <$> dependency_set_of_hyp' mk_expr_map h
    else direct_dependency_set_of_hyp h

/--
`dependency_name_set_of_hyp h` is the set of unique names of the dependencies of
the hypothesis `h`. If you need the dependencies of multiple hypotheses, use
`tactic.dependency_name_sets_of_hyps`.
-/
meta def dependency_name_set_of_hyp (h : expr) : tactic name_set :=
local_set_to_name_set <$> dependency_set_of_hyp h

/--
`dependencies_of_hyp h` is the list of dependencies of the hypothesis `h`.
The dependencies are returned in no particular order. If you need the
dependencies of multiple hypotheses, use `tactic.dependencies_of_hyps`.
-/
meta def dependencies_of_hyp (h : expr) : tactic (list expr) :=
rb_set.to_list <$> dependency_set_of_hyp h

/--
`dependency_sets_of_hyps hs` returns, for each `h ∈ hs`, the set of dependencies
of `h`. This is the same as (but more performant than) using
`tactic.dependency_set_of_hyp` on every `h ∈ hs`.
-/
meta def dependency_sets_of_hyps (hs : list expr) : tactic (list expr_set) := do
  ctx_has_def ← context_has_local_def,
  if ctx_has_def
    then
      let go : expr → list expr_set × expr_map expr_set →
        tactic (list expr_set × expr_map expr_set) := do
      λ h ⟨deps, cache⟩, do
        { (h_deps, cache) ← dependency_set_of_hyp' cache h,
          pure (h_deps :: deps, cache) }
      in
      prod.fst <$> hs.mfoldr go ([], mk_expr_map)
    else
      hs.mmap direct_dependency_set_of_hyp

/--
`dependency_name_sets_of_hyps hs` returns, for each `h ∈ hs`, the set of unique
names of the dependencies of `h`. This is the same as (but more performant than)
using `tactic.dependency_name_set_of_hyp` on every `h ∈ hs`.
-/
meta def dependency_name_sets_of_hyps (hs : list expr) : tactic (list name_set) :=
list.map local_set_to_name_set <$> dependency_sets_of_hyps hs

/--
`dependencies_of_hyps hs` returns, for each `h ∈ hs`, the dependencies of `h`.
The dependencies appear in no particular order in the returned lists. This is
the same as (but more performant than) using `tactic.dependencies_of_hyp` on
every `h ∈ hs`.
-/
meta def dependencies_of_hyps (hs : list expr) : tactic (list (list expr)) :=
list.map rb_set.to_list <$> dependency_sets_of_hyps hs

/--
`dependency_set_of_hyp_inclusive' cache h` is the set of dependencies of the
hypothesis `h`, plus `h` itself. `cache` is a map from hypotheses to all their
dependencies (including indirect dependencies). This is a performance
optimisation, so you can give an empty cache. The tactic also returns an
expanded cache with hypotheses which the tactic has encountered. Note that the
cache records exclusive, not inclusive dependencies.

You probably want to use `tactic.dependency_set_of_hyp_inclusive` or
`tactic.dependency_sets_of_hyps_inclusive` instead of this tactic.
-/
meta def dependency_set_of_hyp_inclusive' (cache : expr_map expr_set) (h : expr) :
  tactic (expr_set × expr_map expr_set) := do
  (deps, cache) ← dependency_set_of_hyp' cache h,
  pure (deps.insert h, cache)

/--
`dependency_set_of_hyp_inclusive h` is the set of dependencies of the hypothesis
`h`, plus `h` itself. If you need the dependencies of multiple hypotheses, use
`tactic.dependency_sets_of_hyps_inclusive`.
-/
meta def dependency_set_of_hyp_inclusive (h : expr) : tactic expr_set := do
  deps ← dependency_set_of_hyp h,
  pure $ deps.insert h

/--
`dependency_name_set_of_hyp_inclusive h` is the set of unique names of the
dependencies of the hypothesis `h`, plus the unique name of `h` itself. If you
need the dependencies of multiple hypotheses, use
`tactic.dependency_name_sets_of_hyps_inclusive`.
-/
meta def dependency_name_set_of_hyp_inclusive (h : expr) : tactic name_set :=
local_set_to_name_set <$> dependency_set_of_hyp_inclusive h

/--
`dependencies_of_hyp_inclusive h` is the list of dependencies of the hypothesis
`h`, plus `h` itself. The dependencies are returned in no particular order. If
you need the dependencies of multiple hypotheses, use
`tactic.dependencies_of_hyps_inclusive`.
-/
meta def dependencies_of_hyp_inclusive (h : expr) : tactic (list expr) :=
rb_set.to_list <$> dependency_set_of_hyp_inclusive h

/--
`dependency_sets_of_hyps_inclusive hs` returns, for each `h ∈ hs`, the
dependencies of `h`, plus `h` itself. This is the same as (but more performant
than) using `tactic.dependency_set_of_hyp_inclusive` on every `h ∈ hs`.
-/
meta def dependency_sets_of_hyps_inclusive (hs : list expr) :
  tactic (list expr_set) := do
  ctx_has_def ← context_has_local_def,
  if ctx_has_def
    then
      let go : expr → list expr_set × expr_map expr_set →
        tactic (list expr_set × expr_map expr_set) :=
      λ h ⟨deps, cache⟩, do
      { (h_deps, cache) ← dependency_set_of_hyp_inclusive' cache h,
        pure (h_deps :: deps, cache) }
      in
      prod.fst <$> hs.mfoldr go ([], mk_expr_map)
    else
      hs.mmap direct_dependency_set_of_hyp_inclusive

/--
`dependency_name_sets_of_hyps_inclusive hs` returns, for each `h ∈ hs`, the
unique names of the dependencies of `h`, plus the unique name of `h` itself.
This is the same as (but more performant than) using
`tactic.dependency_name_set_of_hyp_inclusive` on every `h ∈ hs`.
-/
meta def dependency_name_sets_of_hyps_inclusive (hs : list expr) :
  tactic (list name_set) :=
list.map local_set_to_name_set <$> dependency_sets_of_hyps_inclusive hs

/--
`dependencies_of_hyps_inclusive hs` returns, for each `h ∈ hs`, the dependencies
of `h`, plus `h` itself. The dependencies appear in no particular order in the
returned lists. This is the same as (but more performant than) using
`tactic.dependencies_of_hyp_inclusive` on every `h ∈ hs`.
-/
meta def dependencies_of_hyps_inclusive (hs : list expr) :
  tactic (list (list expr)) :=
list.map rb_set.to_list <$> dependency_sets_of_hyps_inclusive hs

/-! #### Computing the reverse dependencies of a hypothesis -/

private meta def reverse_dependencies_of_hyp_name_set_aux (hs : name_set) :
  list expr → list expr → name_set → tactic (list expr)
| [] revdeps _ := pure revdeps.reverse
| (H :: Hs) revdeps ns := do
  let H_uname := H.local_uniq_name,
  H_is_revdep ← list.mband
    [ pure $ ¬ hs.contains H_uname,
      hyp_directly_depends_on_local_name_set H ns ],
  if H_is_revdep
    then
      reverse_dependencies_of_hyp_name_set_aux Hs (H :: revdeps)
        (ns.insert H_uname)
    else
      reverse_dependencies_of_hyp_name_set_aux Hs revdeps ns

/--
`reverse_dependencies_of_hyp_name_set hs` is the list of reverse dependencies of
the hypotheses whose unique names appear in `hs`, excluding the `hs` themselves.
The reverse dependencies are returned in the order in which they appear in the
context.
-/
meta def reverse_dependencies_of_hyp_name_set (hs : name_set) :
  tactic (list expr) := do
  ctx ← local_context,
  let ctx := ctx.after (λ h, hs.contains h.local_uniq_name),
  reverse_dependencies_of_hyp_name_set_aux hs ctx [] hs

/--
`reverse_dependencies_of_hyp_set hs` is the list of reverse dependencies of the
hypotheses `hs`, excluding the `hs` themselves. The reverse dependencies are
returned in the order in which they appear in the context.
-/
meta def reverse_dependencies_of_hyp_set (hs : expr_set) : tactic (list expr) :=
reverse_dependencies_of_hyp_name_set $ local_set_to_name_set hs

/--
`reverse_dependencies_of_hyps hs` is the list of reverse dependencies of the
hypotheses `hs`, excluding the `hs` themselves. The reverse dependencies are
returned in the order in which they appear in the context.
-/
meta def reverse_dependencies_of_hyps (hs : list expr) : tactic (list expr) :=
reverse_dependencies_of_hyp_name_set $ local_list_to_name_set hs

private meta def reverse_dependencies_of_hyp_name_set_inclusive_aux :
  list expr → list expr → name_set → tactic (list expr)
| [] revdeps _ := pure revdeps.reverse
| (H :: Hs) revdeps ns := do
  let H_uname := H.local_uniq_name,
  H_is_revdep ← list.mbor
    [ pure $ ns.contains H.local_uniq_name,
      hyp_directly_depends_on_local_name_set H ns ],
  if H_is_revdep
    then
      reverse_dependencies_of_hyp_name_set_inclusive_aux Hs (H :: revdeps)
        (ns.insert H_uname)
    else
      reverse_dependencies_of_hyp_name_set_inclusive_aux Hs revdeps ns

/--
`reverse_dependencies_of_hyp_name_set_inclusive hs` is the list of reverse
dependencies of the hypotheses whose unique names appear in `hs`, including the
`hs` themselves. The reverse dependencies are returned in the order in which
they appear in the context.
-/
meta def reverse_dependencies_of_hyp_name_set_inclusive (hs : name_set) :
  tactic (list expr) := do
  ctx ← local_context,
  let ctx := ctx.drop_while (λ h, ¬ hs.contains h.local_uniq_name),
  reverse_dependencies_of_hyp_name_set_inclusive_aux ctx [] hs

/--
`reverse_dependencies_of_hyp_set_inclusive hs` is the list of reverse
dependencies of the hypotheses `hs`, including the `hs` themselves. The
inclusive reverse dependencies are returned in the order in which they appear in
the context.
-/
meta def reverse_dependencies_of_hyp_set_inclusive (hs : expr_set) :
  tactic (list expr) :=
reverse_dependencies_of_hyp_name_set_inclusive $ local_set_to_name_set hs

/--
`reverse_dependencies_of_hyps_inclusive hs` is the list of reverse dependencies
of the hypotheses `hs`, including the `hs` themselves. The reverse dependencies
are returned in the order in which they appear in the context.
-/
meta def reverse_dependencies_of_hyps_inclusive (hs : list expr) :
  tactic (list expr) :=
reverse_dependencies_of_hyp_name_set_inclusive $ local_list_to_name_set hs

/-! ### Reverting a hypothesis and its reverse dependencies -/

/--
`revert_name_set hs` reverts the hypotheses whose unique names appear in `hs`,
as well as any hypotheses that depend on them. Returns the number of reverted
hypotheses and a list containing these hypotheses. The reverted hypotheses are
returned in the order in which they used to appear in the context and are
guaranteed to store the correct type (see `tactic.update_type`).
-/
meta def revert_name_set (hs : name_set) : tactic (ℕ × list expr) := do
  to_revert ← reverse_dependencies_of_hyp_name_set_inclusive hs,
  to_revert_with_types ← to_revert.mmap update_type,
  num_reverted ← revert_lst to_revert,
  pure (num_reverted, to_revert_with_types)

/--
`revert_set hs` reverts the hypotheses `hs`, as well as any hypotheses that
depend on them. Returns the number of reverted hypotheses and a list containing
these hypotheses. The reverted hypotheses are returned in the order in which
they used to appear in the context and are guaranteed to store the correct type
(see `tactic.update_type`).
-/
meta def revert_set (hs : expr_set) : tactic (ℕ × list expr) :=
revert_name_set $ local_set_to_name_set hs

/--
`revert_lst' hs` reverts the hypotheses `hs`, as well as any hypotheses that
depend on them. Returns the number of reverted hypotheses and a list containing
these hypotheses. The reverted hypotheses are returned in the order in which
they used to appear in the context and are guaranteed to store the correct type
(see `tactic.update_type`).

This is a more informative version of `tactic.revert_lst`.
-/
meta def revert_lst' (hs : list expr) : tactic (ℕ × list expr) :=
revert_name_set $ local_list_to_name_set hs

/--
`revert_reverse_dependencies_of_hyp h` reverts all the hypotheses that depend on
the hypothesis `h`, including the local definitions that have `h` in their
value. This fixes a bug in `tactic.revert_kdependencies` that does not revert
local definitions for which `h` only appears in the value. Returns the number
of reverted hypotheses.
-/
/- We cannot implement it as `revert e >> intro1` because that would change the
local constant in the context. -/
meta def revert_reverse_dependencies_of_hyp (h : expr) : tactic ℕ :=
reverse_dependencies_of_hyp_name_set (mk_name_set.insert h.local_uniq_name) >>=
  revert_lst

/--
`revert_reverse_dependencies_of_hyp_name_set hs` reverts all the hypotheses that
depend on a hypothesis whose unique name appears in `hs`. The `hs` themselves
are not reverted, unless they depend on each other. Returns the number of
reverted hypotheses.
-/
meta def revert_reverse_dependencies_of_hyp_name_set (hs : name_set) : tactic ℕ :=
reverse_dependencies_of_hyp_name_set hs >>= revert_lst

/--
`revert_reverse_dependencies_of_hyp_set hs` reverts all the hypotheses that
depend on a hypothesis in `hs`. The `hs` themselves are not reverted, unless
they depend on each other. Returns the number of reverted hypotheses.
-/
meta def revert_reverse_dependencies_of_hyp_set (hs : expr_set) : tactic ℕ :=
reverse_dependencies_of_hyp_set hs >>= revert_lst

/--
`revert_reverse_dependencies_of_hyp hs` reverts all the hypotheses that depend
on a hypothesis in `hs`. The `hs` themselves are not reverted, unless they
depend on each other. Returns the number of reverted hypotheses.
-/
meta def revert_reverse_dependencies_of_hyps (hs : list expr) : tactic ℕ :=
reverse_dependencies_of_hyps hs >>= revert_lst

end tactic


import spaces.bounded_cont_diff_map
import analysis.normed.group.basic
import measure_theory.function.lp_space

open topological_space function measure_theory set filter
open_locale bounded_cont_diff_map topological_space ennreal bounded_continuous_function nnreal

section prelim

-- Commutativity should not be needed, but it's sufficient for now
@[to_additive]
theorem continuous_iff_continuous_at_one {G H hom : Type*} [comm_group G] [comm_group H]
  [topological_space G] [topological_space H] [topological_group G] [topological_group H]
  [monoid_hom_class hom G H] {f : hom} :
  continuous f ↔ continuous_at f 1 :=
begin
  refine ⟨λ h, h.continuous_at, λ h, _⟩,
  letI : uniform_space G := topological_group.to_uniform_space G,
  letI : uniform_group G := topological_group_is_uniform,
  letI : uniform_space H := topological_group.to_uniform_space H,
  letI : uniform_group H := topological_group_is_uniform,
  exact uniform_continuous.continuous (uniform_continuous_of_continuous_at_one f h)
end

theorem continuous_multilinear_map.ext_iff {ι 𝕜 F : Type*} {E : ι → Type*} [decidable_eq ι] [nondiscrete_normed_field 𝕜]
  [Π i, normed_group (E i)] [normed_group F] [Π i, normed_space 𝕜 (E i)] [normed_space 𝕜 F]
  {f g : continuous_multilinear_map 𝕜 E F} : f = g ↔ ∀ x, f x = g x :=
⟨λ h x, h ▸ rfl, continuous_multilinear_map.ext⟩

lemma has_fderiv_at.tsupport_subset {𝕜 E F : Type*} [nondiscrete_normed_field 𝕜]
  [normed_group E] [normed_group F] [normed_space 𝕜 E] [normed_space 𝕜 F]
  {f : E → F} {f' : E → E →L[𝕜] F} (hf : ∀ x, has_fderiv_at f (f' x) x) :
  tsupport f' ⊆ tsupport f :=
begin
  intros x,
  contrapose,
  rw [not_mem_closure_support_iff_eventually_eq, not_mem_closure_support_iff_eventually_eq],
  intros h,
  filter_upwards [h.eventually_eq_nhds],
  intros y hy,
  exact has_fderiv_at.unique (hf y) ((has_fderiv_at_const 0 y).congr_of_eventually_eq hy)
end

lemma fderiv_tsupport_subset {𝕜 E F : Type*} [nondiscrete_normed_field 𝕜]
  [normed_group E] [normed_group F] [normed_space 𝕜 E] [normed_space 𝕜 F]
  {f : E → F} (hf : differentiable 𝕜 f) :
  tsupport (fderiv 𝕜 f) ⊆ tsupport f :=
has_fderiv_at.tsupport_subset (λ x, (hf x).has_fderiv_at)

lemma iterated_fderiv_tsupport_subset {𝕜 E F : Type*} [nondiscrete_normed_field 𝕜]
  [normed_group E] [normed_group F] [normed_space 𝕜 E] [normed_space 𝕜 F]
  {nf : with_top ℕ} {i : ℕ} {f : E → F} (hf : cont_diff 𝕜 nf f)
  (hif : (i : with_top ℕ) ≤ nf) :
  tsupport (iterated_fderiv 𝕜 i f) ⊆ tsupport f :=
begin
  induction i with i hi,
  { refine subset_of_eq (congr_arg closure _),
    ext x,
    refine not_iff_not_of_iff _,
    simp only [continuous_multilinear_map.ext_iff, iterated_fderiv_zero_apply,
                continuous_multilinear_map.zero_apply, forall_const], },
  { have hif' : (i : with_top ℕ) < nf :=
      lt_of_lt_of_le (with_top.coe_lt_coe.mpr $ i.lt_succ_self) hif,
    have hdf : differentiable 𝕜 (iterated_fderiv 𝕜 i f) :=
      (cont_diff_iff_continuous_differentiable.mp hf).2 i hif',
    calc tsupport (iterated_fderiv 𝕜 (i+1) f)
        = tsupport (fderiv 𝕜 $ iterated_fderiv 𝕜 i f) : by
          { refine congr_arg closure _,
            ext x,
            refine not_iff_not_of_iff _,
            rw [iterated_fderiv_succ_eq_comp_left, comp_apply,
                (continuous_multilinear_curry_left_equiv _ _ _).map_eq_zero_iff] }
    ... ⊆ tsupport (iterated_fderiv 𝕜 i f) : fderiv_tsupport_subset hdf
    ... ⊆ tsupport f : hi hif'.le }
end

lemma has_compact_support_iterated_fderiv {𝕜 E F : Type*} [nondiscrete_normed_field 𝕜]
  [normed_group E] [normed_group F] [normed_space 𝕜 E] [normed_space 𝕜 F]
  {nf : with_top ℕ} {i : ℕ} {f : E → F} (hf : cont_diff 𝕜 nf f) (hsupp : has_compact_support f)
  (hif : (i : with_top ℕ) ≤ nf) : has_compact_support (iterated_fderiv 𝕜 i f) :=
compact_of_is_closed_subset hsupp (is_closed_tsupport _) (iterated_fderiv_tsupport_subset hf hif)

lemma continuous.mem_ℒp_of_has_compact_support {α E : Type*} [hα : nonempty α]
  [topological_space α] {m : measurable_space α} [t2_space α] [opens_measurable_space α]
  [normed_group E] [measurable_space E] [borel_space E]
  [second_countable_topology_either α E]  -- TODO : this should be removable because we are integrating
                                          -- on a compact subset
  {f : α → E} (hf : continuous f) (hsupp : has_compact_support f)
  (p : ℝ≥0∞) (μ : measure α) [is_finite_measure_on_compacts μ]:
  mem_ℒp f p μ :=
begin
  have : bdd_above (range $ norm ∘ f),
    from hf.norm.bdd_above_range_of_has_compact_support hsupp.norm,
  refine mem_ℒp.of_le
    (mem_ℒp_indicator_const p hsupp.measurable_set (⨆ x, ∥f x∥) (or.inr hsupp.measure_lt_top.ne))
    hf.ae_strongly_measurable (ae_of_all _ $ λ x, _),
  rw norm_indicator_eq_indicator_norm,
  refine set.le_indicator (λ a _, _) (λ a, _) x,
  { rw real.norm_of_nonneg (le_csupr_of_le this hα.some (norm_nonneg _)),
    exact le_csupr this a },
  { intros ha,
    simpa using not_mem_of_not_mem_closure ha }
end

end prelim

private def cont_diff_map_supported_in_submodule (𝕜 E F : Type*) [nondiscrete_normed_field 𝕜]
  [normed_group E] [normed_group F] [normed_space 𝕜 E] [normed_space 𝕜 F] (K : set E)
  (n : with_top ℕ) : submodule 𝕜 (E → F) :=
{ carrier := {f | cont_diff 𝕜 n f ∧ ∀ x ∉ K, f x = 0},
  zero_mem' := ⟨cont_diff_zero_fun, λ x hx, rfl⟩,
  add_mem' := λ f g hf hg, ⟨hf.1.add hg.1, λ x hx,
    by rw [pi.add_apply, hf.2 x hx, hg.2 x hx, add_zero]⟩,
  smul_mem' := λ c f hf, ⟨cont_diff_const.smul hf.1, λ x hx,
    by rw [pi.smul_apply, hf.2 x hx, smul_zero]⟩ }

def cont_diff_map_supported_in (𝕜 E F : Type*) [nondiscrete_normed_field 𝕜]
  [normed_group E] [normed_group F] [normed_space 𝕜 E] [normed_space 𝕜 F] (K : set E)
  (n : with_top ℕ) := ↥(cont_diff_map_supported_in_submodule 𝕜 E F K n)

namespace cont_diff_map_supported_in

section any_set

variables {𝕜 E F : Type*} [nondiscrete_normed_field 𝕜] [normed_group E] [normed_group F]
  [normed_space 𝕜 E] [normed_space 𝕜 F] {K : set E} {n : with_top ℕ}
  {f g : cont_diff_map_supported_in 𝕜 E F K n} {x : E}

instance : add_comm_group (cont_diff_map_supported_in 𝕜 E F K n) := submodule.add_comm_group _
instance : module 𝕜 (cont_diff_map_supported_in 𝕜 E F K n) := submodule.module _
instance : has_coe_to_fun (cont_diff_map_supported_in 𝕜 E F K n) (λ _, E → F) := ⟨λ f, f.1⟩

@[ext] lemma ext (H : ∀x, f x = g x) : f = g :=
by {ext, exact H x}

protected lemma cont_diff (f : cont_diff_map_supported_in 𝕜 E F K n) :
  cont_diff 𝕜 n f :=
f.2.1

protected lemma continuous (f : cont_diff_map_supported_in 𝕜 E F K n) :
  continuous f :=
f.cont_diff.continuous

lemma supported_in (f : cont_diff_map_supported_in 𝕜 E F K n) :
  ∀ x ∉ K, f x = 0 :=
f.2.2

lemma support_subset (f : cont_diff_map_supported_in 𝕜 E F K n) :
  support f ⊆ K :=
support_subset_iff'.mpr f.2.2

end any_set

section compact

variables {𝕜 E F : Type*} [nondiscrete_normed_field 𝕜] [normed_group E] [normed_group F]
  [normed_space 𝕜 E] [normed_space 𝕜 F] {K : compacts E} {n : with_top ℕ}
  {f g : cont_diff_map_supported_in 𝕜 E F K n} {x : E}

lemma has_compact_support (f : cont_diff_map_supported_in 𝕜 E F K n) :
  has_compact_support f :=
has_compact_support.intro K.2 f.supported_in

lemma tsupport_subset (f : cont_diff_map_supported_in 𝕜 E F K n) :
  tsupport f ⊆ K :=
closure_minimal f.support_subset K.2.is_closed

def of_support_subset {f : E → F} (hf : cont_diff 𝕜 n f) (hsupp : support f ⊆ K) :
  cont_diff_map_supported_in 𝕜 E F K n :=
⟨f, ⟨hf, λ x hx, by_contra $ λ hxn, hx (hsupp hxn)⟩⟩

protected lemma bounded (f : cont_diff_map_supported_in 𝕜 E F K n) {i : ℕ} (hi : ↑i ≤ n) :
  ∃ C, ∀ x, ∥iterated_fderiv 𝕜 i f x∥ ≤ C :=
continuous.bounded_above_of_compact_support
  ((cont_diff_iff_continuous_differentiable.mp f.cont_diff).1 i hi)
  (has_compact_support_iterated_fderiv f.cont_diff f.has_compact_support hi)

def to_bounded_cont_diff_map (f : cont_diff_map_supported_in 𝕜 E F K n) :
  B^n⟮E,F;𝕜⟯ :=
⟨f, f.cont_diff, λ i hi, f.bounded hi⟩

lemma coe_to_bounded_cont_diff_map (f : cont_diff_map_supported_in 𝕜 E F K n) :
  (f.to_bounded_cont_diff_map : E → F) = f := rfl

lemma to_bounded_cont_diff_map_injective :
  injective (to_bounded_cont_diff_map : cont_diff_map_supported_in 𝕜 E F K n → B^n⟮E,F;𝕜⟯) :=
begin
  intros f g hfg,
  ext x,
  change to_bounded_cont_diff_map f x = _,
  rw hfg,
  refl
end

def to_bounded_cont_diff_mapₗ :
  cont_diff_map_supported_in 𝕜 E F K n →ₗ[𝕜] (B^n⟮E,F;𝕜⟯) :=
{ to_fun := to_bounded_cont_diff_map,
  map_add' := λ f g, rfl,
  map_smul' := λ a f, rfl }

noncomputable instance : topological_space (cont_diff_map_supported_in 𝕜 E F K n) :=
topological_space.induced to_bounded_cont_diff_mapₗ infer_instance

noncomputable instance : uniform_space (cont_diff_map_supported_in 𝕜 E F K n) :=
uniform_space.comap to_bounded_cont_diff_mapₗ infer_instance

instance : topological_add_group (cont_diff_map_supported_in 𝕜 E F K n) :=
topological_add_group_induced _

instance : has_continuous_smul 𝕜 (cont_diff_map_supported_in 𝕜 E F K n) :=
has_continuous_smul_induced _

variables (𝕜 E F K n)

noncomputable def snorms :
  seminorm_family 𝕜 (cont_diff_map_supported_in 𝕜 E F K n)
  (bounded_cont_diff_map.snorms_ind n) :=
(bounded_cont_diff_map.snorms 𝕜 E F n).comp to_bounded_cont_diff_mapₗ

variables {𝕜 E F K n}

lemma snorms_monotone :
  monotone (snorms 𝕜 E F K n) :=
λ i j hij, seminorm.comp_mono _ (bounded_cont_diff_map.snorms_monotone hij)

--lemma finset.sup_eq_csupr {α β : Type*} [nonempty α] [conditionally_complete_linear_order_bot β] (s : finset α)
--  (f : α → β) : s.sup f = (⨆a∈s, f a) :=
--le_antisymm
--  (finset.sup_le $ assume a ha, le_csupr_of_le sorry a $ le_csupr finite_range_const.bdd_above ha)
--  (csupr_le' $ assume a, csupr_le' $ assume ha, finset.le_sup ha)

lemma snorms_apply (i : ℕ) (hi : ↑i ≤ n) :
  snorms 𝕜 E F K n ⟨i, hi⟩ f = ↑(⨆ (j ≤ i) (x ∈ K), ∥iterated_fderiv 𝕜 j f x∥₊) :=
begin
  rw [snorms, bounded_cont_diff_map.snorms, seminorm_family.comp_apply, seminorm.comp_apply,
      subtype.coe_mk, seminorm.finset_sup_apply, nnreal.coe_injective.eq_iff],
  -- finset.sup'_eq_csupr, sup_ima],
  sorry
end

lemma snorms_directed :
  directed (≤) (snorms 𝕜 E F K n) :=
monotone.directed_le snorms_monotone

instance with_seminorms : with_seminorms (cont_diff_map_supported_in.snorms 𝕜 E F K n) :=
to_bounded_cont_diff_mapₗ.with_seminorms_induced

noncomputable! def to_bounded_cont_diff_mapL :
  cont_diff_map_supported_in 𝕜 E F K n →L[𝕜] (B^n⟮E,F;𝕜⟯) :=
{ to_linear_map := to_bounded_cont_diff_mapₗ,
  cont := continuous_induced_dom }

lemma continuous_iff {X : Type*} [topological_space X]
  (φ : X → cont_diff_map_supported_in 𝕜 E F K n) :
  continuous φ ↔ continuous
    (cont_diff_map_supported_in.to_bounded_cont_diff_map ∘ φ) :=
⟨λ hφ, to_bounded_cont_diff_mapL.continuous.comp hφ, continuous_induced_rng⟩

lemma continuous_of_commutes {𝕜' E' F' : Type*} [normed_group E'] [normed_group F']
  [nondiscrete_normed_field 𝕜'] [normed_space 𝕜' E'] [normed_space 𝕜' F'] {K' : compacts E'}
  (φ : cont_diff_map_supported_in 𝕜 E F K n → cont_diff_map_supported_in 𝕜' E' F' K' n)
  (ψ : B^n⟮E,F;𝕜⟯ → B^n⟮E',F';𝕜'⟯) (hcont : continuous ψ)
  (hcomm : to_bounded_cont_diff_map ∘ φ = ψ ∘ to_bounded_cont_diff_map) :
  continuous φ :=
begin
  rw [continuous_iff, hcomm],
  exact hcont.comp to_bounded_cont_diff_mapL.continuous
end

protected def of_le {k : with_top ℕ} (hkn : k ≤ n) (f : cont_diff_map_supported_in 𝕜 E F K n) :
  cont_diff_map_supported_in 𝕜 E F K k :=
⟨f, f.cont_diff.of_le hkn, f.supported_in⟩

protected def of_leₗ {k : with_top ℕ} (hkn : k ≤ n) :
  cont_diff_map_supported_in 𝕜 E F K n →ₗ[𝕜] cont_diff_map_supported_in 𝕜 E F K k :=
{ to_fun := cont_diff_map_supported_in.of_le hkn,
  map_add' := λ f g, by ext; refl,
  map_smul' := λ c f, by ext; refl }

protected noncomputable! def of_leL {k : with_top ℕ} (hkn : k ≤ n) :
  (cont_diff_map_supported_in 𝕜 E F K n) →L[𝕜] (cont_diff_map_supported_in 𝕜 E F K k) :=
{ to_linear_map := cont_diff_map_supported_in.of_leₗ hkn,
  cont := continuous_induced_rng
    ((bounded_cont_diff_map.of_leL 𝕜 E F hkn).comp (to_bounded_cont_diff_mapL)).continuous }

protected noncomputable def iterated_fderivL (i : ℕ) :
  (cont_diff_map_supported_in 𝕜 E F K n) →L[𝕜] (E →ᵇ (E [×i]→L[𝕜] F)) :=
bounded_cont_diff_map.iterated_fderivL i ∘L to_bounded_cont_diff_mapL

protected lemma has_basis_zero :
  (𝓝 0 : filter $ cont_diff_map_supported_in 𝕜 E F K n).has_basis
  (λ Nε : ℕ × ℝ, 0 < Nε.2) (λ Nε, ⋂ (i : ℕ) (hiN : i ≤ Nε.1) (hi : ↑i ≤ n),
    cont_diff_map_supported_in.iterated_fderivL i ⁻¹' metric.ball 0 Nε.2) :=
begin
  rw [nhds_induced],
  convert bounded_cont_diff_map.has_basis_zero.comap to_bounded_cont_diff_mapₗ,
  ext,
  simp only [mem_Inter, mem_preimage, mem_ball_zero_iff],
  refl
end

section zero

noncomputable instance : metric_space (cont_diff_map_supported_in 𝕜 E F K 0) :=
metric_space.induced (to_bounded_cont_diff_mapL : cont_diff_map_supported_in 𝕜 E F K 0 →L[𝕜] _)
  to_bounded_cont_diff_map_injective infer_instance

noncomputable instance : normed_group (cont_diff_map_supported_in 𝕜 E F K 0) :=
{ to_metric_space := infer_instance,
  ..normed_group.induced
    (to_bounded_cont_diff_mapL : cont_diff_map_supported_in 𝕜 E F K 0 →L[𝕜] _)
      .to_linear_map.to_add_monoid_hom to_bounded_cont_diff_map_injective }

lemma norm_def {f : cont_diff_map_supported_in 𝕜 E F K 0} : ∥f∥ = ∥to_bounded_cont_diff_mapL f∥ := rfl

-- TODO : add `cont_diff_map_supported_in.to_bounded_continuous_function`
lemma le_norm {f : cont_diff_map_supported_in 𝕜 E F K 0} (x : E) : ∥f x∥ ≤ ∥f∥ :=
bounded_continuous_function.norm_coe_le_norm
  (bounded_cont_diff_map.to_bounded_continuous_function 𝕜 E F 0 (to_bounded_cont_diff_mapL f)) x

noncomputable! instance : normed_space 𝕜 (cont_diff_map_supported_in 𝕜 E F K 0) :=
{ norm_smul_le := λ c f,
  begin
    rw [norm_def, norm_def, continuous_linear_map.map_smul],
    exact normed_space.norm_smul_le _ _
  end }

end zero

section infinity

lemma differentiable (f : cont_diff_map_supported_in 𝕜 E F K ⊤) : differentiable 𝕜 f :=
f.cont_diff.differentiable le_top

noncomputable def fderiv (f : cont_diff_map_supported_in 𝕜 E F K ⊤) :
  cont_diff_map_supported_in 𝕜 E (E →L[𝕜] F) K ⊤ :=
of_support_subset (cont_diff_top_iff_fderiv.mp f.cont_diff).2
  (subset_closure.trans $ (fderiv_tsupport_subset f.differentiable).trans f.tsupport_subset)

noncomputable def fderivₗ : cont_diff_map_supported_in 𝕜 E F K ⊤
  →ₗ[𝕜] cont_diff_map_supported_in 𝕜 E (E →L[𝕜] F) K ⊤ :=
{ to_fun := cont_diff_map_supported_in.fderiv,
  map_add' := λ f g,
  begin
    ext x : 1,
    exact fderiv_add f.differentiable.differentiable_at
      g.differentiable.differentiable_at,
  end,
  map_smul' := λ a f,
  begin
    ext x : 1,
    exact fderiv_const_smul f.differentiable.differentiable_at _
  end }

noncomputable def fderivL : cont_diff_map_supported_in 𝕜 E F K ⊤
  →L[𝕜] cont_diff_map_supported_in 𝕜 E (E →L[𝕜] F) K ⊤ :=
{ to_linear_map := fderivₗ,
  cont := continuous_induced_rng
    (bounded_cont_diff_map.fderivL ∘L to_bounded_cont_diff_mapL).continuous }

end infinity

lemma mem_ℒp (f : cont_diff_map_supported_in 𝕜 E F K n)
  [measurable_space 𝕜] [opens_measurable_space 𝕜]
  {m : measurable_space E} [opens_measurable_space E] [measurable_space F]
  [second_countable_topology F] [borel_space F] (p : ℝ≥0∞) (μ : measure E) [fact (1 ≤ p)]
  [is_finite_measure_on_compacts μ] : mem_ℒp f p μ :=
f.continuous.mem_ℒp_of_has_compact_support f.has_compact_support p μ

variables (n)

def to_Lpₗ [measurable_space 𝕜] [opens_measurable_space 𝕜]
  {m : measurable_space E} [opens_measurable_space E] [measurable_space F]
  [second_countable_topology F] [borel_space F] (p : ℝ≥0∞) (μ : measure E) [fact (1 ≤ p)]
  [is_finite_measure_on_compacts μ] :
  (cont_diff_map_supported_in 𝕜 E F K n) →ₗ[𝕜] (Lp F p μ) :=
{ to_fun := λ f, (f.mem_ℒp p μ).to_Lp f,
  map_add' := λ f g, (f.mem_ℒp p μ).to_Lp_add (g.mem_ℒp p μ),
  map_smul' := λ c f, (f.mem_ℒp p μ).to_Lp_const_smul c }

lemma coe_fn_to_Lpₗ [measurable_space 𝕜] [opens_measurable_space 𝕜]
  {m : measurable_space E} [opens_measurable_space E] [measurable_space F]
  [second_countable_topology F] [borel_space F] (p : ℝ≥0∞) (μ : measure E) [fact (1 ≤ p)]
  [is_finite_measure_on_compacts μ] (f : cont_diff_map_supported_in 𝕜 E F K n) :
  to_Lpₗ n p μ f =ᵐ[μ] f :=
(f.mem_ℒp p μ).coe_fn_to_Lp

noncomputable! def to_Lp_zero [measurable_space 𝕜] [opens_measurable_space 𝕜]
  {m : measurable_space E} [opens_measurable_space E] [measurable_space F]
  [second_countable_topology F] [borel_space F] (p : ℝ≥0∞) (μ : measure E) [fact (1 ≤ p)]
  [is_finite_measure_on_compacts μ] :
  (cont_diff_map_supported_in 𝕜 E F K 0) →L[𝕜] (Lp F p μ) :=
{ to_linear_map := to_Lpₗ 0 p μ,
  cont :=
  begin
    -- TODO : unify with `continuous.mem_ℒp_of_has_compact_support`
    have hK₁ : measurable_set ↑K := K.compact.measurable_set,
    have hK₂ : μ ↑K ≠ ∞ := K.compact.measure_lt_top.ne,
    change continuous (to_Lpₗ 0 p μ),
    refine linear_map.continuous_of_bound (to_Lpₗ 0 p μ)
      (∥indicator_const_Lp p hK₁ hK₂ (1 : ℝ)∥) (λ f, _),
    rw [mul_comm],
    refine Lp.norm_le_mul_norm_of_ae_le_mul _,
    filter_upwards [f.coe_fn_to_Lpₗ 0 p μ,
      (indicator_const_Lp_coe_fn : indicator_const_Lp p hK₁ hK₂ (1 : ℝ) =ᵐ[μ] _)],
    intros x hx₁ hx₂,
    rw [hx₁, hx₂, ← norm_norm f, ← norm_smul, ← indicator_const_smul_apply, norm_indicator_eq_indicator_norm],
    refine set.le_indicator (λ a _, _) (λ a ha, _) x,
    { rw [smul_eq_mul, mul_one, norm_norm],
      exact le_norm _ },
    { rw [f.supported_in _ ha, norm_zero] }
  end }

noncomputable! def to_Lp [measurable_space 𝕜] [opens_measurable_space 𝕜]
  {m : measurable_space E} [opens_measurable_space E] [measurable_space F]
  [second_countable_topology F] [borel_space F] (p : ℝ≥0∞) (μ : measure E) [fact (1 ≤ p)]
  [is_finite_measure_on_compacts μ] :
  (cont_diff_map_supported_in 𝕜 E F K n) →L[𝕜] (Lp F p μ) :=
{ to_fun := λ f, (f.mem_ℒp p μ).to_Lp f,
  map_add' := λ f g, (f.mem_ℒp p μ).to_Lp_add (g.mem_ℒp p μ),
  map_smul' := λ c f, (f.mem_ℒp p μ).to_Lp_const_smul c,
  cont := ((to_Lp_zero p μ).comp (cont_diff_map_supported_in.of_leL (zero_le _))).continuous }

end compact

section real

variables {E F G : Type*} [normed_group E] [normed_group F] [normed_group G]
  [normed_space ℝ E] [normed_space ℝ F] [normed_space ℝ G] {K : compacts E}
  {n : with_top ℕ} {f g : cont_diff_map_supported_in ℝ E F K n} {x : E}

lemma continuous_iff_of_linear (T : cont_diff_map_supported_in ℝ E F K n →ₗ[ℝ] G) :
  continuous T ↔ ∃ (p : ℕ), ∃ C > 0, ∀ f : cont_diff_map_supported_in ℝ E F K n,
    ∥T f∥ ≤ C * (⨆ (i ≤ p) (hin : ↑i ≤ n) (x : E), ∥iterated_fderiv ℝ i f x∥) :=
begin
  rw [continuous_iff_continuous_at_zero, continuous_at, map_zero],
  rw cont_diff_map_supported_in.has_basis_zero.tendsto_iff metric.nhds_basis_ball,
  split,
  { intros H,
    rcases H 1 one_pos with ⟨⟨p, ε⟩, hε, H⟩,
    have hε' : 0 < ε⁻¹ := inv_pos.mpr hε,
    refine ⟨p, ε⁻¹, hε', λ f, _⟩,
    rw [← div_eq_inv_mul, le_div_iff hε, mul_comm, ← real.norm_of_nonneg hε.le,
        ← norm_smul ε (T f), ← T.map_smul _],
    let N : ℝ := ⨆ (i ≤ p) (hin : ↑i ≤ n) (x : E), ∥iterated_fderiv ℝ i f x∥,
    sorry },
  sorry
end

lemma continuous_iff_of_linear' (T : cont_diff_map_supported_in ℝ E F K n →ₗ[ℝ] G) :
  continuous T ↔ ∃ (p : ℕ) (C > 0), ∀ f : cont_diff_map_supported_in ℝ E F K n,
    ∥T f∥ ≤ C * (⨆ (i ≤ p) (hin : ↑i ≤ n) (x : E), ∥iterated_fderiv ℝ i f x∥) :=
begin
  letI : uniform_add_group (cont_diff_map_supported_in ℝ E F K n) := sorry,
  split,
  { sorry },
  { intro h,
    refine seminorm.continuous_from_bounded (snorms ℝ E F K n) (λ _ : fin 1, norm_seminorm ℝ G) _ _,
    rw seminorm.is_bounded_iff_of_directed_dom,
    { intro,
      rcases h with ⟨p, C, hC, hbound⟩,
      refine ⟨⟨p, sorry⟩, C.to_nnreal, sorry, _⟩,
      intros f,
      convert hbound f,
      rw [snorms, seminorm.coe_smul, pi.smul_apply, seminorm_family.comp_apply, seminorm.comp_apply,
          bounded_cont_diff_map.snorms],
      sorry },
    sorry }
end

end real

end cont_diff_map_supported_in