import chess.utils
import data.matrix.notation
import data.set.finite

/-!

# Definitions and theorems about the chess board field

## Summary

The field on which chess pieces are placed is a 2D plane, where each
position corresponds to a piece index. This is because we think of
defining pieces and moves, usually, by indicating which position
they are at, and which position they are moved to.

## Main definitions

1. The playfield itself (`playfield`)
2. Conversion from a `matrix` of (possibly) occupied spaces to a `playfield`
3. Moving a piece by switching the indices at two specified positions using `move_piece`
4. Making a sequence of moves at once using `move_sequence`

## Implementation details

1. The `playfield` type itself has no requirements to be finite in any dimension,
or that the indices used are finite. We represent the actual index wrapped by
`option`, such that the empty square can be an `option.none`. The playfield definition
wraps the two types used to define the dimensions of the board into a pair.

2. In the current implementation, the way to construct a `playfield` is to provide
a matrix. This limits the `playfield` to a finite 2D plane. Another possible implementation
is of a "sparse matrix", where for each index, we can look up where the piece is.
This now allows for an infinite playfield, but still complicates using infinite pieces.
For now, the closely-tied `matrix` definition makes `playfield` a light type wrapper
on top of `matrix`, i.e. a function of two variables.

3. Currently, `move_piece` just swaps the (potentially absent) indices at two positions.
This is done by using an `equiv.swap` as an updating function. For now, this means that
moves that use `move_piece` are non-capturing. Additionally, no math or other requirements
on the positions or their contents is required. This means that `move_piece` supports a
move from a position to itself. A separate `move` is defined in `chess.move` that has
more chess-like rule constraints.

4. Index presence on the board is not limited to have each index on at-most-one position.
Preventing duplication of indices is not enforced by the `playfield` itself. However,
any given position can hold at-most-one index on it. The actual chess-like rule constraints
are in `chess.board`.

5. Sequences of moves are implemented on top of `move`s, rather than vice versa (`move`s
being defined as sequences of length one). This *probably* causes a bit of duplication,
which may warrant flipping things later.

-/

-- The height and width of the playfield
variables (m n : Type*)
-- The index type at (possibly) each position
variables (ι : Type*)

/--
A `playfield m n ι` represents a `matrix (m × n) option ι`, which is
a model for a `m × n` shaped game board where not every square is
occupied.
-/
def playfield : Type* := m × n → option ι

section playfield

-- The dimensions and index type of the playfield can be assumed
variables {m n ι}

/--
A conversion function to turn a bare `matrix` into a `playfield`.
A `matrix` requires the dimensions to be finite.

An example empty 3 × 3 playfield for 4 pieces could be generated by:
```
matrix_to_playfield ((
  ![![none, none, none],
    ![none, none, none],
    ![none, none, none]] : matrix (fin 3) (fin 3) (option (fin 4))
```

where the positions are 0-indexed, with the origin in the top-left,
first dimension for the row, and second dimension for the column
(0,0) (0,1) (0,2)
(1,0) (1,1) (1,2)
(2,0) (2,1) (2,2)

-/
def matrix_to_playfield [fintype m] [fintype n]
  (M : matrix m n (option ι)) : playfield m n ι :=
λ ⟨x, y⟩, M x y

-- Provide a short notation to be used for `playfield` construction when
-- using matrix notation
notation `PF` M := matrix_to_playfield M

/--
A `playfield` is by default `inhabited` by empty squares everywhere.
-/
instance playfield.inhabited : inhabited (playfield m n ι) :=
⟨λ ⟨x, y⟩, none⟩

-- Definitions and lemmas in this section will be accessible as `pf.___`
-- for a `pf : playfield m n ι`
namespace playfield

section repr

-- The size of the "vectors" for a `fin n' → ι`, for `has_repr` definitions
variables {m' n' : ℕ}
-- Require an index to be `repr`esentable to be able to represent a "vector" of it
variable [has_repr ι]

-- A finite `playfield` is just a uncurried `matrix`.
instance playfield_repr_instance :
  has_repr (playfield (fin n') (fin m') ι) := ⟨chess.utils.matrix_repr ∘ function.curry⟩

end repr

/--
A piece, identified by an index, is on the board, if there is any position
such that the index at that position is the one we're inquiring about.
Providing a `has_mem` instance allows using `ix ∈ pf` for `ix : ι, pf : playfield m n ι`.
This definition does not preclude duplicated indices on the playfield.
See "Implementation details".
-/
instance : has_mem ι (playfield m n ι) :=
⟨λ ix p, ∃ pos, p pos = some ix⟩

instance playfield_decidable_in [fintype m] [fintype n] [decidable_eq ι]
  {pf : playfield m n ι} {ix : ι} : decidable (ix ∈ pf) := fintype.decidable_exists_fintype

instance fintype
  [fintype m] [fintype n] [decidable_eq m] [decidable_eq n]
  [fintype ι] :
  fintype (playfield m n ι) := pi.fintype

-- Fix a `pf : playfield m n ι` to use in definitions and lemmas below
variables (pf : playfield m n ι)

section occupied

/--
A wrapper to indicate that there is some `ix : ι` such that
for a `pf : playfield m n ι`, at `pos : m × n`, `pf pos = some ix`.
-/
@[reducible] def occupied_at (pos : m × n) : Prop := ∃ ix, pf pos = some ix

/--
The predicate that `pf.occupied_at pos` for some pos is decidable
if the indices `ix : ι` are finite and decidably equal.
-/
instance occupied_at.decidable_pred [fintype ι] [decidable_eq ι] : decidable_pred pf.occupied_at :=
  set.decidable_set_of (λ (a : m × n), ∃ (ix : ι), pf a = some ix)

variable {pf}

/--
A wrapper to indicate that there is some `ix : ι` such that
for a `pf : playfield m n ι`, at `pos : m × n`, `pf pos = some ix`.
-/
lemma occupied_at_def {pos : m × n} (h : pf.occupied_at pos) : ∃ ix, pf pos = some ix := h

/--
A wrapper to indicate that there is some `ix : ι` such that
for a `pf : playfield m n ι`, at `pos : m × n`, `pf pos = some ix`.
-/
lemma occupied_at_iff {pos : m × n} : pf.occupied_at pos ↔ ∃ ix, pf pos = some ix := iff.rfl

/--
A `pos : m × n` is unoccupied iff it is `none`.
-/
lemma not_occupied_at_iff {pos : m × n} : ¬ pf.occupied_at pos ↔ pf pos = none :=
by simp only [option.eq_none_iff_forall_not_mem, not_exists, option.mem_def]

/--
A `pf : playfield m n ι` maps any occupied `pos` uniquely.
-/
lemma occupied_at_unique {pos : m × n} (h : pf.occupied_at pos) : ∃! ix : ι, pf pos = some ix :=
begin
  refine exists_unique_of_exists_of_unique h _,
  intros _ _ H H',
  apply option.some.inj,
  rw [←H, ←H']
end

/--
If for some `pf : playfield m n ι`, at `pos : m × n`, `pf pos = some ix`,
then that is equivalent to `pf.occupied_at pos`.
-/
lemma occupied_at_of_some {pos : m × n} {ix : ι} (h : pf pos = some ix) :
  pf.occupied_at pos :=
⟨ix, h⟩

/--
If for some `pf : playfield m n ι`, at `pos : m × n`, `pf.occupied_at pos`,
then for a `pos' : m × n` such that `pf pos = pf pos'`, we have that
`pf.occupied_at pos'`.
-/
lemma occupied_at_transfer {pos pos' : m × n} (h : pf.occupied_at pos) (H : pf pos = pf pos') :
  pf.occupied_at pos' :=
exists.elim h (λ ix hix, ⟨ix, H ▸ hix⟩)

/--
If for some `pf : playfield m n ι`, at `pos : m × n`, `pf pos ≠ none`,
then that is equivalent to `pf.occupied_at pos`.
-/
lemma occupied_at_of_ne {pos : m × n} (hne : pf pos ≠ none) : pf.occupied_at pos :=
option.ne_none_iff_exists'.mp hne

/-- A wrapper API for underlying `option.is_some` propositions. -/
lemma occupied_has_some {pos : m × n} :
  (pf pos).is_some ↔ pf.occupied_at pos :=
option.is_some_iff_exists

/-- A wrapper API for converting between inequalities and existentials. -/
lemma occupied_has_not_none {pos : m × n} :
  pf pos ≠ none ↔ pf.occupied_at pos :=
option.ne_none_iff_exists'

/--
If for some `pf : playfield m n ι`, at `pos : m × n`, `pf pos = none`,
then that is equivalent to `¬ pf.occupied_at pos`.
-/
lemma not_occupied_has_none {pos : m × n} :
  (pf pos).is_none ↔ ¬ pf.occupied_at pos :=
by simpa only [not_occupied_at_iff] using option.is_none_iff_eq_none

variable (pf)

/-- The `set` of all positions that are `occupied_at`. -/
def occupied_positions : set (m × n) := {p : m × n | pf.occupied_at p}

/--
The `pos : m × n` that is in `pf.occupied_positions`
by definition is the proposition that `pf.occupied_at pos`.
-/
@[simp] lemma occupied_positions_in (pos : m × n) :
  pos ∈ pf.occupied_positions ↔ pf.occupied_at pos := iff.rfl

/--
The predicate that `λ p, p ∈ pf.occupied_positions` for some pos is
decidable if the indices `ix : ι` are finite and decidably equal.
-/
instance occcupied_positions_mem_decidable [fintype ι] [decidable_eq ι] :
  decidable_pred (∈ pf.occupied_positions) :=
occupied_at.decidable_pred (λ (pos : m × n), pf pos)

variables [fintype m] [fintype n]

/--
When the `playfield` dimensions are all finite,
the `occupied_positions`–all positions that are `occupied_at`–is a `fintype`.
-/
lemma finite_occupied : pf.occupied_positions.finite :=
set.finite.of_fintype pf.occupied_positions

variables [fintype ι] [decidable_eq ι]

/--
When the `playfield` dimensions are all finite,
the `occupied_positions_set` of all positions that are `occupied_at` is finite.
-/
instance fintype_occupied : fintype (pf.occupied_positions) :=
by { unfold occupied_positions, exact set_fintype _ }

/--
The `finset` of all positions that are `occupied_at`,
when all the dimensions of the `playfield` are `fintype`.
-/
def occupied_position_finset : finset (m × n) := pf.occupied_positions.to_finset

end occupied

section injective

/-- A `playfield` on which every index that appears, appears only once. -/
def some_injective : Prop :=
∀ ⦃a₁⦄, pf.occupied_at a₁ → ∀ ⦃a₂⦄, pf a₁ = pf a₂ → a₁ = a₂

/-- The injectivity of `some_injective` is equivalent to the `set.inj_on` proposition. -/
lemma inj_on_occupied : pf.some_injective ↔ set.inj_on pf pf.occupied_positions :=
⟨λ h _ hp _ _ H, h hp H, λ h _ hp _ H, h hp (occupied_at_transfer hp H) H⟩

/--
Explicitly state that the proposition that `pf.some_injective`
is `decidable`, when the `ι` is itself `decidable_eq`.
-/
instance some_injective_decidable
  [fintype m] [fintype n] [decidable_eq m] [decidable_eq n]
  [fintype ι] [decidable_eq ι] :
  decidable (pf.some_injective) := fintype.decidable_forall_fintype

variable (h_inj : pf.some_injective)
include h_inj

/--
When a `pf : playfield m n ι` is `some_injective`,
if it is occupied at some `pos : m × n`, then it
is injective at that `pos`.
-/
protected lemma inj_iff {pos pos'} (h : pf.occupied_at pos) :
pf pos = pf pos' ↔ pos = pos' :=
⟨λ H, h_inj h H, congr_arg pf⟩

/--
When a `pf : playfield m n ι` is `some_injective`,
if it is not empty at some `pos : m × n`, then it
is injective at that `pos`.
-/
protected lemma injective {pos pos'} {ix : ι} (h : pf pos = some ix) (h' : pf pos' = some ix) :
  pos = pos' :=
begin
  apply h_inj,
  { exact ⟨_, h⟩ },
  { rw [h, h'] }
end

/--
When a `pf : playfield m n ι` is `some_injective`, every index
`ix : ι ∈ pf` exists in the `pf` uniquely.
-/
lemma unique_of_injective {ix : ι} (h : ix ∈ pf) :
  ∃! pos, pf pos = some ix :=
begin
  refine exists_unique_of_exists_of_unique h _,
  intros _ _,
  exact pf.injective h_inj
end

end injective

section decidable

variables [fintype m] [fintype n]
variables [fintype ι] [decidable_eq ι]

/--
The `occupied_positions` of a `pf : playfield m n ι` are finite
if the dimensions of the playfield and the indices are finite.
-/
instance occupied_fintype : fintype pf.occupied_positions := subtype.fintype pf.occupied_at

end decidable

/-- A `pos : pf.occupied_positions` can be used as a `pos : m × n`. -/
lemma coe_occ_val {pos : pf.occupied_positions} : (pos : m × n) = pos.val := rfl

/--
A `pos : pf.occupied_positions` has the property
that there is an not-necessarily-unique `ix : ι` such that `pf pos = some ix`.
-/
lemma exists_of_occupied (pos : pf.occupied_positions) : pf.occupied_at pos :=
pos.property

/--
A `pos : pf.occupied_positions` has the property
that there is a necessarily-unique `ix : ι` such that `pf pos = some ix`.
-/
lemma exists_unique_of_occupied (pos : pf.occupied_positions) : ∃! ix : ι, pf pos = ix :=
occupied_at_unique (pf.exists_of_occupied pos)

/-- A `pos : pf.occupied_positions` has the property that `pf pos` is occupied. -/
lemma occupied_is_some (pos : pf.occupied_positions) : (pf pos).is_some :=
occupied_has_some.mpr pos.property

variables (pf)

/--
Given some `ix : ι` such that for `pf : playfield m n ι` and `pos : m × n`,
`pf pos = some ix`, we can subtype into `pos : pf.occupied_positions`.
-/
def occupied_positions.mk {pos : m × n} {ix : ι} (h : pf pos = some ix) : pf.occupied_positions :=
⟨pos, exists.intro ix h⟩

/--
Given some `ix : ι` such that for `pf : playfield m n ι` and `pos : m × n`,
`pf pos = some ix`, we can subtype into `pos : pf.occupied_positions`.
-/
lemma occupied_positions_def {pos : m × n} {ix : ι} {h : pf pos = some ix} :
  pf (occupied_positions.mk pf h) = pf pos := rfl

/-- The injectivity of `pf.some_injective` extends to the `pf.occupied_positions` subtype. -/
lemma occupied_some_injective (h_inj : pf.some_injective) :
  ∀ ⦃a₁ a₂ : pf.occupied_positions⦄, pf a₁ = pf a₂ → a₁ = a₂ :=
λ p p' h, subtype.eq (h_inj p.property h)

/--
When a `pf : playfield m n ι` is `some_injective`, every `pos : pf.occupied_positions`
maps to a unique index via `pf pos`.
-/
lemma unique_of_occupied (h_inj : pf.some_injective) (pos : pf.occupied_positions) :
  ∃! (pos' : pf.occupied_positions), pf pos' = pf pos :=
begin
  use pos,
  split,
  { simp only [eq_self_iff_true] },
  { intros _ h,
    exact pf.occupied_some_injective h_inj h }
end

section index_at

variables (pos pos' : pf.occupied_positions)

variables (pf)

/-- Extract the `ix : ι` that is at `pf pos = some ix`. -/
def index_at := option.get (occupied_has_some.mpr pos.property)

variables {pf}

/-- Extract the `ix : ι` that is at `pf pos = some ix`. -/
lemma index_at_def : pf.index_at pos = option.get (occupied_has_some.mpr pos.property) := rfl

/--
For a `pos : pf.occupied_positions`, the wrapped index
given by `pf.index_at pos` is precisely `pf pos`.
-/
@[simp] lemma index_at_some : some (pf.index_at pos) = pf pos :=
by simp only [index_at_def, option.some_get, subtype.val_eq_coe]

/--
For a `pos : pf.occupied_positions`, the wrapped index `ix : ι`
given by `pf.index_at pos` is precisely `pf pos`, in iff form.
-/
@[simp] lemma index_at_iff {ix : ι} : pf pos = some ix ↔ pf.index_at pos = ix :=
⟨λ h, option.some.inj (h ▸ index_at_some pos), λ h, h ▸ (index_at_some pos).symm⟩

/--
For a `pos : m × n`, and the hypothesis that `h : pf pos = some ix`,
the index given by `pf.index_at (occupied_positions.mk _ h)` is precisely `ix`.
-/
@[simp] lemma index_at_mk {pos : m × n} {ix : ι} {h : pf pos = some ix} :
pf.index_at (occupied_positions.mk _ h) = ix :=
begin
  apply option.some.inj,
  rw ←h,
  simp only [index_at_some, pf.occupied_positions_def],
end

/-- The index retrieved via `pf.index_at` is known to be in the `pf`. -/
lemma index_at_in : pf.index_at pos ∈ pf :=
⟨pos, (index_at_some pos).symm⟩

/-- The index retrieved via `pf.index_at` is known to be in the `pf`, in existential format. -/
lemma index_at_exists : ∃ pos', pf pos' = some (pf.index_at pos) :=
⟨pos, (index_at_some pos).symm⟩

/--
The index retrieved via `pf.index_at` is known to be in the `pf`,
in existential format, operating on the `pf.occupied_positions` subtype.
-/
lemma index_at_exists' : ∃ (pos' : pf.occupied_positions), pf pos' = some (pf.index_at pos) :=
⟨pos, (index_at_some pos).symm⟩

/--
The index retrieved via `pf.index_at` is known to be unique in the `pf`,
given an injectivity condition via `pf.some_injective`.
-/
lemma occupied_unique_of_injective (h_inj : pf.some_injective) :
  ∃! (pos' : pf.occupied_positions), pf pos' = some (pf.index_at pos) :=
begin
  refine exists_unique_of_exists_of_unique (index_at_exists' pos) _,
  intros _ _ h h',
  apply pf.occupied_some_injective h_inj,
  rw [h, h']
end

/--
Index retrieval via `pf.index_at` is known to be injective,
given an injectivity condition via `pf.some_injective`.
-/
lemma index_at_inj (h_inj : pf.some_injective) {pos pos' : pf.occupied_positions} :
  pf.index_at pos = pf.index_at pos' ↔ pos = pos' :=
begin
  split,
  { intro h,
    rw ←option.some_inj at h,
    simp only [index_at_some, coe_coe] at h,
    exact pf.occupied_some_injective h_inj h },
  { intro h,
    rw h },
end

/--
Index retrieval via `pf.index_at` is known to be injective,
given an injectivity condition via `pf.some_injective`.
-/
lemma index_at.injective (h_inj : pf.some_injective) : function.injective pf.index_at :=
λ p p' h, (index_at_inj h_inj).mp h

/--
Index retrieval via `pf.index_at` is known to be surjective,
given an surjectivity condition via `function.surjective pf`.
-/
lemma index_at.surjective (h : function.surjective pf) :
  function.surjective pf.index_at :=
begin
  intro ix,
  obtain ⟨pos, hpos⟩ := h ix,
  use pos,
  { exact occupied_at_of_some hpos },
  { apply option.some.inj,
    simpa only [index_at_some] },
end

/--
Index retrieval via `pf` is known to be surjective,
given an surjectivity condition via `function.surjective pf.index_at`
and an unoccupied square somewhere.
-/
lemma index_at.implies_surjective (h : function.surjective pf.index_at)
  {unocc_pos : m × n} (h_unocc : ¬ pf.occupied_at unocc_pos) :
  function.surjective pf :=
begin
  intro ix,
  cases ix,
  { use unocc_pos,
    rwa [←occupied_has_not_none, not_not] at h_unocc },
  { obtain ⟨pos, hpos⟩ := h ix,
    use pos,
    simp only [←hpos, index_at_some] },
end

end index_at

section pos_from

variables (pf)
variables (ix : ι)

/--
A helper subtype definition describing all the positions that match an index.

No inhabited instance exists because the type could be empty,
if none of the positions of the playfield have this index.
-/
@[nolint has_inhabited_instance]
def pos_from_aux : Type* := {pos // pf.index_at pos = ix}

instance : has_coe (pf.pos_from_aux ix) pf.occupied_positions := ⟨subtype.val⟩

/-- A helper subtype definition describing all the positions that match an index. -/
@[simp] lemma pos_from_aux_subtype (pos : pf.pos_from_aux ix) :
  pf.index_at (pos : pf.occupied_positions) = ix := pos.property

/-- A helper set definition describing all the positions that match an index. -/
lemma pos_from_auxf_set (pos : pf.pos_from_aux ix) :
  (pos : pf.occupied_positions) ∈ {pos | pf.index_at pos = ix} := pos.property

/--
Given an injectivity condition of `pf.some_injective`, the type
of `pos : pf.occupied_positions` that identify a particular index is a subsingleton.
-/
lemma subsingleton_pos (h_inj : pf.some_injective) : subsingleton (pf.pos_from_aux ix) :=
begin
  refine subsingleton.intro _,
  intros pos pos',
  apply subtype.eq,
  rw ←index_at_inj h_inj,
  simp only [pos_from_aux_subtype, subtype.val_eq_coe]
end

/--
Given a surjectivity condition of `pf.index_at`, the type
of `pos : pf.occupied_positions` that identify a particular index is a nonempty.
-/
lemma nonempty_pos (surj : function.surjective pf.index_at) : nonempty (pf.pos_from_aux ix) :=
nonempty_subtype.mpr (surj ix)

variables [fintype m] [fintype n]
variables [fintype ι] [decidable_eq ι]

/-- A helper finset definition describing all the positions that match an index. -/
def pos_from_auxf : finset pf.occupied_positions := {pos | pf.index_at pos = ix}.to_finset

/-- A helper finset definition describing all the positions that match an index. -/
lemma pos_from_auxf_finset (pos : pf.pos_from_aux ix) :
  (pos : pf.occupied_positions) ∈ pf.pos_from_auxf ix :=
begin
  unfold pos_from_auxf,
  simp only [set.mem_to_finset],
  exact pos.property,
end

/-- A helper finset definition describing all the positions that match an index. -/
@[simp] lemma pos_from_auxf_in {pos : pf.occupied_positions} (h : pf.index_at pos = ix) :
  pos ∈ pf.pos_from_auxf ix :=
begin
  unfold pos_from_auxf,
  simp only [set.mem_to_finset],
  exact h,
end

variables (h_inj : pf.some_injective) (surj : function.surjective pf.index_at)
include h_inj surj

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`, the type
of `pos : pf.occupied_positions` that identify a particular index is a unique.
-/
lemma unique_pos :
  ∃! (pos : pf.occupied_positions), pos ∈ pf.pos_from_auxf ix ∧ pf.index_at pos = ix :=
begin
  obtain ⟨pos, hpos⟩ := surj ix,
  use pos,
  split,
  { simp only [hpos, and_true, eq_self_iff_true, pos_from_auxf_in] },
  { intros pos' h',
    rw ←index_at_inj h_inj,
    simp only [hpos, h'] }
end

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
we can retrieve the `pos : pf.occupied_positions`
such that `pf.index_at pos = ix`.
-/
def pos_from' (ix : ι) : pf.occupied_positions :=
finset.choose (λ pos, pf.index_at pos = ix) (pf.pos_from_auxf ix) (pf.unique_pos ix h_inj surj)

variable {pf}

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
we can retrieve the `pos : pf.occupied_positions`
such that `pf.index_at pos = ix`.
-/
lemma pos_from_def' : pf.pos_from' h_inj surj ix =
  finset.choose (λ pos, pf.index_at pos = ix) (pf.pos_from_auxf ix) (pf.unique_pos ix h_inj surj) :=
rfl

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
round-tripping to get the `pf.index_at (pf.pos_from' ix _ _)` is exactly `ix`.
-/
@[simp] lemma pos_from_index_at' : pf.index_at (pf.pos_from' h_inj surj ix) = ix :=
by { rw [pos_from_def'], exact finset.choose_property (λ pos, pf.index_at pos = ix) _ _ }

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
round-tripping to get the `pf (pf.pos_from' ix _ _)` is exactly `some ix`,
which goes through the coercion down to `pos : m × n`.
-/
lemma pos_from_at' : pf (pf.pos_from' h_inj surj ix) = some ix :=
by simp only [index_at_iff, pos_from_index_at']

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
the left inverse of `pf.index_at` is `pf.pos_from'`.
-/
lemma pos_from_inv_index_at' : function.left_inverse pf.index_at (pf.pos_from' h_inj surj) :=
λ ix, pos_from_index_at' ix h_inj surj

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
the right inverse of `pf.index_at` is `pf.pos_from'`.
-/
lemma index_at_inv_pos_from' : function.right_inverse pf.index_at (pf.pos_from' h_inj surj) :=
function.right_inverse_of_injective_of_left_inverse
  (index_at.injective h_inj)
  (pos_from_inv_index_at' h_inj surj)

variable (pf)

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`, the type
there exists a `pos : m × n' such that `pf pos = some ix`.
-/
def pos_from (ix : ι) : m × n := pf.pos_from' h_inj surj ix

variable {pf}

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`, the type
there exists a `pos : m × n' such that `pf pos = some ix`.
-/
lemma pos_from_def : pf.pos_from h_inj surj ix = pf.pos_from' h_inj surj ix := rfl

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
round-tripping to get the `pf (pf.pos_from ix _ _)` is exactly `some ix`,
-/
lemma pos_from_at : pf (pf.pos_from h_inj surj ix) = some ix :=
by simp only [pos_from_def, index_at_iff, pos_from_index_at']

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
the position retrieved via `pf.pos_from` means that
the `pf` is `occupied_at` it.
-/
lemma pos_from_occupied : pf.occupied_at (pf.pos_from h_inj surj ix) :=
⟨ix, pos_from_at _ _ _⟩

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
the partial inverse of `pf.pos_from` is `pf` itself.
-/
lemma pos_from_inv : function.is_partial_inv (pf.pos_from h_inj surj) pf :=
begin
  intros ix pos,
  split,
  { intro h,
    apply h_inj,
    { exact pos_from_occupied _ _ _ },
    { rw h,
      exact pos_from_at ix _ _ } },
  { intro h,
    rw ←h,
    exact pos_from_at ix _ _ }
end

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
the function `pf.pos_from` is injective.
-/
lemma pos_from.injective : function.injective (pf.pos_from h_inj surj) :=
function.injective_of_partial_inv (pos_from_inv h_inj surj)

end pos_from

section equiv

variables [fintype m] [fintype n]
variables [fintype ι] [decidable_eq ι]
variables (h_inj : pf.some_injective) (surj : function.surjective pf.index_at)
include h_inj surj

/--
Given a surjectivity condition of `pf.index_at`,
and an injectivity condition of `pf.some_injective`,
there is an explicit equivalence from the indices `ι` to
the type of positions in `pf.occupied_positions`.
-/
def index_equiv : ι ≃ pf.occupied_positions :=
{ to_fun := pf.pos_from' h_inj surj,
  inv_fun := pf.index_at,
  left_inv := pos_from_inv_index_at' h_inj surj,
  right_inv := index_at_inv_pos_from' h_inj surj}

end equiv

-- To be able to state whether two positions are equal
-- we need to be able to make the equality on each of the dimensions `decidable`
variables [decidable_eq m] [decidable_eq n]

section move_piece

-- Fix `start_square` and `end_square : m × n` to use in definitions and lemmas below
variables (start_square end_square : m × n)

/--
Move an (optional) index from `start_square` to `end_square` on a `playfield`,
swapping the indices at those squares.

Does not assume anything about occupancy.
-/
def move_piece : playfield m n ι :=
λ pos, pf (equiv.swap start_square end_square pos)

/--
Equivalent to to `move_piece`, but useful for `rewrite`\ ing.
-/
lemma move_piece_def : pf.move_piece start_square end_square =
    λ pos, pf (equiv.swap start_square end_square pos) := rfl

/--
Moving an (optional) index that was at `start_square` places it at `end_square`
-/
@[simp] lemma move_piece_start :
pf.move_piece start_square end_square start_square = pf end_square :=
by simp only [move_piece_def, equiv.swap_apply_left]

/--
Moving an (optional) index that was at `end_square` places it at `start_square`
-/
@[simp] lemma move_piece_end :
pf.move_piece start_square end_square end_square = pf start_square :=
by simp only [move_piece_def, equiv.swap_apply_right]

/--
Moving an (optional) index retains whatever (optional) indices that were at other squares.
-/
@[simp] lemma move_piece_diff
  {start_square end_square other_square : m × n}
  (ne_start : other_square ≠ start_square)
  (ne_end : other_square ≠ end_square) :
pf.move_piece start_square end_square other_square = pf other_square :=
by simp only [move_piece_def, equiv.swap_apply_of_ne_of_ne ne_start ne_end]

/--
The `pf : playfield m n ι` is `occupied_at start_square` after a `move_piece`
iff it is `occupied_at end_square` before the piece move.
-/
@[simp] lemma move_piece_occupied_start :
(pf.move_piece start_square end_square).occupied_at start_square ↔ pf.occupied_at end_square :=
by simp only [occupied_at, move_piece_start]

/--
The `pf : playfield m n ι` is `occupied_at end_square` after a `move_piece`
iff it is `occupied_at start_square` before the piece move.
-/
@[simp] lemma move_piece_occupied_end :
(pf.move_piece start_square end_square).occupied_at end_square ↔ pf.occupied_at start_square :=
by simp only [occupied_at, move_piece_end]

/--
The `pf : playfield m n ι` is `occupied_at other_square` after a `move_piece`,
for a `pos` that is neither `start_square` nor `end_square`,
iff it is `occupied_at other_square` before the piece move.
-/
@[simp] lemma move_piece_occupied_diff
  {start_square end_square other_square : m × n}
  (ne_start : other_square ≠ start_square)
  (ne_end : other_square ≠ end_square) :
(pf.move_piece start_square end_square).occupied_at other_square ↔ pf.occupied_at other_square :=
by simp only [occupied_at, pf.move_piece_diff ne_start ne_end]

/-- Pieces do not disappear after a `move_piece`. -/
lemma retains_pieces (pf : playfield m n ι) (start_square end_square : m × n) (ix : ι)
  (h_pf : ix ∈ pf) :
    ix ∈ pf.move_piece start_square end_square :=
begin
  obtain ⟨pos, h⟩ := h_pf,
  by_cases hs : pos = start_square;
  by_cases he : pos = end_square,
  { use pos,
    simp [←hs, ←h, he] },
  { use end_square,
    simp [hs, he, ←h] },
  { use start_square,
    simp [hs, he, ←h] },
  { use pos,
    simp [hs, he, ←h] }
end

/--
Each index that is present on the playfield and appears only once,
appears only once after a `move_piece`.
-/
lemma retains_injectivity (pf : playfield m n ι) (h : pf.some_injective)
  {start_square end_square : m × n} (h_occ : pf.occupied_at start_square) :
  (pf.move_piece start_square end_square).some_injective :=
begin
  intros pos h_some pos' h_eq,
  rcases split_eq pos start_square end_square with rfl|rfl|⟨hS, hE⟩;
  rcases split_eq pos' start_square end_square with rfl|rfl|⟨hS', hE'⟩,
  { refl },
  { simp only [move_piece_start, move_piece_end] at h_eq,
    exact h h_occ h_eq.symm },
  { simp only [move_piece_diff, hS', hE', move_piece_start,
               move_piece_occupied_start, ne.def, not_false_iff] at h_eq h_some,
    have : pos' = end_square := (h h_some h_eq).symm,
    contradiction },
  { simp only [move_piece_start, move_piece_end] at h_eq,
    exact (h h_occ h_eq).symm },
  { refl },
  { simp only [move_piece_diff _ hS' hE', move_piece_end,
               move_piece_occupied_end] at h_eq h_some,
    have : pos' = start_square := (h h_some h_eq).symm,
    contradiction },
  { simp only [move_piece_diff, hS, hE, move_piece_start,
               move_piece_occupied_diff, ne.def, not_false_iff] at h_eq h_some,
    have : pos = end_square := h h_some h_eq,
    contradiction },
  { simp only [move_piece_diff, hS, hE, move_piece_end,
               move_piece_occupied_diff, ne.def, not_false_iff] at h_eq h_some,
    have : pos = start_square := h h_some h_eq,
    contradiction },
  { simp only [move_piece_diff, hS, hE, hS', hE',
               move_piece_occupied_diff, ne.def, not_false_iff] at h_eq h_some,
    exact h h_some h_eq }
end

/--
If every index and the empty square is present in the `pf : playfield m n ι`,
as given by a `function.surjective pf` proposition, then each index
is present on the playfield after a `move_piece`.
-/
lemma retains_surjectivity (pf : playfield m n ι) (h : function.surjective pf)
  {start_square end_square : m × n} (h_occ : pf.occupied_at start_square) :
  function.surjective (pf.move_piece start_square end_square) :=
begin
  intro ix,
  obtain ⟨six', hsi⟩ := h_occ,
  obtain ⟨P, hP⟩ := h ix,
  rw ←hP,
  rcases split_eq (P : m × n) start_square end_square with H|H|⟨hS, hE⟩,
  { use end_square,
    simp only [move_piece_end, H] },
  { use start_square,
    simp only [H, move_piece_start] },
  { use P,
    simp only [hS, hE, ne.def, not_false_iff, move_piece_diff] }
end

/--
If every index and the empty square is present in the `pf : playfield m n ι`,
as given by a `function.surjective pf` proposition, then each index
is present on the playfield after a `move_piece`.
-/
lemma index_at_retains_surjectivity (pf : playfield m n ι) (h : function.surjective pf.index_at)
  {start_square end_square : m × n} (h_occ : pf.occupied_at start_square)  :
  function.surjective (pf.move_piece start_square end_square).index_at :=
begin
  intro ix,
  obtain ⟨P, hP⟩ := h ix,
  simp_rw [←index_at_iff, ←hP],
  rcases split_eq (P : m × n) start_square end_square with H|H|⟨hS, hE⟩,
  { use end_square,
    { simpa only [move_piece_occupied_end, occupied_positions_in] using h_occ },
    { simp only [H, index_at_some, subtype.coe_mk, move_piece_end] } },
  { use start_square,
    { simpa only [H, move_piece_occupied_start, occupied_positions_in] using P.property },
    { simp only [H, move_piece_start, index_at_some, subtype.coe_mk] } },
  { use P,
    { simpa only [hS, hE, ne.def, not_false_iff,
                  move_piece_occupied_diff, occupied_positions_in] using P.property},
    { simp only [hS, hE, index_at_some, ne.def, not_false_iff,
                 move_piece_diff, subtype.coe_mk] } }
end

end move_piece

section move_sequence

-- The length of the sequence
variables {o : ℕ}
-- Fix a sequence of start and end squares.
variables (seq : vector ((m × n) × (m × n)) o)

/-- Make a sequence of `move`s all at once. -/
def move_sequence : fin (o + 1) → playfield m n ι :=
(vector.scanl (λ acc (x : prod _ _), move_piece acc x.fst x.snd) pf seq).nth

/--
Equivalent to to `move_sequence`, but useful for `rewrite`\ ing.
-/
lemma move_sequence_def : pf.move_sequence seq =
  (vector.scanl (λ acc (x : prod _ _), move_piece acc x.fst x.snd) pf seq).nth := rfl

/--
Throughout a sequence, moving an (optional) index that was at
`start_square` places it at `end_square` on the next board.
-/
lemma move_sequence_start (e : fin o) :
((pf.move_sequence seq) e.cast_succ) (seq.nth e).fst = ((pf.move_sequence seq) e.succ) (seq.nth e).snd :=
by simp only [move_sequence_def, vector.scanl_nth, move_piece_end]

/--
Throughout a sequence, moving an (optional) index that was at
`end_square` places it at `start_square` on the next board.
-/
lemma move_sequence_end (e : fin o) :
((pf.move_sequence seq) e.cast_succ) (seq.nth e).snd = ((pf.move_sequence seq) e.succ) (seq.nth e).fst :=
by simp only [move_sequence_def, vector.scanl_nth, move_piece_start]

/--
Throughout a sequence, moving an (optional) index retains whatever
(optional) indices that were at other squares on the next board.
-/
@[simp] lemma move_sequence_diff
  (other_square : m × n)
  (e : fin o)
  (ne_start : other_square ≠ (seq.nth e).fst)
  (ne_end : other_square ≠ (seq.nth e).snd) :
  ((pf.move_sequence seq) e.cast_succ) other_square = ((pf.move_sequence seq) e.succ) other_square :=
by simp only [move_sequence_def, vector.scanl_nth, ne_start, ne_end,
              ne.def, not_false_iff, move_piece_diff]

end move_sequence

end playfield

end playfield


/-
Copyright (c) 2020 Floris van Doorn. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Floris van Doorn
-/
import measure_theory.measure.giry_monad
import dynamics.ergodic.measure_preserving
import measure_theory.integral.set_integral

/-!
# The product measure

In this file we define and prove properties about the binary product measure. If `α` and `β` have
σ-finite measures `μ` resp. `ν` then `α × β` can be equipped with a σ-finite measure `μ.prod ν` that
satisfies `(μ.prod ν) s = ∫⁻ x, ν {y | (x, y) ∈ s} ∂μ`.
We also have `(μ.prod ν) (s ×ˢ t) = μ s * ν t`, i.e. the measure of a rectangle is the product of
the measures of the sides.

We also prove Tonelli's theorem and Fubini's theorem.

## Main definition

* `measure_theory.measure.prod`: The product of two measures.

## Main results

* `measure_theory.measure.prod_apply` states `μ.prod ν s = ∫⁻ x, ν {y | (x, y) ∈ s} ∂μ`
  for measurable `s`. `measure_theory.measure.prod_apply_symm` is the reversed version.
* `measure_theory.measure.prod_prod` states `μ.prod ν (s ×ˢ t) = μ s * ν t` for measurable sets
  `s` and `t`.
* `measure_theory.lintegral_prod`: Tonelli's theorem. It states that for a measurable function
  `α × β → ℝ≥0∞` we have `∫⁻ z, f z ∂(μ.prod ν) = ∫⁻ x, ∫⁻ y, f (x, y) ∂ν ∂μ`. The version
  for functions `α → β → ℝ≥0∞` is reversed, and called `lintegral_lintegral`. Both versions have
  a variant with `_symm` appended, where the order of integration is reversed.
  The lemma `measurable.lintegral_prod_right'` states that the inner integral of the right-hand side
  is measurable.
* `measure_theory.integrable_prod_iff` states that a binary function is integrable iff both
  * `y ↦ f (x, y)` is integrable for almost every `x`, and
  * the function `x ↦ ∫ ∥f (x, y)∥ dy` is integrable.
* `measure_theory.integral_prod`: Fubini's theorem. It states that for a integrable function
  `α × β → E` (where `E` is a second countable Banach space) we have
  `∫ z, f z ∂(μ.prod ν) = ∫ x, ∫ y, f (x, y) ∂ν ∂μ`. This theorem has the same variants as
  Tonelli's theorem. The lemma `measure_theory.integrable.integral_prod_right` states that the
  inner integral of the right-hand side is integrable.

## Implementation Notes

Many results are proven twice, once for functions in curried form (`α → β → γ`) and one for
functions in uncurried form (`α × β → γ`). The former often has an assumption
`measurable (uncurry f)`, which could be inconvenient to discharge, but for the latter it is more
common that the function has to be given explicitly, since Lean cannot synthesize the function by
itself. We name the lemmas about the uncurried form with a prime.
Tonelli's theorem and Fubini's theorem have a different naming scheme, since the version for the
uncurried version is reversed.

## Tags

product measure, Fubini's theorem, Tonelli's theorem, Fubini-Tonelli theorem
-/

noncomputable theory
open_locale classical topological_space ennreal measure_theory
open set function real ennreal
open measure_theory measurable_space measure_theory.measure
open topological_space (hiding generate_from)
open filter (hiding prod_eq map)

variables {α α' β β' γ E : Type*}

/-- Rectangles formed by π-systems form a π-system. -/
lemma is_pi_system.prod {C : set (set α)} {D : set (set β)} (hC : is_pi_system C)
  (hD : is_pi_system D) : is_pi_system (image2 (×ˢ) C D) :=
begin
  rintro _ ⟨s₁, t₁, hs₁, ht₁, rfl⟩ _ ⟨s₂, t₂, hs₂, ht₂, rfl⟩ hst,
  rw [prod_inter_prod] at hst ⊢, rw [prod_nonempty_iff] at hst,
  exact mem_image2_of_mem (hC _ hs₁ _ hs₂ hst.1) (hD _ ht₁ _ ht₂ hst.2)
end

/-- Rectangles of countably spanning sets are countably spanning. -/
lemma is_countably_spanning.prod {C : set (set α)} {D : set (set β)}
  (hC : is_countably_spanning C) (hD : is_countably_spanning D) :
  is_countably_spanning (image2 (×ˢ) C D) :=
begin
  rcases ⟨hC, hD⟩ with ⟨⟨s, h1s, h2s⟩, t, h1t, h2t⟩,
  refine ⟨λ n, (s n.unpair.1) ×ˢ (t n.unpair.2), λ n, mem_image2_of_mem (h1s _) (h1t _), _⟩,
  rw [Union_unpair_prod, h2s, h2t, univ_prod_univ]
end

variables [measurable_space α] [measurable_space α'] [measurable_space β] [measurable_space β']
variables [measurable_space γ]
variables {μ : measure α} {ν : measure β} {τ : measure γ}
variables [normed_group E] [measurable_space E]

/-! ### Measurability

Before we define the product measure, we can talk about the measurability of operations on binary
functions. We show that if `f` is a binary measurable function, then the function that integrates
along one of the variables (using either the Lebesgue or Bochner integral) is measurable.
-/

/-- The product of generated σ-algebras is the one generated by rectangles, if both generating sets
  are countably spanning. -/
lemma generate_from_prod_eq {α β} {C : set (set α)} {D : set (set β)}
  (hC : is_countably_spanning C) (hD : is_countably_spanning D) :
  @prod.measurable_space _ _ (generate_from C) (generate_from D) =
    generate_from (image2 (×ˢ) C D) :=
begin
  apply le_antisymm,
  { refine sup_le _ _; rw [comap_generate_from];
      apply generate_from_le; rintro _ ⟨s, hs, rfl⟩,
    { rcases hD with ⟨t, h1t, h2t⟩,
      rw [← prod_univ, ← h2t, prod_Union],
      apply measurable_set.Union,
      intro n, apply measurable_set_generate_from,
      exact ⟨s, t n, hs, h1t n, rfl⟩ },
    { rcases hC with ⟨t, h1t, h2t⟩,
      rw [← univ_prod, ← h2t, Union_prod_const],
      apply measurable_set.Union,
      rintro n, apply measurable_set_generate_from,
      exact mem_image2_of_mem (h1t n) hs } },
  { apply generate_from_le, rintro _ ⟨s, t, hs, ht, rfl⟩, rw [prod_eq],
    apply (measurable_fst _).inter (measurable_snd _),
    { exact measurable_set_generate_from hs },
    { exact measurable_set_generate_from ht } }
end

/-- If `C` and `D` generate the σ-algebras on `α` resp. `β`, then rectangles formed by `C` and `D`
  generate the σ-algebra on `α × β`. -/
lemma generate_from_eq_prod {C : set (set α)} {D : set (set β)} (hC : generate_from C = ‹_›)
  (hD : generate_from D = ‹_›) (h2C : is_countably_spanning C) (h2D : is_countably_spanning D) :
    generate_from (image2 (×ˢ) C D) = prod.measurable_space :=
by rw [← hC, ← hD, generate_from_prod_eq h2C h2D]

/-- The product σ-algebra is generated from boxes, i.e. `s ×ˢ t` for sets `s : set α` and
  `t : set β`. -/
lemma generate_from_prod :
  generate_from (image2 (×ˢ) {s : set α | measurable_set s} {t : set β | measurable_set t}) =
  prod.measurable_space :=
generate_from_eq_prod generate_from_measurable_set generate_from_measurable_set
  is_countably_spanning_measurable_set is_countably_spanning_measurable_set

/-- Rectangles form a π-system. -/
lemma is_pi_system_prod :
  is_pi_system (image2 (×ˢ) {s : set α | measurable_set s} {t : set β | measurable_set t}) :=
is_pi_system_measurable_set.prod is_pi_system_measurable_set

/-- If `ν` is a finite measure, and `s ⊆ α × β` is measurable, then `x ↦ ν { y | (x, y) ∈ s }` is
  a measurable function. `measurable_measure_prod_mk_left` is strictly more general. -/
lemma measurable_measure_prod_mk_left_finite [is_finite_measure ν] {s : set (α × β)}
  (hs : measurable_set s) : measurable (λ x, ν (prod.mk x ⁻¹' s)) :=
begin
  refine induction_on_inter generate_from_prod.symm is_pi_system_prod _ _ _ _ hs,
  { simp [measurable_zero, const_def] },
  { rintro _ ⟨s, t, hs, ht, rfl⟩, simp only [mk_preimage_prod_right_eq_if, measure_if],
    exact measurable_const.indicator hs },
  { intros t ht h2t,
    simp_rw [preimage_compl, measure_compl (measurable_prod_mk_left ht) (measure_ne_top ν _)],
    exact h2t.const_sub _ },
  { intros f h1f h2f h3f, simp_rw [preimage_Union],
    have : ∀ b, ν (⋃ i, prod.mk b ⁻¹' f i) = ∑' i, ν (prod.mk b ⁻¹' f i) :=
      λ b, measure_Union (λ i j hij, disjoint.preimage _ (h1f i j hij))
        (λ i, measurable_prod_mk_left (h2f i)),
    simp_rw [this], apply measurable.ennreal_tsum h3f },
end

/-- If `ν` is a σ-finite measure, and `s ⊆ α × β` is measurable, then `x ↦ ν { y | (x, y) ∈ s }` is
  a measurable function. -/
lemma measurable_measure_prod_mk_left [sigma_finite ν] {s : set (α × β)}
  (hs : measurable_set s) : measurable (λ x, ν (prod.mk x ⁻¹' s)) :=
begin
  have : ∀ x, measurable_set (prod.mk x ⁻¹' s) := λ x, measurable_prod_mk_left hs,
  simp only [← @supr_restrict_spanning_sets _ _ ν, this],
  apply measurable_supr, intro i,
  haveI := fact.mk (measure_spanning_sets_lt_top ν i),
  exact measurable_measure_prod_mk_left_finite hs
end

/-- If `μ` is a σ-finite measure, and `s ⊆ α × β` is measurable, then `y ↦ μ { x | (x, y) ∈ s }` is
  a measurable function. -/
lemma measurable_measure_prod_mk_right {μ : measure α} [sigma_finite μ] {s : set (α × β)}
  (hs : measurable_set s) : measurable (λ y, μ ((λ x, (x, y)) ⁻¹' s)) :=
measurable_measure_prod_mk_left (measurable_set_swap_iff.mpr hs)

lemma measurable.map_prod_mk_left [sigma_finite ν] : measurable (λ x : α, map (prod.mk x) ν) :=
begin
  apply measurable_of_measurable_coe, intros s hs,
  simp_rw [map_apply measurable_prod_mk_left hs],
  exact measurable_measure_prod_mk_left hs
end

lemma measurable.map_prod_mk_right {μ : measure α} [sigma_finite μ] :
  measurable (λ y : β, map (λ x : α, (x, y)) μ) :=
begin
  apply measurable_of_measurable_coe, intros s hs,
  simp_rw [map_apply measurable_prod_mk_right hs],
  exact measurable_measure_prod_mk_right hs
end

/-- The Lebesgue integral is measurable. This shows that the integrand of (the right-hand-side of)
  Tonelli's theorem is measurable. -/
lemma measurable.lintegral_prod_right' [sigma_finite ν] :
  ∀ {f : α × β → ℝ≥0∞} (hf : measurable f), measurable (λ x, ∫⁻ y, f (x, y) ∂ν) :=
begin
  have m := @measurable_prod_mk_left,
  refine measurable.ennreal_induction _ _ _,
  { intros c s hs, simp only [← indicator_comp_right],
    suffices : measurable (λ x, c * ν (prod.mk x ⁻¹' s)),
    { simpa [lintegral_indicator _ (m hs)] },
    exact (measurable_measure_prod_mk_left hs).const_mul _ },
  { rintro f g - hf hg h2f h2g, simp_rw [pi.add_apply, lintegral_add (hf.comp m) (hg.comp m)],
    exact h2f.add h2g },
  { intros f hf h2f h3f,
    have := measurable_supr h3f,
    have : ∀ x, monotone (λ n y, f n (x, y)) := λ x i j hij y, h2f hij (x, y),
    simpa [lintegral_supr (λ n, (hf n).comp m), this] }
end

/-- The Lebesgue integral is measurable. This shows that the integrand of (the right-hand-side of)
  Tonelli's theorem is measurable.
  This version has the argument `f` in curried form. -/
lemma measurable.lintegral_prod_right [sigma_finite ν] {f : α → β → ℝ≥0∞}
  (hf : measurable (uncurry f)) : measurable (λ x, ∫⁻ y, f x y ∂ν) :=
hf.lintegral_prod_right'

/-- The Lebesgue integral is measurable. This shows that the integrand of (the right-hand-side of)
  the symmetric version of Tonelli's theorem is measurable. -/
lemma measurable.lintegral_prod_left' [sigma_finite μ] {f : α × β → ℝ≥0∞}
  (hf : measurable f) : measurable (λ y, ∫⁻ x, f (x, y) ∂μ) :=
(measurable_swap_iff.mpr hf).lintegral_prod_right'

/-- The Lebesgue integral is measurable. This shows that the integrand of (the right-hand-side of)
  the symmetric version of Tonelli's theorem is measurable.
  This version has the argument `f` in curried form. -/
lemma measurable.lintegral_prod_left [sigma_finite μ] {f : α → β → ℝ≥0∞}
  (hf : measurable (uncurry f)) : measurable (λ y, ∫⁻ x, f x y ∂μ) :=
hf.lintegral_prod_left'

lemma measurable_set_integrable [sigma_finite ν] [opens_measurable_space E] ⦃f : α → β → E⦄
  (hf : measurable (uncurry f)) : measurable_set { x | integrable (f x) ν } :=
begin
  simp_rw [integrable, hf.of_uncurry_left.ae_measurable, true_and],
  exact measurable_set_lt (measurable.lintegral_prod_right hf.ennnorm) measurable_const
end

section
variables [second_countable_topology E] [normed_space ℝ E]
  [complete_space E] [borel_space E]

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  Fubini's theorem is measurable.
  This version has `f` in curried form. -/
lemma measurable.integral_prod_right [sigma_finite ν] ⦃f : α → β → E⦄
  (hf : measurable (uncurry f)) : measurable (λ x, ∫ y, f x y ∂ν) :=
begin
  let s : ℕ → simple_func (α × β) E := simple_func.approx_on _ hf univ _ (mem_univ 0),
  let s' : ℕ → α → simple_func β E := λ n x, (s n).comp (prod.mk x) measurable_prod_mk_left,
  let f' : ℕ → α → E := λ n, {x | integrable (f x) ν}.indicator
    (λ x, (s' n x).integral ν),
  have hf' : ∀ n, measurable (f' n),
  { intro n, refine measurable.indicator _ (measurable_set_integrable hf),
    have : ∀ x, (s' n x).range.filter (λ x, x ≠ 0) ⊆ (s n).range,
    { intros x, refine finset.subset.trans (finset.filter_subset _ _) _, intro y,
      simp_rw [simple_func.mem_range], rintro ⟨z, rfl⟩, exact ⟨(x, z), rfl⟩ },
    simp only [simple_func.integral_eq_sum_of_subset (this _)],
    refine finset.measurable_sum _ (λ x _, _),
    refine (measurable.ennreal_to_real _).smul_const _,
    simp only [simple_func.coe_comp, preimage_comp] {single_pass := tt},
    apply measurable_measure_prod_mk_left,
    exact (s n).measurable_set_fiber x },
  have h2f' : tendsto f' at_top (𝓝 (λ (x : α), ∫ (y : β), f x y ∂ν)),
  { rw [tendsto_pi_nhds], intro x,
    by_cases hfx : integrable (f x) ν,
    { have : ∀ n, integrable (s' n x) ν,
      { intro n, apply (hfx.norm.add hfx.norm).mono' (s' n x).measurable.ae_measurable,
        apply eventually_of_forall, intro y,
        simp_rw [s', simple_func.coe_comp], exact simple_func.norm_approx_on_zero_le _ _ (x, y) n },
      simp only [f', hfx, simple_func.integral_eq_integral _ (this _), indicator_of_mem,
        mem_set_of_eq],
      refine tendsto_integral_of_dominated_convergence (λ y, ∥f x y∥ + ∥f x y∥)
        (λ n, (s' n x).ae_measurable) (hfx.norm.add hfx.norm) _ _,
      { exact λ n, eventually_of_forall (λ y, simple_func.norm_approx_on_zero_le _ _ (x, y) n) },
      { exact eventually_of_forall (λ y, simple_func.tendsto_approx_on _ _ (by simp)) } },
    { simpa [f', hfx, integral_undef] using @tendsto_const_nhds _ _ _ (0 : E) _, } },
  exact measurable_of_tendsto_metric hf' h2f'
end

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  Fubini's theorem is measurable. -/
lemma measurable.integral_prod_right' [sigma_finite ν] ⦃f : α × β → E⦄
  (hf : measurable f) : measurable (λ x, ∫ y, f (x, y) ∂ν) :=
by { rw [← uncurry_curry f] at hf, exact hf.integral_prod_right }

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  the symmetric version of Fubini's theorem is measurable.
  This version has `f` in curried form. -/
lemma measurable.integral_prod_left [sigma_finite μ] ⦃f : α → β → E⦄
  (hf : measurable (uncurry f)) : measurable (λ y, ∫ x, f x y ∂μ) :=
(hf.comp measurable_swap).integral_prod_right'

/-- The Bochner integral is measurable. This shows that the integrand of (the right-hand-side of)
  the symmetric version of Fubini's theorem is measurable. -/
lemma measurable.integral_prod_left' [sigma_finite μ] ⦃f : α × β → E⦄
  (hf : measurable f) : measurable (λ y, ∫ x, f (x, y) ∂μ) :=
(hf.comp measurable_swap).integral_prod_right'

end

/-! ### The product measure -/

namespace measure_theory

namespace measure

/-- The binary product of measures. They are defined for arbitrary measures, but we basically
  prove all properties under the assumption that at least one of them is σ-finite. -/
@[irreducible] protected def prod (μ : measure α) (ν : measure β) : measure (α × β) :=
bind μ $ λ x : α, map (prod.mk x) ν

instance prod.measure_space {α β} [measure_space α] [measure_space β] : measure_space (α × β) :=
{ volume := volume.prod volume }

variables {μ ν} [sigma_finite ν]

lemma volume_eq_prod (α β) [measure_space α] [measure_space β] :
  (volume : measure (α × β)) = (volume : measure α).prod (volume : measure β) :=
rfl

lemma prod_apply {s : set (α × β)} (hs : measurable_set s) :
  μ.prod ν s = ∫⁻ x, ν (prod.mk x ⁻¹' s) ∂μ :=
by simp_rw [measure.prod, bind_apply hs measurable.map_prod_mk_left,
  map_apply measurable_prod_mk_left hs]

/-- The product measure of the product of two sets is the product of their measures. Note that we
do not need the sets to be measurable. -/
@[simp] lemma prod_prod (s : set α) (t : set β) : μ.prod ν (s ×ˢ t) = μ s * ν t :=
begin
  apply le_antisymm,
  { set ST := (to_measurable μ s) ×ˢ (to_measurable ν t),
    have hSTm : measurable_set ST :=
      (measurable_set_to_measurable _ _).prod (measurable_set_to_measurable _ _),
    calc μ.prod ν (s ×ˢ t) ≤ μ.prod ν ST :
      measure_mono $ set.prod_mono (subset_to_measurable _ _) (subset_to_measurable _ _)
    ... = μ (to_measurable μ s) * ν (to_measurable ν t) :
      by simp_rw [prod_apply hSTm, mk_preimage_prod_right_eq_if, measure_if,
        lintegral_indicator _ (measurable_set_to_measurable _ _), lintegral_const,
        restrict_apply_univ, mul_comm]
    ... = μ s * ν t : by rw [measure_to_measurable, measure_to_measurable] },
  { /- Formalization is based on https://mathoverflow.net/a/254134/136589 -/
    set ST := to_measurable (μ.prod ν) (s ×ˢ t),
    have hSTm : measurable_set ST := measurable_set_to_measurable _ _,
    have hST : s ×ˢ t ⊆ ST := subset_to_measurable _ _,
    set f : α → ℝ≥0∞ := λ x, ν (prod.mk x ⁻¹' ST),
    have hfm : measurable f := measurable_measure_prod_mk_left hSTm,
    set s' : set α := {x | ν t ≤ f x},
    have hss' : s ⊆ s' := λ x hx, measure_mono (λ y hy, hST $ mk_mem_prod hx hy),
    calc μ s * ν t ≤ μ s' * ν t : mul_le_mul_right' (measure_mono hss') _
    ... = ∫⁻ x in s', ν t ∂μ    : by rw [set_lintegral_const, mul_comm]
    ... ≤ ∫⁻ x in s', f x ∂μ    : set_lintegral_mono measurable_const hfm (λ x, id)
    ... ≤ ∫⁻ x, f x ∂μ          : lintegral_mono' restrict_le_self le_rfl
    ... = μ.prod ν ST           : (prod_apply hSTm).symm
    ... = μ.prod ν (s ×ˢ t)     : measure_to_measurable _ }
end

lemma ae_measure_lt_top {s : set (α × β)} (hs : measurable_set s)
  (h2s : (μ.prod ν) s ≠ ∞) : ∀ᵐ x ∂μ, ν (prod.mk x ⁻¹' s) < ∞ :=
by { simp_rw [prod_apply hs] at h2s, refine ae_lt_top (measurable_measure_prod_mk_left hs) h2s }

lemma integrable_measure_prod_mk_left {s : set (α × β)}
  (hs : measurable_set s) (h2s : (μ.prod ν) s ≠ ∞) :
  integrable (λ x, (ν (prod.mk x ⁻¹' s)).to_real) μ :=
begin
  refine ⟨(measurable_measure_prod_mk_left hs).ennreal_to_real.ae_measurable, _⟩,
  simp_rw [has_finite_integral, ennnorm_eq_of_real to_real_nonneg],
  convert h2s.lt_top using 1, simp_rw [prod_apply hs], apply lintegral_congr_ae,
  refine (ae_measure_lt_top hs h2s).mp _, apply eventually_of_forall, intros x hx,
  rw [lt_top_iff_ne_top] at hx, simp [of_real_to_real, hx],
end

/-- Note: the assumption `hs` cannot be dropped. For a counterexample, see
  Walter Rudin *Real and Complex Analysis*, example (c) in section 8.9. -/
lemma measure_prod_null {s : set (α × β)}
  (hs : measurable_set s) : μ.prod ν s = 0 ↔ (λ x, ν (prod.mk x ⁻¹' s)) =ᵐ[μ] 0 :=
by simp_rw [prod_apply hs, lintegral_eq_zero_iff (measurable_measure_prod_mk_left hs)]

/-- Note: the converse is not true without assuming that `s` is measurable. For a counterexample,
  see Walter Rudin *Real and Complex Analysis*, example (c) in section 8.9. -/
lemma measure_ae_null_of_prod_null {s : set (α × β)}
  (h : μ.prod ν s = 0) : (λ x, ν (prod.mk x ⁻¹' s)) =ᵐ[μ] 0 :=
begin
  obtain ⟨t, hst, mt, ht⟩ := exists_measurable_superset_of_null h,
  simp_rw [measure_prod_null mt] at ht,
  rw [eventually_le_antisymm_iff],
  exact ⟨eventually_le.trans_eq
    (eventually_of_forall $ λ x, (measure_mono (preimage_mono hst) : _)) ht,
    eventually_of_forall $ λ x, zero_le _⟩
end

/-- Note: the converse is not true. For a counterexample, see
  Walter Rudin *Real and Complex Analysis*, example (c) in section 8.9. -/
lemma ae_ae_of_ae_prod {p : α × β → Prop} (h : ∀ᵐ z ∂μ.prod ν, p z) :
  ∀ᵐ x ∂ μ, ∀ᵐ y ∂ ν, p (x, y) :=
measure_ae_null_of_prod_null h

/-- `μ.prod ν` has finite spanning sets in rectangles of finite spanning sets. -/
def finite_spanning_sets_in.prod {ν : measure β} {C : set (set α)} {D : set (set β)}
  (hμ : μ.finite_spanning_sets_in C) (hν : ν.finite_spanning_sets_in D) :
  (μ.prod ν).finite_spanning_sets_in (image2 (×ˢ) C D) :=
begin
  haveI := hν.sigma_finite,
  refine ⟨λ n, hμ.set n.unpair.1 ×ˢ hν.set n.unpair.2,
    λ n, mem_image2_of_mem (hμ.set_mem _) (hν.set_mem _), λ n, _, _⟩,
  { rw [prod_prod],
    exact mul_lt_top (hμ.finite _).ne (hν.finite _).ne },
  { simp_rw [Union_unpair_prod, hμ.spanning, hν.spanning, univ_prod_univ] }
end

lemma prod_fst_absolutely_continuous : map prod.fst (μ.prod ν) ≪ μ :=
begin
  refine absolutely_continuous.mk (λ s hs h2s, _),
  rw [map_apply measurable_fst hs, ← prod_univ, prod_prod, h2s, zero_mul],
end

lemma prod_snd_absolutely_continuous : map prod.snd (μ.prod ν) ≪ ν :=
begin
  refine absolutely_continuous.mk (λ s hs h2s, _),
  rw [map_apply measurable_snd hs, ← univ_prod, prod_prod, h2s, mul_zero]
end

variables [sigma_finite μ]

instance prod.sigma_finite : sigma_finite (μ.prod ν) :=
(μ.to_finite_spanning_sets_in.prod ν.to_finite_spanning_sets_in).sigma_finite

/-- A measure on a product space equals the product measure if they are equal on rectangles
  with as sides sets that generate the corresponding σ-algebras. -/
lemma prod_eq_generate_from {μ : measure α} {ν : measure β} {C : set (set α)}
  {D : set (set β)} (hC : generate_from C = ‹_›)
  (hD : generate_from D = ‹_›) (h2C : is_pi_system C) (h2D : is_pi_system D)
  (h3C : μ.finite_spanning_sets_in C) (h3D : ν.finite_spanning_sets_in D)
  {μν : measure (α × β)}
  (h₁ : ∀ (s ∈ C) (t ∈ D), μν (s ×ˢ t) = μ s * ν t) : μ.prod ν = μν :=
begin
  refine (h3C.prod h3D).ext
    (generate_from_eq_prod hC hD h3C.is_countably_spanning h3D.is_countably_spanning).symm
    (h2C.prod h2D) _,
  { rintro _ ⟨s, t, hs, ht, rfl⟩, haveI := h3D.sigma_finite,
    rw [h₁ s hs t ht, prod_prod] }
end

/-- A measure on a product space equals the product measure if they are equal on rectangles. -/
lemma prod_eq {μν : measure (α × β)}
  (h : ∀ s t, measurable_set s → measurable_set t → μν (s ×ˢ t) = μ s * ν t) : μ.prod ν = μν :=
prod_eq_generate_from generate_from_measurable_set generate_from_measurable_set
  is_pi_system_measurable_set is_pi_system_measurable_set
  μ.to_finite_spanning_sets_in ν.to_finite_spanning_sets_in (λ s hs t ht, h s t hs ht)

lemma prod_swap : map prod.swap (μ.prod ν) = ν.prod μ :=
begin
  refine (prod_eq _).symm,
  intros s t hs ht,
  simp_rw [map_apply measurable_swap (hs.prod ht), preimage_swap_prod, prod_prod, mul_comm]
end

lemma prod_apply_symm {s : set (α × β)} (hs : measurable_set s) :
  μ.prod ν s = ∫⁻ y, μ ((λ x, (x, y)) ⁻¹' s) ∂ν :=
by { rw [← prod_swap, map_apply measurable_swap hs],
     simp only [prod_apply (measurable_swap hs)], refl }

lemma prod_assoc_prod [sigma_finite τ] :
  map measurable_equiv.prod_assoc ((μ.prod ν).prod τ) = μ.prod (ν.prod τ) :=
begin
  refine (prod_eq_generate_from generate_from_measurable_set generate_from_prod
    is_pi_system_measurable_set is_pi_system_prod μ.to_finite_spanning_sets_in
    (ν.to_finite_spanning_sets_in.prod τ.to_finite_spanning_sets_in) _).symm,
  rintro s hs _ ⟨t, u, ht, hu, rfl⟩, rw [mem_set_of_eq] at hs ht hu,
  simp_rw [map_apply (measurable_equiv.measurable _) (hs.prod (ht.prod hu)),
    measurable_equiv.prod_assoc, measurable_equiv.coe_mk, equiv.prod_assoc_preimage,
    prod_prod, mul_assoc]
end

/-! ### The product of specific measures -/

lemma prod_restrict (s : set α) (t : set β) :
  (μ.restrict s).prod (ν.restrict t) = (μ.prod ν).restrict (s ×ˢ t) :=
begin
  refine prod_eq (λ s' t' hs' ht', _),
  rw [restrict_apply (hs'.prod ht'), prod_inter_prod, prod_prod, restrict_apply hs',
    restrict_apply ht']
end

lemma restrict_prod_eq_prod_univ (s : set α) :
  (μ.restrict s).prod ν = (μ.prod ν).restrict (s ×ˢ (univ : set β)) :=
begin
  have : ν = ν.restrict set.univ := measure.restrict_univ.symm,
  rwa [this, measure.prod_restrict, ← this],
end

lemma prod_dirac (y : β) : μ.prod (dirac y) = map (λ x, (x, y)) μ :=
begin
  refine prod_eq (λ s t hs ht, _),
  simp_rw [map_apply measurable_prod_mk_right (hs.prod ht), mk_preimage_prod_left_eq_if, measure_if,
    dirac_apply' _ ht, ← indicator_mul_right _ (λ x, μ s), pi.one_apply, mul_one]
end

lemma dirac_prod (x : α) : (dirac x).prod ν = map (prod.mk x) ν :=
begin
  refine prod_eq (λ s t hs ht, _),
  simp_rw [map_apply measurable_prod_mk_left (hs.prod ht), mk_preimage_prod_right_eq_if, measure_if,
    dirac_apply' _ hs, ← indicator_mul_left _ _ (λ x, ν t), pi.one_apply, one_mul]
end

lemma dirac_prod_dirac {x : α} {y : β} : (dirac x).prod (dirac y) = dirac (x, y) :=
by rw [prod_dirac, map_dirac measurable_prod_mk_right]

lemma prod_sum {ι : Type*} [fintype ι] (ν : ι → measure β) [∀ i, sigma_finite (ν i)] :
  μ.prod (sum ν) = sum (λ i, μ.prod (ν i)) :=
begin
  refine prod_eq (λ s t hs ht, _),
  simp_rw [sum_apply _ (hs.prod ht), sum_apply _ ht, prod_prod, ennreal.tsum_mul_left]
end

lemma sum_prod {ι : Type*} [fintype ι] (μ : ι → measure α) [∀ i, sigma_finite (μ i)] :
  (sum μ).prod ν = sum (λ i, (μ i).prod ν) :=
begin
  refine prod_eq (λ s t hs ht, _),
  simp_rw [sum_apply _ (hs.prod ht), sum_apply _ hs, prod_prod, ennreal.tsum_mul_right]
end

lemma prod_add (ν' : measure β) [sigma_finite ν'] : μ.prod (ν + ν') = μ.prod ν + μ.prod ν' :=
by { refine prod_eq (λ s t hs ht, _), simp_rw [add_apply, prod_prod, left_distrib] }

lemma add_prod (μ' : measure α) [sigma_finite μ'] : (μ + μ').prod ν = μ.prod ν + μ'.prod ν :=
by { refine prod_eq (λ s t hs ht, _), simp_rw [add_apply, prod_prod, right_distrib] }

@[simp] lemma zero_prod (ν : measure β) : (0 : measure α).prod ν = 0 :=
by { rw measure.prod, exact bind_zero_left _ }

@[simp] lemma prod_zero (μ : measure α) : μ.prod (0 : measure β) = 0 :=
by simp [measure.prod]

lemma map_prod_map {δ} [measurable_space δ] {f : α → β} {g : γ → δ}
  {μa : measure α} {μc : measure γ} (hfa : sigma_finite (map f μa))
  (hgc : sigma_finite (map g μc)) (hf : measurable f) (hg : measurable g) :
  (map f μa).prod (map g μc) = map (prod.map f g) (μa.prod μc) :=
begin
  haveI := hgc.of_map μc hg,
  refine prod_eq (λ s t hs ht, _),
  rw [map_apply (hf.prod_map hg) (hs.prod ht), map_apply hf hs, map_apply hg ht],
  exact prod_prod (f ⁻¹' s) (g ⁻¹' t)
end

end measure

namespace measure_preserving

open measure

variables {δ : Type*} [measurable_space δ] {μa : measure α} {μb : measure β}
  {μc : measure γ} {μd : measure δ}

lemma skew_product [sigma_finite μb] [sigma_finite μd]
  {f : α → β} (hf : measure_preserving f μa μb) {g : α → γ → δ}
  (hgm : measurable (uncurry g)) (hg : ∀ᵐ x ∂μa, map (g x) μc = μd) :
  measure_preserving (λ p : α × γ, (f p.1, g p.1 p.2)) (μa.prod μc) (μb.prod μd) :=
begin
  classical,
  have : measurable (λ p : α × γ, (f p.1, g p.1 p.2)) := (hf.1.comp measurable_fst).prod_mk hgm,
  /- if `μa = 0`, then the lemma is trivial, otherwise we can use `hg`
  to deduce `sigma_finite μc`. -/
  rcases eq_or_ne μa 0 with (rfl|ha),
  { rw [← hf.map_eq, zero_prod, (map f).map_zero, zero_prod],
    exact ⟨this, (map _).map_zero⟩ },
  haveI : sigma_finite μc,
  { rcases (ae_ne_bot.2 ha).nonempty_of_mem hg with ⟨x, hx : map (g x) μc = μd⟩,
    exact sigma_finite.of_map _ hgm.of_uncurry_left (by rwa hx) },
  -- Thus we can apply `measure.prod_eq` to prove equality of measures.
  refine ⟨this, (prod_eq $ λ s t hs ht, _).symm⟩,
  rw [map_apply this (hs.prod ht)],
  refine (prod_apply (this $ hs.prod ht)).trans _,
  have : ∀ᵐ x ∂μa, μc ((λ y, (f x, g x y)) ⁻¹' (s ×ˢ t)) = indicator (f ⁻¹' s) (λ y, μd t) x,
  { refine hg.mono (λ x hx, _), unfreezingI { subst hx },
    simp only [mk_preimage_prod_right_fn_eq_if, indicator_apply, mem_preimage],
    split_ifs,
    exacts [(map_apply hgm.of_uncurry_left ht).symm, measure_empty] },
  simp only [preimage_preimage],
  rw [lintegral_congr_ae this, lintegral_indicator _ (hf.1 hs),
    set_lintegral_const, hf.measure_preimage hs, mul_comm]
end

/-- If `f : α → β` sends the measure `μa` to `μb` and `g : γ → δ` sends the measure `μc` to `μd`,
then `prod.map f g` sends `μa.prod μc` to `μb.prod μd`. -/
protected lemma prod [sigma_finite μb] [sigma_finite μd] {f : α → β} {g : γ → δ}
  (hf : measure_preserving f μa μb) (hg : measure_preserving g μc μd) :
  measure_preserving (prod.map f g) (μa.prod μc) (μb.prod μd) :=
have measurable (uncurry $ λ _ : α, g), from (hg.1.comp measurable_snd),
hf.skew_product this $ filter.eventually_of_forall $ λ _, hg.map_eq

end measure_preserving

end measure_theory

open measure_theory.measure

section

lemma ae_measurable.prod_swap [sigma_finite μ] [sigma_finite ν] {f : β × α → γ}
  (hf : ae_measurable f (ν.prod μ)) : ae_measurable (λ (z : α × β), f z.swap) (μ.prod ν) :=
by { rw ← prod_swap at hf, exact hf.comp_measurable measurable_swap }

lemma ae_measurable.fst [sigma_finite ν] {f : α → γ}
  (hf : ae_measurable f μ) : ae_measurable (λ (z : α × β), f z.1) (μ.prod ν) :=
hf.comp_measurable' measurable_fst prod_fst_absolutely_continuous

lemma ae_measurable.snd [sigma_finite ν] {f : β → γ}
  (hf : ae_measurable f ν) : ae_measurable (λ (z : α × β), f z.2) (μ.prod ν) :=
hf.comp_measurable' measurable_snd prod_snd_absolutely_continuous

/-- The Bochner integral is a.e.-measurable.
  This shows that the integrand of (the right-hand-side of) Fubini's theorem is a.e.-measurable. -/
lemma ae_measurable.integral_prod_right' [sigma_finite ν]
  [second_countable_topology E] [normed_space ℝ E] [borel_space E] [complete_space E]
  ⦃f : α × β → E⦄ (hf : ae_measurable f (μ.prod ν)) : ae_measurable (λ x, ∫ y, f (x, y) ∂ν) μ :=
⟨λ x, ∫ y, hf.mk f (x, y) ∂ν, hf.measurable_mk.integral_prod_right',
  by { filter_upwards [ae_ae_of_ae_prod hf.ae_eq_mk] with _ hx using integral_congr_ae hx }⟩

lemma ae_measurable.prod_mk_left [sigma_finite ν] {f : α × β → γ}
  (hf : ae_measurable f (μ.prod ν)) : ∀ᵐ x ∂μ, ae_measurable (λ y, f (x, y)) ν := by
{ filter_upwards [ae_ae_of_ae_prod hf.ae_eq_mk] with x hx
    using ⟨λ y, hf.mk f (x, y), hf.measurable_mk.comp measurable_prod_mk_left, hx⟩ }

end

namespace measure_theory

/-! ### The Lebesgue integral on a product -/

variables [sigma_finite ν]

lemma lintegral_prod_swap [sigma_finite μ] (f : α × β → ℝ≥0∞)
  (hf : ae_measurable f (μ.prod ν)) : ∫⁻ z, f z.swap ∂(ν.prod μ) = ∫⁻ z, f z ∂(μ.prod ν) :=
by { rw ← prod_swap at hf, rw [← lintegral_map' hf measurable_swap, prod_swap] }

/-- **Tonelli's Theorem**: For `ℝ≥0∞`-valued measurable functions on `α × β`,
  the integral of `f` is equal to the iterated integral. -/
lemma lintegral_prod_of_measurable :
  ∀ (f : α × β → ℝ≥0∞) (hf : measurable f), ∫⁻ z, f z ∂(μ.prod ν) = ∫⁻ x, ∫⁻ y, f (x, y) ∂ν ∂μ :=
begin
  have m := @measurable_prod_mk_left,
  refine measurable.ennreal_induction _ _ _,
  { intros c s hs, simp only [← indicator_comp_right],
    simp [lintegral_indicator, m hs, hs, lintegral_const_mul, measurable_measure_prod_mk_left hs,
      prod_apply] },
  { rintro f g - hf hg h2f h2g,
    simp [lintegral_add, measurable.lintegral_prod_right', hf.comp m, hg.comp m,
      hf, hg, h2f, h2g] },
  { intros f hf h2f h3f,
    have kf : ∀ x n, measurable (λ y, f n (x, y)) := λ x n, (hf n).comp m,
    have k2f : ∀ x, monotone (λ n y, f n (x, y)) := λ x i j hij y, h2f hij (x, y),
    have lf : ∀ n, measurable (λ x, ∫⁻ y, f n (x, y) ∂ν) := λ n, (hf n).lintegral_prod_right',
    have l2f : monotone (λ n x, ∫⁻ y, f n (x, y) ∂ν) := λ i j hij x, lintegral_mono (k2f x hij),
    simp only [lintegral_supr hf h2f, lintegral_supr (kf _), k2f, lintegral_supr lf l2f, h3f] },
end

/-- **Tonelli's Theorem**: For `ℝ≥0∞`-valued almost everywhere measurable functions on `α × β`,
  the integral of `f` is equal to the iterated integral. -/
lemma lintegral_prod (f : α × β → ℝ≥0∞) (hf : ae_measurable f (μ.prod ν)) :
  ∫⁻ z, f z ∂(μ.prod ν) = ∫⁻ x, ∫⁻ y, f (x, y) ∂ν ∂μ :=
begin
  have A : ∫⁻ z, f z ∂(μ.prod ν) = ∫⁻ z, hf.mk f z ∂(μ.prod ν) :=
    lintegral_congr_ae hf.ae_eq_mk,
  have B : ∫⁻ x, ∫⁻ y, f (x, y) ∂ν ∂μ = ∫⁻ x, ∫⁻ y, hf.mk f (x, y) ∂ν ∂μ,
  { apply lintegral_congr_ae,
    filter_upwards [ae_ae_of_ae_prod hf.ae_eq_mk] with _ ha using lintegral_congr_ae ha, },
  rw [A, B, lintegral_prod_of_measurable _ hf.measurable_mk],
  apply_instance
end

/-- The symmetric verion of Tonelli's Theorem: For `ℝ≥0∞`-valued almost everywhere measurable
functions on `α × β`,  the integral of `f` is equal to the iterated integral, in reverse order. -/
lemma lintegral_prod_symm [sigma_finite μ] (f : α × β → ℝ≥0∞)
  (hf : ae_measurable f (μ.prod ν)) : ∫⁻ z, f z ∂(μ.prod ν) = ∫⁻ y, ∫⁻ x, f (x, y) ∂μ ∂ν :=
by { simp_rw [← lintegral_prod_swap f hf], exact lintegral_prod _ hf.prod_swap }

/-- The symmetric verion of Tonelli's Theorem: For `ℝ≥0∞`-valued measurable
functions on `α × β`,  the integral of `f` is equal to the iterated integral, in reverse order. -/
lemma lintegral_prod_symm' [sigma_finite μ] (f : α × β → ℝ≥0∞)
  (hf : measurable f) : ∫⁻ z, f z ∂(μ.prod ν) = ∫⁻ y, ∫⁻ x, f (x, y) ∂μ ∂ν :=
lintegral_prod_symm f hf.ae_measurable

/-- The reversed version of **Tonelli's Theorem**. In this version `f` is in curried form, which
makes it easier for the elaborator to figure out `f` automatically. -/
lemma lintegral_lintegral ⦃f : α → β → ℝ≥0∞⦄
  (hf : ae_measurable (uncurry f) (μ.prod ν)) :
  ∫⁻ x, ∫⁻ y, f x y ∂ν ∂μ = ∫⁻ z, f z.1 z.2 ∂(μ.prod ν) :=
(lintegral_prod _ hf).symm

/-- The reversed version of **Tonelli's Theorem** (symmetric version). In this version `f` is in
curried form, which makes it easier for the elaborator to figure out `f` automatically. -/
lemma lintegral_lintegral_symm [sigma_finite μ] ⦃f : α → β → ℝ≥0∞⦄
  (hf : ae_measurable (uncurry f) (μ.prod ν)) :
  ∫⁻ x, ∫⁻ y, f x y ∂ν ∂μ = ∫⁻ z, f z.2 z.1 ∂(ν.prod μ) :=
(lintegral_prod_symm _ hf.prod_swap).symm

/-- Change the order of Lebesgue integration. -/
lemma lintegral_lintegral_swap [sigma_finite μ] ⦃f : α → β → ℝ≥0∞⦄
  (hf : ae_measurable (uncurry f) (μ.prod ν)) :
  ∫⁻ x, ∫⁻ y, f x y ∂ν ∂μ = ∫⁻ y, ∫⁻ x, f x y ∂μ ∂ν :=
(lintegral_lintegral hf).trans (lintegral_prod_symm _ hf)

lemma lintegral_prod_mul {f : α → ℝ≥0∞} {g : β → ℝ≥0∞}
  (hf : ae_measurable f μ) (hg : ae_measurable g ν) :
  ∫⁻ z, f z.1 * g z.2 ∂(μ.prod ν) = ∫⁻ x, f x ∂μ * ∫⁻ y, g y ∂ν :=
by simp [lintegral_prod _ (hf.fst.mul hg.snd), lintegral_lintegral_mul hf hg]

/-! ### Integrability on a product -/
section

variables [opens_measurable_space E]

lemma integrable.swap [sigma_finite μ] ⦃f : α × β → E⦄
  (hf : integrable f (μ.prod ν)) : integrable (f ∘ prod.swap) (ν.prod μ) :=
⟨hf.ae_measurable.prod_swap,
  (lintegral_prod_swap _ hf.ae_measurable.ennnorm : _).le.trans_lt hf.has_finite_integral⟩

lemma integrable_swap_iff [sigma_finite μ] ⦃f : α × β → E⦄ :
  integrable (f ∘ prod.swap) (ν.prod μ) ↔ integrable f (μ.prod ν) :=
⟨λ hf, by { convert hf.swap, ext ⟨x, y⟩, refl }, λ hf, hf.swap⟩

lemma has_finite_integral_prod_iff ⦃f : α × β → E⦄ (h1f : measurable f) :
  has_finite_integral f (μ.prod ν) ↔ (∀ᵐ x ∂ μ, has_finite_integral (λ y, f (x, y)) ν) ∧
    has_finite_integral (λ x, ∫ y, ∥f (x, y)∥ ∂ν) μ :=
begin
  simp only [has_finite_integral, lintegral_prod_of_measurable _ h1f.ennnorm],
  have : ∀ x, ∀ᵐ y ∂ν, 0 ≤ ∥f (x, y)∥ := λ x, eventually_of_forall (λ y, norm_nonneg _),
  simp_rw [integral_eq_lintegral_of_nonneg_ae (this _)
    (h1f.norm.comp measurable_prod_mk_left).ae_measurable,
    ennnorm_eq_of_real to_real_nonneg, of_real_norm_eq_coe_nnnorm],
  -- this fact is probably too specialized to be its own lemma
  have : ∀ {p q r : Prop} (h1 : r → p), (r ↔ p ∧ q) ↔ (p → (r ↔ q)) :=
  λ p q r h1, by rw [← and.congr_right_iff, and_iff_right_of_imp h1],
  rw [this],
  { intro h2f, rw lintegral_congr_ae,
    refine h2f.mp _, apply eventually_of_forall, intros x hx, dsimp only,
    rw [of_real_to_real], rw [← lt_top_iff_ne_top], exact hx },
  { intro h2f, refine ae_lt_top _ h2f.ne, exact h1f.ennnorm.lintegral_prod_right' },
end

lemma has_finite_integral_prod_iff' ⦃f : α × β → E⦄ (h1f : ae_measurable f (μ.prod ν)) :
  has_finite_integral f (μ.prod ν) ↔ (∀ᵐ x ∂ μ, has_finite_integral (λ y, f (x, y)) ν) ∧
    has_finite_integral (λ x, ∫ y, ∥f (x, y)∥ ∂ν) μ :=
begin
  rw [has_finite_integral_congr h1f.ae_eq_mk, has_finite_integral_prod_iff h1f.measurable_mk],
  apply and_congr,
  { apply eventually_congr,
    filter_upwards [ae_ae_of_ae_prod h1f.ae_eq_mk.symm],
    assume x hx,
    exact has_finite_integral_congr hx },
  { apply has_finite_integral_congr,
    filter_upwards [ae_ae_of_ae_prod h1f.ae_eq_mk.symm] with _ hx
      using integral_congr_ae (eventually_eq.fun_comp hx _), },
  { apply_instance, },
end

/-- A binary function is integrable if the function `y ↦ f (x, y)` is integrable for almost every
  `x` and the function `x ↦ ∫ ∥f (x, y)∥ dy` is integrable. -/
lemma integrable_prod_iff ⦃f : α × β → E⦄ (h1f : ae_measurable f (μ.prod ν)) :
  integrable f (μ.prod ν) ↔
    (∀ᵐ x ∂ μ, integrable (λ y, f (x, y)) ν) ∧ integrable (λ x, ∫ y, ∥f (x, y)∥ ∂ν) μ :=
by simp [integrable, h1f, has_finite_integral_prod_iff', h1f.norm.integral_prod_right',
         h1f.prod_mk_left]

/-- A binary function is integrable if the function `x ↦ f (x, y)` is integrable for almost every
  `y` and the function `y ↦ ∫ ∥f (x, y)∥ dx` is integrable. -/
lemma integrable_prod_iff' [sigma_finite μ] ⦃f : α × β → E⦄ (h1f : ae_measurable f (μ.prod ν)) :
  integrable f (μ.prod ν) ↔
    (∀ᵐ y ∂ ν, integrable (λ x, f (x, y)) μ) ∧ integrable (λ y, ∫ x, ∥f (x, y)∥ ∂μ) ν :=
by { convert integrable_prod_iff (h1f.prod_swap) using 1, rw [integrable_swap_iff] }

lemma integrable.prod_left_ae [sigma_finite μ] ⦃f : α × β → E⦄
  (hf : integrable f (μ.prod ν)) : ∀ᵐ y ∂ ν, integrable (λ x, f (x, y)) μ :=
((integrable_prod_iff' hf.ae_measurable).mp hf).1

lemma integrable.prod_right_ae [sigma_finite μ] ⦃f : α × β → E⦄
  (hf : integrable f (μ.prod ν)) : ∀ᵐ x ∂ μ, integrable (λ y, f (x, y)) ν :=
hf.swap.prod_left_ae

lemma integrable.integral_norm_prod_left ⦃f : α × β → E⦄
  (hf : integrable f (μ.prod ν)) : integrable (λ x, ∫ y, ∥f (x, y)∥ ∂ν) μ :=
((integrable_prod_iff hf.ae_measurable).mp hf).2

lemma integrable.integral_norm_prod_right [sigma_finite μ] ⦃f : α × β → E⦄
  (hf : integrable f (μ.prod ν)) : integrable (λ y, ∫ x, ∥f (x, y)∥ ∂μ) ν :=
hf.swap.integral_norm_prod_left

end

variables [second_countable_topology E] [normed_space ℝ E]
  [complete_space E] [borel_space E]

lemma integrable.integral_prod_left ⦃f : α × β → E⦄
  (hf : integrable f (μ.prod ν)) : integrable (λ x, ∫ y, f (x, y) ∂ν) μ :=
integrable.mono hf.integral_norm_prod_left hf.ae_measurable.integral_prod_right' $
  eventually_of_forall $ λ x, (norm_integral_le_integral_norm _).trans_eq $
  (norm_of_nonneg $ integral_nonneg_of_ae $ eventually_of_forall $
  λ y, (norm_nonneg (f (x, y)) : _)).symm

lemma integrable.integral_prod_right [sigma_finite μ] ⦃f : α × β → E⦄
  (hf : integrable f (μ.prod ν)) : integrable (λ y, ∫ x, f (x, y) ∂μ) ν :=
hf.swap.integral_prod_left

/-! ### The Bochner integral on a product -/

variables [sigma_finite μ]

lemma integral_prod_swap (f : α × β → E)
  (hf : ae_measurable f (μ.prod ν)) : ∫ z, f z.swap ∂(ν.prod μ) = ∫ z, f z ∂(μ.prod ν) :=
begin
  rw ← prod_swap at hf,
  rw [← integral_map measurable_swap hf, prod_swap]
end

variables {E' : Type*} [measurable_space E'] [normed_group E'] [borel_space E'] [complete_space E']
  [normed_space ℝ E'] [second_countable_topology E']

/-! Some rules about the sum/difference of double integrals. They follow from `integral_add`, but
  we separate them out as separate lemmas, because they involve quite some steps. -/

/-- Integrals commute with addition inside another integral. `F` can be any function. -/
lemma integral_fn_integral_add ⦃f g : α × β → E⦄ (F : E → E')
  (hf : integrable f (μ.prod ν)) (hg : integrable g (μ.prod ν)) :
  ∫ x, F (∫ y, f (x, y) + g (x, y) ∂ν) ∂μ = ∫ x, F (∫ y, f (x, y) ∂ν + ∫ y, g (x, y) ∂ν) ∂μ :=
begin
  refine integral_congr_ae _,
  filter_upwards [hf.prod_right_ae, hg.prod_right_ae] with _ h2f h2g,
  simp [integral_add h2f h2g],
end

/-- Integrals commute with subtraction inside another integral.
  `F` can be any measurable function. -/
lemma integral_fn_integral_sub ⦃f g : α × β → E⦄ (F : E → E')
  (hf : integrable f (μ.prod ν)) (hg : integrable g (μ.prod ν)) :
  ∫ x, F (∫ y, f (x, y) - g (x, y) ∂ν) ∂μ = ∫ x, F (∫ y, f (x, y) ∂ν - ∫ y, g (x, y) ∂ν) ∂μ :=
begin
  refine integral_congr_ae _,
  filter_upwards [hf.prod_right_ae, hg.prod_right_ae] with _ h2f h2g,
  simp [integral_sub h2f h2g],
end

/-- Integrals commute with subtraction inside a lower Lebesgue integral.
  `F` can be any function. -/
lemma lintegral_fn_integral_sub ⦃f g : α × β → E⦄
  (F : E → ℝ≥0∞) (hf : integrable f (μ.prod ν)) (hg : integrable g (μ.prod ν)) :
  ∫⁻ x, F (∫ y, f (x, y) - g (x, y) ∂ν) ∂μ = ∫⁻ x, F (∫ y, f (x, y) ∂ν - ∫ y, g (x, y) ∂ν) ∂μ :=
begin
  refine lintegral_congr_ae _,
  filter_upwards [hf.prod_right_ae, hg.prod_right_ae] with _ h2f h2g,
  simp [integral_sub h2f h2g],
end

/-- Double integrals commute with addition. -/
lemma integral_integral_add ⦃f g : α × β → E⦄
  (hf : integrable f (μ.prod ν)) (hg : integrable g (μ.prod ν)) :
  ∫ x, ∫ y, f (x, y) + g (x, y) ∂ν ∂μ = ∫ x, ∫ y, f (x, y) ∂ν ∂μ + ∫ x, ∫ y, g (x, y) ∂ν ∂μ :=
(integral_fn_integral_add id hf hg).trans $
  integral_add hf.integral_prod_left hg.integral_prod_left

/-- Double integrals commute with addition. This is the version with `(f + g) (x, y)`
  (instead of `f (x, y) + g (x, y)`) in the LHS. -/
lemma integral_integral_add' ⦃f g : α × β → E⦄
  (hf : integrable f (μ.prod ν)) (hg : integrable g (μ.prod ν)) :
  ∫ x, ∫ y, (f + g) (x, y) ∂ν ∂μ = ∫ x, ∫ y, f (x, y) ∂ν ∂μ + ∫ x, ∫ y, g (x, y) ∂ν ∂μ :=
integral_integral_add hf hg

/-- Double integrals commute with subtraction. -/
lemma integral_integral_sub ⦃f g : α × β → E⦄
  (hf : integrable f (μ.prod ν)) (hg : integrable g (μ.prod ν)) :
  ∫ x, ∫ y, f (x, y) - g (x, y) ∂ν ∂μ = ∫ x, ∫ y, f (x, y) ∂ν ∂μ - ∫ x, ∫ y, g (x, y) ∂ν ∂μ :=
(integral_fn_integral_sub id hf hg).trans $
  integral_sub hf.integral_prod_left hg.integral_prod_left

/-- Double integrals commute with subtraction. This is the version with `(f - g) (x, y)`
  (instead of `f (x, y) - g (x, y)`) in the LHS. -/
lemma integral_integral_sub' ⦃f g : α × β → E⦄
  (hf : integrable f (μ.prod ν)) (hg : integrable g (μ.prod ν)) :
  ∫ x, ∫ y, (f - g) (x, y) ∂ν ∂μ = ∫ x, ∫ y, f (x, y) ∂ν ∂μ - ∫ x, ∫ y, g (x, y) ∂ν ∂μ :=
integral_integral_sub hf hg

/-- The map that sends an L¹-function `f : α × β → E` to `∫∫f` is continuous. -/
lemma continuous_integral_integral :
  continuous (λ (f : α × β →₁[μ.prod ν] E), ∫ x, ∫ y, f (x, y) ∂ν ∂μ) :=
begin
  rw [continuous_iff_continuous_at], intro g,
  refine tendsto_integral_of_L1 _ (L1.integrable_coe_fn g).integral_prod_left
    (eventually_of_forall $ λ h, (L1.integrable_coe_fn h).integral_prod_left) _,
  simp_rw [← lintegral_fn_integral_sub (λ x, (nnnorm x : ℝ≥0∞)) (L1.integrable_coe_fn _)
    (L1.integrable_coe_fn g)],
  refine tendsto_of_tendsto_of_tendsto_of_le_of_le tendsto_const_nhds _ (λ i, zero_le _) _,
  { exact λ i, ∫⁻ x, ∫⁻ y, nnnorm (i (x, y) - g (x, y)) ∂ν ∂μ },
  swap, { exact λ i, lintegral_mono (λ x, ennnorm_integral_le_lintegral_ennnorm _) },
  show tendsto (λ (i : α × β →₁[μ.prod ν] E),
    ∫⁻ x, ∫⁻ (y : β), nnnorm (i (x, y) - g (x, y)) ∂ν ∂μ) (𝓝 g) (𝓝 0),
  have : ∀ (i : α × β →₁[μ.prod ν] E), measurable (λ z, (nnnorm (i z - g z) : ℝ≥0∞)) :=
  λ i, ((Lp.measurable i).sub (Lp.measurable g)).ennnorm,
  simp_rw [← lintegral_prod_of_measurable _ (this _), ← L1.of_real_norm_sub_eq_lintegral,
    ← of_real_zero],
  refine (continuous_of_real.tendsto 0).comp _,
  rw [← tendsto_iff_norm_tendsto_zero], exact tendsto_id
end

/-- **Fubini's Theorem**: For integrable functions on `α × β`,
  the Bochner integral of `f` is equal to the iterated Bochner integral.
  `integrable_prod_iff` can be useful to show that the function in question in integrable.
  `measure_theory.integrable.integral_prod_right` is useful to show that the inner integral
  of the right-hand side is integrable. -/
lemma integral_prod : ∀ (f : α × β → E) (hf : integrable f (μ.prod ν)),
  ∫ z, f z ∂(μ.prod ν) = ∫ x, ∫ y, f (x, y) ∂ν ∂μ :=
begin
  apply integrable.induction,
  { intros c s hs h2s,
    simp_rw [integral_indicator hs, ← indicator_comp_right,
      function.comp, integral_indicator (measurable_prod_mk_left hs),
      set_integral_const, integral_smul_const,
      integral_to_real (measurable_measure_prod_mk_left hs).ae_measurable
      (ae_measure_lt_top hs h2s.ne), prod_apply hs] },
  { intros f g hfg i_f i_g hf hg,
    simp_rw [integral_add' i_f i_g, integral_integral_add' i_f i_g, hf, hg] },
  { exact is_closed_eq continuous_integral continuous_integral_integral },
  { intros f g hfg i_f hf, convert hf using 1,
    { exact integral_congr_ae hfg.symm },
    { refine integral_congr_ae _,
      refine (ae_ae_of_ae_prod hfg).mp _,
      apply eventually_of_forall, intros x hfgx,
      exact integral_congr_ae (ae_eq_symm hfgx) } }
end

/-- Symmetric version of **Fubini's Theorem**: For integrable functions on `α × β`,
  the Bochner integral of `f` is equal to the iterated Bochner integral.
  This version has the integrals on the right-hand side in the other order. -/
lemma integral_prod_symm (f : α × β → E) (hf : integrable f (μ.prod ν)) :
  ∫ z, f z ∂(μ.prod ν) = ∫ y, ∫ x, f (x, y) ∂μ ∂ν :=
by { simp_rw [← integral_prod_swap f hf.ae_measurable], exact integral_prod _ hf.swap }

/-- Reversed version of **Fubini's Theorem**. -/
lemma integral_integral {f : α → β → E} (hf : integrable (uncurry f) (μ.prod ν)) :
  ∫ x, ∫ y, f x y ∂ν ∂μ = ∫ z, f z.1 z.2 ∂(μ.prod ν) :=
(integral_prod _ hf).symm

/-- Reversed version of **Fubini's Theorem** (symmetric version). -/
lemma integral_integral_symm {f : α → β → E} (hf : integrable (uncurry f) (μ.prod ν)) :
  ∫ x, ∫ y, f x y ∂ν ∂μ = ∫ z, f z.2 z.1 ∂(ν.prod μ) :=
(integral_prod_symm _ hf.swap).symm

/-- Change the order of Bochner integration. -/
lemma integral_integral_swap ⦃f : α → β → E⦄ (hf : integrable (uncurry f) (μ.prod ν)) :
  ∫ x, ∫ y, f x y ∂ν ∂μ = ∫ y, ∫ x, f x y ∂μ ∂ν :=
(integral_integral hf).trans (integral_prod_symm _ hf)

/-- **Fubini's Theorem** for set integrals. -/
lemma set_integral_prod (f : α × β → E) {s : set α} {t : set β}
  (hf : integrable_on f (s ×ˢ t) (μ.prod ν)) :
  ∫ z in s ×ˢ t, f z ∂(μ.prod ν) = ∫ x in s, ∫ y in t, f (x, y) ∂ν ∂μ :=
begin
  simp only [← measure.prod_restrict s t, integrable_on] at hf ⊢,
  exact integral_prod f hf
end

end measure_theory


/-
Copyright (c) 2019 Yury Kudryashov. All rights reserved.
Released under Apache 2.0 license as described in the file LICENSE.
Authors: Yury Kudryashov
-/
import category_theory.endomorphism
import category_theory.category.Cat
import algebra.category.Mon.basic
import combinatorics.quiver.single_obj

/-!
# Single-object category

> THIS FILE IS SYNCHRONIZED WITH MATHLIB4.
> Any changes to this file require a corresponding PR to mathlib4.

Single object category with a given monoid of endomorphisms.
It is defined to facilitate transfering some definitions and lemmas (e.g., conjugacy etc.)
from category theory to monoids and groups.

## Main definitions

Given a type `α` with a monoid structure, `single_obj α` is `unit` type with `category` structure
such that `End (single_obj α).star` is the monoid `α`.  This can be extended to a functor `Mon ⥤
Cat`.

If `α` is a group, then `single_obj α` is a groupoid.

An element `x : α` can be reinterpreted as an element of `End (single_obj.star α)` using
`single_obj.to_End`.

## Implementation notes

- `category_struct.comp` on `End (single_obj.star α)` is `flip (*)`, not `(*)`. This way
  multiplication on `End` agrees with the multiplication on `α`.

- By default, Lean puts instances into `category_theory` namespace instead of
  `category_theory.single_obj`, so we give all names explicitly.
-/

universes u v w

namespace category_theory

/--
Abbreviation that allows writing `category_theory.single_obj` rather than `quiver.single_obj`.
-/
abbreviation single_obj := quiver.single_obj

namespace single_obj

variables (α : Type u)

/-- One and `flip (*)` become `id` and `comp` for morphisms of the single object category. -/
instance category_struct [has_one α] [has_mul α] : category_struct (single_obj α) :=
{ hom := λ _ _, α,
  comp := λ _ _ _ x y, y * x,
  id := λ _, 1 }

/-- Monoid laws become category laws for the single object category. -/
instance category [monoid α] : category (single_obj α) :=
{ comp_id' := λ _ _, one_mul,
  id_comp' := λ _ _, mul_one,
  assoc' := λ _ _ _ _ x y z, (mul_assoc z y x).symm }

lemma id_as_one [monoid α] (x : single_obj α) : 𝟙 x = 1 := rfl

lemma comp_as_mul [monoid α] {x y z : single_obj α} (f : x ⟶ y) (g : y ⟶ z) :
  f ≫ g = g * f := rfl

/--
Groupoid structure on `single_obj α`.

See <https://stacks.math.columbia.edu/tag/0019>.
-/
instance groupoid [group α] : groupoid (single_obj α) :=
{ inv := λ _ _ x, x⁻¹,
  inv_comp' := λ _ _, mul_right_inv,
  comp_inv' := λ _ _, mul_left_inv }

/--
Abbreviation that allows writing `category_theory.single_obj.star` rather than
`quiver.single_obj.star`.
-/
abbreviation star : single_obj α := quiver.single_obj.star α

/-- The endomorphisms monoid of the only object in `single_obj α` is equivalent to the original
     monoid α. -/
def to_End [monoid α] : α ≃* End (single_obj.star α) :=
{ map_mul' := λ x y, rfl,
  .. equiv.refl α }

lemma to_End_def [monoid α] (x : α) : to_End α x = x := rfl

/-- There is a 1-1 correspondence between monoid homomorphisms `α → β` and functors between the
    corresponding single-object categories. It means that `single_obj` is a fully faithful
    functor.

See <https://stacks.math.columbia.edu/tag/001F> --
although we do not characterize when the functor is full or faithful.
-/
def map_hom (α : Type u) (β : Type v) [monoid α] [monoid β] :
  (α →* β) ≃ (single_obj α) ⥤ (single_obj β) :=
{ to_fun := λ f,
  { obj := id,
    map := λ _ _, ⇑f,
    map_id' := λ _, f.map_one,
    map_comp' := λ _ _ _ x y, f.map_mul y x },
  inv_fun := λ f,
    { to_fun := @functor.map _ _ _ _ f (single_obj.star α) (single_obj.star α),
      map_one' := f.map_id _,
      map_mul' := λ x y, f.map_comp y x },
  left_inv := λ ⟨f, h₁, h₂⟩, rfl,
  right_inv := λ f, by cases f; obviously }

lemma map_hom_id (α : Type u) [monoid α] : map_hom α α (monoid_hom.id α) = 𝟭 _ := rfl

lemma map_hom_comp {α : Type u} {β : Type v} [monoid α] [monoid β] (f : α →* β)
  {γ : Type w} [monoid γ] (g : β →* γ) :
  map_hom α γ (g.comp f) = map_hom α β f ⋙ map_hom β γ g :=
rfl

/-- Given a function `f : C → G` from a category to a group, we get a functor
    `C ⥤ G` sending any morphism `x ⟶ y` to `f y * (f x)⁻¹`. -/
@[simps] def difference_functor {C G} [category C] [group G] (f : C → G) : C ⥤ single_obj G :=
{ obj := λ _, (),
  map := λ x y _, f y * (f x)⁻¹,
  map_id' := by { intro, rw [single_obj.id_as_one, mul_right_inv] },
  map_comp' := by { intros, rw [single_obj.comp_as_mul, ←mul_assoc,
    mul_left_inj, mul_assoc, inv_mul_self, mul_one] } }

end single_obj

end category_theory

open category_theory

namespace monoid_hom

/-- Reinterpret a monoid homomorphism `f : α → β` as a functor `(single_obj α) ⥤ (single_obj β)`.
See also `category_theory.single_obj.map_hom` for an equivalence between these types. -/
@[reducible] def to_functor {α : Type u} {β : Type v} [monoid α] [monoid β] (f : α →* β) :
  (single_obj α) ⥤ (single_obj β) :=
single_obj.map_hom α β f

@[simp] lemma id_to_functor (α : Type u) [monoid α] : (id α).to_functor = 𝟭 _ := rfl
@[simp] lemma comp_to_functor {α : Type u} {β : Type v} [monoid α] [monoid β] (f : α →* β)
  {γ : Type w} [monoid γ] (g : β →* γ) :
  (g.comp f).to_functor = f.to_functor ⋙ g.to_functor :=
rfl

end monoid_hom

namespace units

variables (α : Type u) [monoid α]

/--
The units in a monoid are (multiplicatively) equivalent to
the automorphisms of `star` when we think of the monoid as a single-object category. -/
def to_Aut : αˣ ≃* Aut (single_obj.star α) :=
(units.map_equiv (single_obj.to_End α)).trans $
  Aut.units_End_equiv_Aut _

@[simp] lemma to_Aut_hom (x : αˣ) : (to_Aut α x).hom = single_obj.to_End α x := rfl
@[simp] lemma to_Aut_inv (x : αˣ) :
  (to_Aut α x).inv = single_obj.to_End α (x⁻¹ : αˣ) :=
rfl
end units

namespace Mon

open category_theory

/-- The fully faithful functor from `Mon` to `Cat`. -/
def to_Cat : Mon ⥤ Cat :=
{ obj := λ x, Cat.of (single_obj x),
  map := λ x y f, single_obj.map_hom x y f }

instance to_Cat_full : full to_Cat :=
{ preimage := λ x y, (single_obj.map_hom x y).inv_fun,
  witness' := λ x y, by apply equiv.right_inv }

instance to_Cat_faithful : faithful to_Cat :=
{ map_injective' := λ x y, by apply equiv.injective }

end Mon


import smt2

lemma int_ctor (n : nat) :
    int.of_nat n >= 0 :=
begin
    z3
end