open Util
open Constants
open Lacaml.D

type t = Mat.t
external matrix : t -> Mat.t = "%identity"
external of_matrix : Mat.t -> t = "%identity"


module Am  = AngularMomentum
module Bs  = Basis
module Co  = Coordinate
module Cs  = ContractedShell
module Csp = ContractedShellPair
module Po  = Powers
module Psp = PrimitiveShellPair


let cutoff = integrals_cutoff

let to_powers x =
  let open Zkey in
  match to_powers x with
  | Six x -> x
  | _ -> assert false

(** Computes all the overlap integrals of the contracted shell pair *)
let contracted_class shell_a shell_b : float Zmap.t =

  match Csp.make shell_a shell_b with
  | None -> Zmap.create 0
  | Some shell_p ->
    begin

      (* Pre-computation of integral class indices *)
      let class_indices = Csp.zkey_array shell_p in

      let contracted_class =
        Array.make (Array.length class_indices) 0.
      in

      let a_minus_b =
        Csp.a_minus_b shell_p
      in
      let norm_coef_scales =
        Csp.norm_scales shell_p
      in

      (* Compute all integrals in the shell for each pair of significant shell pairs *)

      let xyz_of_int k =
        match k with
        | 0 -> Co.X
        | 1 -> Co.Y
        | _ -> Co.Z
      in

      List.iter (fun (coef_prod, psp) ->
          (** Screening on the product of coefficients *)
          if (abs_float coef_prod) > 1.e-6*.cutoff then
            begin
              let expo_inv = Psp.exponent_inv psp
              and center_pa = Psp.center_minus_a psp
              in

              Array.iteri (fun i key ->
                  let (angMomA,angMomB) = to_powers key in
                  let f k =
                    let xyz = xyz_of_int k in
                    Overlap_primitives.hvrr (Po.get xyz angMomA, Po.get xyz angMomB)
                      expo_inv
                      (Co.get xyz a_minus_b,
                      Co.get xyz center_pa)
                  in
                  let norm =  norm_coef_scales.(i) in
                  let integral = chop norm (fun () -> (f 0)*.(f 1)*.(f 2)) in
                  contracted_class.(i) <- contracted_class.(i) +. coef_prod *. integral
                ) class_indices
            end
        ) (Csp.coefs_and_shell_pairs shell_p);
      let result =
        Zmap.create (Array.length contracted_class)
      in
      Array.iteri (fun i key -> Zmap.add result key contracted_class.(i)) class_indices;
      result
    end


(** Create overlap matrix *)
let of_basis basis =
  let to_powers x =
    let open Zkey in
    match to_powers x with
    | Three x -> x
    | _ -> assert false
  in

  let n     = Bs.size basis
  and shell = Bs.contracted_shells basis
  in

  let result = Mat.create n n in
  for j=0 to (Array.length shell) - 1 do
    for i=0 to j do
      (* Compute all the integrals of the class *)
      let cls =
        contracted_class shell.(i) shell.(j)
      in

      Array.iteri (fun j_c powers_j ->
          let j_c = Cs.index shell.(j) + j_c + 1 in
          let xj = to_powers powers_j in
          Array.iteri (fun i_c powers_i ->
              let i_c = Cs.index shell.(i) + i_c + 1 in
              let xi = to_powers powers_i in
              let key =
                Zkey.of_powers_six xi xj
              in
              let value =
                try Zmap.find cls key
                with Not_found -> 0.
              in
              result.{i_c,j_c} <- value;
              result.{j_c,i_c} <- value;
            ) (Am.zkey_array (Singlet (Cs.ang_mom shell.(i))))
        ) (Am.zkey_array (Singlet (Cs.ang_mom shell.(j))))
    done;
  done;
  Mat.detri result;
  result


(** Create mixed overlap matrix *)
let of_basis_pair first_basis second_basis =
  let to_powers x =
    let open Zkey in
    match to_powers x with
    | Three x -> x
    | _ -> assert false
  in

  let n      = Bs.size first_basis
  and m      = Bs.size second_basis
  and first  = Bs.contracted_shells first_basis
  and second = Bs.contracted_shells second_basis
  in

  let result = Mat.create n m in
  for j=0 to (Array.length second) - 1 do
    for i=0 to  (Array.length first) - 1 do
      (* Compute all the integrals of the class *)
      let cls =
        contracted_class first.(i) second.(j)
      in

      Array.iteri (fun j_c powers_j ->
          let j_c = Cs.index second.(j) + j_c + 1 in
          let xj = to_powers powers_j in
          Array.iteri (fun i_c powers_i ->
              let i_c = Cs.index first.(i) + i_c + 1 in
              let xi = to_powers powers_i in
              let key =
                Zkey.of_powers_six xi xj
              in
              let value =
                try Zmap.find cls key
                with Not_found -> 0.
              in
              result.{i_c,j_c} <- value;
            ) (Am.zkey_array (Singlet (Cs.ang_mom first.(i))))
        ) (Am.zkey_array (Singlet (Cs.ang_mom second.(j))))
    done;
  done;
  result



(** Write all overlap integrals to a file *)
let to_file ~filename overlap =

  let oc = open_out filename in
  let n =
    Mat.dim1 overlap
  in

  for j=1 to n  do
    for i=1 to j do
      if (abs_float overlap.{i,j} > cutoff) then
        Printf.fprintf oc "%4d %4d %20.12e\n" i j overlap.{i,j}
    done;
  done;
  close_out oc



