let is_add_monoid = new_definition
  `is_add_monoid (zero:A) (add:A->A->A) <=>
      (!x. add x zero = x) /\
      (!x y. add x y = add y x) /\
      (!x y z. add (add x y) z = add x (add y z))`;;

let is_integral = new_definition
  `is_integral (integral:(A->A)->A) (add:A->A->A) (zero:A) <=>
      (!g h. (!t. g t = h t) ==> integral g = integral h) /\
      (!c. integral (\t. c) = c) /\
      (!f g. integral (\t. add (f t) (g t)) =
             add (integral f) (integral g)) /\
      (!f c. integral (\t. f (add t c)) = integral f)`;;

let circleMap = new_definition
  `circleMap (add:A->A->A) c t = add t c`;;

let circleAverage = new_definition
  `circleAverage (integral:(A->A)->A) (add:A->A->A) (f:A->A) c =
      integral (\t. f (circleMap add c t))`;;

let dest_add_monoid th = CONJUNCTS (REWRITE_RULE[is_add_monoid] th);;
let dest_integral th = CONJUNCTS (REWRITE_RULE[is_integral] th);;

let circleMap_zero = prove
 (`!(zero:A) (add:A->A->A).
      is_add_monoid zero add ==> !t:A. circleMap add zero t = t`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun mono ->
    let add_zero = hd (dest_add_monoid mono) in
    ASSUME_TAC add_zero) THEN
  GEN_TAC THEN REWRITE_TAC[circleMap] THEN ASM_REWRITE_TAC[]);;

let circleAverage_zero = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_add_monoid zero add /\ is_integral integral add zero ==>
      !f:A->A. circleAverage integral add f zero = integral f`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun th ->
    let int_th = CONJUNCT2 th in
    let shift = List.nth (dest_integral int_th) 3 in
    ASSUME_TAC shift) THEN
  GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN ASM_REWRITE_TAC[]);;

let circleAverage_add = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_integral integral add zero ==>
      !(f:A->A) (g:A->A) (c:A).
        circleAverage integral add (\z. add (f z) (g z)) c =
        add (circleAverage integral add f c) (circleAverage integral add g c)`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun int_th ->
    let add_th = List.nth (dest_integral int_th) 2 in
    ASSUME_TAC add_th) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun add_th ->
    ASM_REWRITE_TAC[
      SPECL
        [`\t:A. (f:A->A) ((add:A->A->A) t (c:A))`;
         `\t:A. (g:A->A) ((add:A->A->A) t (c:A))`]
        add_th]) THEN
  REFL_TAC);;

let circleAverage_fun_add = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_add_monoid zero add /\ is_integral integral add zero ==>
      !(f:A->A) (c:A).
        circleAverage integral add (\z. f (add z c)) zero =
        circleAverage integral add f c`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun th ->
    let mono_th = CONJUNCT1 th in
    let int_th = CONJUNCT2 th in
    let add_zero = hd (dest_add_monoid mono_th) in
    let int_ext = hd (dest_integral int_th) in
    ASSUME_TAC add_zero THEN ASSUME_TAC int_ext) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun int_ext ->
    MATCH_MP_TAC
      (SPECL
        [`\t:A. (f:A->A) ((add:A->A->A) ((add:A->A->A) t (zero:A)) (c:A))`;
         `\t:A. (f:A->A) ((add:A->A->A) t (c:A))`]
        int_ext)) THEN
  GEN_TAC THEN
  FIRST_ASSUM (fun add_zero -> ASM_REWRITE_TAC[SPEC `t:A` add_zero]) THEN
  REFL_TAC);;

let circleMap_add = prove
 (`!(zero:A) (add:A->A->A).
      is_add_monoid zero add ==>
      !(c:A) (d:A) (t:A).
        circleMap add (add c d) t = circleMap add c (circleMap add d t)`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun mono ->
    let add_comm = List.nth (dest_add_monoid mono) 1 in
    let add_assoc = List.nth (dest_add_monoid mono) 2 in
    ASSUME_TAC add_comm THEN ASSUME_TAC add_assoc) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleMap] THEN
  FIRST_ASSUM (fun add_comm -> ASM_REWRITE_TAC[SPECL [`c:A`; `d:A`] add_comm]) THEN
  FIRST_ASSUM (fun add_assoc -> ASM_REWRITE_TAC[SPECL [`t:A`; `d:A`; `c:A`] add_assoc]) THEN
  REFL_TAC);;

let circleAverage_shift = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_add_monoid zero add /\ is_integral integral add zero ==>
      !(f:A->A) (c:A) (d:A).
        circleAverage integral add f (add c d) =
        circleAverage integral add (\z. f (add z d)) c`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun th ->
    let mono_th = CONJUNCT1 th in
    let int_th = CONJUNCT2 th in
    let add_assoc = List.nth (dest_add_monoid mono_th) 2 in
    let int_ext = hd (dest_integral int_th) in
    ASSUME_TAC add_assoc THEN ASSUME_TAC int_ext) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun int_ext ->
    MATCH_MP_TAC
      (SPECL
        [`\t:A. (f:A->A) ((add:A->A->A) t ((add:A->A->A) (c:A) (d:A)))`;
         `\t:A. (f:A->A) ((add:A->A->A) ((add:A->A->A) t (c:A)) (d:A))`]
        int_ext)) THEN
  GEN_TAC THEN
  FIRST_ASSUM (fun add_assoc ->
    REWRITE_TAC[GSYM (SPECL [`t:A`; `c:A`; `d:A`] add_assoc)]) THEN
  REFL_TAC);;

let circleAverage_const = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_integral integral add zero ==>
      !(k:A) (c:A). circleAverage integral add (\z. k) c = k`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun int_th ->
    let int_const = List.nth (dest_integral int_th) 1 in
    ASSUME_TAC int_const) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun int_const -> ASM_REWRITE_TAC[SPEC `k:A` int_const]) THEN
  REFL_TAC);;

let circleAverage_add_const = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_integral integral add zero ==>
      !(f:A->A) (k:A) (c:A).
        circleAverage integral add (\z. add (f z) k) c =
        add (circleAverage integral add f c) k`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun int_th ->
    let int_const = List.nth (dest_integral int_th) 1 in
    let int_add = List.nth (dest_integral int_th) 2 in
    ASSUME_TAC int_const THEN ASSUME_TAC int_add) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun int_add ->
    ASM_REWRITE_TAC[
      SPECL
        [`\t:A. (f:A->A) ((add:A->A->A) t (c:A))`;
         `\t:A. (k:A)`]
        int_add]) THEN
  FIRST_ASSUM (fun int_const -> ASM_REWRITE_TAC[SPEC `k:A` int_const]) THEN
  REFL_TAC);;

let circleAverage_comm_add = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_add_monoid zero add /\ is_integral integral add zero ==>
      !(f:A->A) (g:A->A) (c:A).
        circleAverage integral add (\z. add (f z) (g z)) c =
        circleAverage integral add (\z. add (g z) (f z)) c`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun th ->
    let mono_th = CONJUNCT1 th in
    let int_th = CONJUNCT2 th in
    let add_comm = List.nth (dest_add_monoid mono_th) 1 in
    let int_ext = hd (dest_integral int_th) in
    REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
    MATCH_MP_TAC
      (SPECL
        [`\t:A. (add:A->A->A)
                 ((f:A->A) ((add:A->A->A) t (c:A)))
                 ((g:A->A) ((add:A->A->A) t (c:A)))`;
         `\t:A. (add:A->A->A)
                 ((g:A->A) ((add:A->A->A) t (c:A)))
                 ((f:A->A) ((add:A->A->A) t (c:A)))`]
        int_ext) THEN
    GEN_TAC THEN BETA_TAC THEN
    ACCEPT_TAC
      (SPECL
        [`(f:A->A) ((add:A->A->A) (t:A) (c:A))`;
         `(g:A->A) ((add:A->A->A) (t:A) (c:A))`]
        add_comm)));;

let circleAverage_add_assoc = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_add_monoid zero add /\ is_integral integral add zero ==>
      !(f:A->A) (g:A->A) (h:A->A) (c:A).
        circleAverage integral add (\z. add (add (f z) (g z)) (h z)) c =
        add (circleAverage integral add f c)
            (add (circleAverage integral add g c)
                 (circleAverage integral add h c))`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun th ->
    let mono_th = CONJUNCT1 th in
    let int_th = CONJUNCT2 th in
    let add_assoc = List.nth (dest_add_monoid mono_th) 2 in
    let int_add = List.nth (dest_integral int_th) 2 in
    ASSUME_TAC add_assoc THEN ASSUME_TAC int_add) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun int_add ->
    ASM_REWRITE_TAC[
      SPECL
        [`\t:A. (add:A->A->A)
                 ((f:A->A) ((add:A->A->A) t (c:A)))
                 ((g:A->A) ((add:A->A->A) t (c:A)))`;
         `\t:A. (h:A->A) ((add:A->A->A) t (c:A))`]
        int_add]) THEN
  FIRST_ASSUM (fun int_add ->
    ASM_REWRITE_TAC[
      SPECL
        [`\t:A. (f:A->A) ((add:A->A->A) t (c:A))`;
         `\t:A. (g:A->A) ((add:A->A->A) t (c:A))`]
        int_add]) THEN
  FIRST_ASSUM (fun add_assoc ->
    ASM_REWRITE_TAC[SPECL
      [`integral (\t. f (add t c)):A`;
       `integral (\t. g (add t c)):A`;
       `integral (\t. h (add t c)):A`] add_assoc]) THEN
  REFL_TAC);;

let circleAverage_center_comm = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_add_monoid zero add /\ is_integral integral add zero ==>
      !(f:A->A) (c:A) (d:A).
        circleAverage integral add f (add c d) =
        circleAverage integral add f (add d c)`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun th ->
    let mono_th = CONJUNCT1 th in
    let int_th = CONJUNCT2 th in
    let add_comm = List.nth (dest_add_monoid mono_th) 1 in
    let int_ext = hd (dest_integral int_th) in
    ASSUME_TAC add_comm THEN ASSUME_TAC int_ext) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun int_ext ->
    MATCH_MP_TAC
      (SPECL
        [`\t:A. (f:A->A) ((add:A->A->A) t ((add:A->A->A) (c:A) (d:A)))`;
         `\t:A. (f:A->A) ((add:A->A->A) t ((add:A->A->A) (d:A) (c:A)))`]
        int_ext)) THEN
  GEN_TAC THEN
  FIRST_ASSUM (fun add_comm -> ASM_REWRITE_TAC[SPECL [`c:A`; `d:A`] add_comm]) THEN
  REFL_TAC);;

let circleAverage_center_independent = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_integral integral add zero ==>
      !(f:A->A) (c:A). circleAverage integral add f c = integral f`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun int_th ->
    let shift = List.nth (dest_integral int_th) 3 in
    ASSUME_TAC shift) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN ASM_REWRITE_TAC[]);;

let circleAverage_center_eq = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_integral integral add zero ==>
      !(f:A->A) (c:A) (d:A).
        circleAverage integral add f c = circleAverage integral add f d`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun int_assum ->
    let ind0 =
      MATCH_MP
        (SPECL [`zero:A`; `add:A->A->A`; `integral:(A->A)->A`]
          circleAverage_center_independent)
        int_assum in
    REPEAT GEN_TAC THEN
    ASM_REWRITE_TAC[
      SPECL [`f:A->A`; `c:A`] ind0;
      SPECL [`f:A->A`; `d:A`] ind0] THEN
    REFL_TAC));;

let circleAverage_idempotent = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_integral integral add zero ==>
      !(f:A->A) (c:A).
        circleAverage integral add (\z. circleAverage integral add f z) c =
        circleAverage integral add f c`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun int_th ->
    let int_const = List.nth (dest_integral int_th) 1 in
    let shift = List.nth (dest_integral int_th) 3 in
    ASSUME_TAC int_const THEN ASSUME_TAC shift) THEN
  REPEAT GEN_TAC THEN
  REWRITE_TAC[circleAverage; circleMap] THEN

  FIRST_ASSUM (fun shift ->
    ASM_REWRITE_TAC[SPECL [`f:A->A`; `add t c:A`] shift]) THEN
  FIRST_ASSUM (fun int_const -> ASM_REWRITE_TAC[SPEC `integral (f:A->A):A` int_const]) THEN
  REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun shift -> ASM_REWRITE_TAC[SPECL [`f:A->A`; `c:A`] shift]) THEN
  REFL_TAC);;

let circleAverage_of_zero_integral = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_integral integral add zero ==>
      !(f:A->A) (c:A).
        integral f = zero ==> circleAverage integral add f c = zero`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun int_assum ->
    let ind0 =
      MATCH_MP
        (SPECL [`zero:A`; `add:A->A->A`; `integral:(A->A)->A`]
          circleAverage_center_independent)
        int_assum in
    REPEAT GEN_TAC THEN DISCH_TAC THEN
    ASM_REWRITE_TAC[SPECL [`f:A->A`; `c:A`] ind0]));;

let circleAverage_linear = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_integral integral add zero ==>
      !(f:A->A) (g:A->A) (c:A).
        circleAverage integral add (\z. add (f z) (g z)) c =
        add (circleAverage integral add f c) (circleAverage integral add g c)`,
  ACCEPT_TAC circleAverage_add);;

let circleAverage_shift_commute = prove
 (`!(zero:A) (add:A->A->A) (integral:(A->A)->A).
      is_add_monoid zero add /\ is_integral integral add zero ==>
      !(f:A->A) (c:A) (d:A).
        circleAverage integral add (\z. f (circleMap add d z)) c =
        circleAverage integral add f (add c d)`,
  REPEAT GEN_TAC THEN DISCH_THEN (fun th ->
    let mono_th = CONJUNCT1 th in
    let int_th = CONJUNCT2 th in
    let add_assoc = List.nth (dest_add_monoid mono_th) 2 in
    let int_ext = hd (dest_integral int_th) in
    ASSUME_TAC add_assoc THEN ASSUME_TAC int_ext) THEN
  REPEAT GEN_TAC THEN REWRITE_TAC[circleAverage; circleMap] THEN
  FIRST_ASSUM (fun int_ext ->
    MATCH_MP_TAC
      (SPECL
        [`\t:A. (f:A->A) ((add:A->A->A) ((add:A->A->A) t (c:A)) (d:A))`;
         `\t:A. (f:A->A) ((add:A->A->A) t ((add:A->A->A) (c:A) (d:A)))`]
        int_ext)) THEN
  GEN_TAC THEN
  FIRST_ASSUM (fun add_assoc -> ASM_REWRITE_TAC[SPECL [`t:A`; `c:A`; `d:A`] add_assoc]) THEN
  REFL_TAC);;