open List
open Printf
let (|>) x f = f x

type t = {
	tree : T.t;
	qms : Q.Diag.t array;
	pms : P.matrix array;
	prior : float array option
}

let make ?prior t qms =
	let qms = if Array.length qms = 1 then Array.make (T.size t - 1) qms.(0) else Array.copy qms
	if Array.length qms <> T.size t - 1 then invalid_arg "CamlPaml.PhyloModel.make"
	for i = 0 to T.root t - 1 do
		if Q.Diag.dim qms.(i) <> Q.Diag.dim qms.(0) || T.branch t i < 0. then invalid_arg "CamlPaml.PhyloModel.make"
	let pms = Array.init (T.size t - 1) (fun br -> Q.Diag.to_Pt qms.(br) (T.branch t br))
	let prior = match prior with	
		| Some pr ->
			if Array.length pr <> Q.Diag.dim qms.(0) then invalid_arg "CamlPaml.PhyloModel.make"
			Some (Array.copy pr)
		| None ->
			if not (Q.Diag.reversible qms.(snd (T.children t (T.root t)))) then invalid_arg "CamlPaml.PhyloModel.make"
			None
	{ tree = T.copy t; qms; pms; prior }
	
let tree { tree } = T.copy tree
let q { qms } br = qms.(br)
let p { pms } br = pms.(br) (* Array.map Array.copy pms.(br) *)
let prior { tree; qms; prior } = match prior with
	| Some pr -> Array.copy pr
	| None -> Q.Diag.equilibrium qms.(snd (T.children tree (T.root tree))) (* q was verified reversible in [make], above*)

let prepare_lik ?workspace m leaves = PhyloLik.prepare ?workspace m.tree m.pms (prior m) leaves

let checksum = 1., 1e-6

let simulate m =
	let branch_choosers = m.pms |> Array.map (fun pm -> (Array.map (Tools.random_chooser ~checksum:checksum) (Gsl.Matrix.to_arrays pm)))
	let root_chooser = Tools.random_chooser ~checksum:checksum (prior m)
	fun ?root ?a () ->
		let t = m.tree
		let a = match a with
			| Some a -> a
			| None -> Array.make (T.size t) (-1)

		a.(T.root t) <- (match root with
		                   | None -> root_chooser ()
		                   | Some ch when ch >= 0 && ch < Q.Diag.dim m.qms.(0) -> ch
		                   | _ -> invalid_arg "CamlPaml.PhyloModel.simulate (root)")
		for i = T.root t - 1 downto 0 do
			let p = a.(T.parent t i)
			a.(i) <- branch_choosers.(i).(p) ()
		a	

module P14n = struct
	type q_p14n = Expr.t array array

	type model_p14n = {
		q_p14ns : q_p14n array;
		q_scale_p14ns : Expr.t array;
		q_domains : Fit.domain array;

		tree_shape : T.t;
		tree_p14n : Expr.t array;
		tree_domains : Fit.domain array
	}

	type instance = {
		model : t;
		p14n : model_p14n;
		q_settings : float array;
		tree_settings : float array
	}
	
	let fill_q_diagonal q =
		let n = Array.length q
		for i = 0 to n-1 do
			if Array.length q.(i) <> n then invalid_arg "CamlPaml.PhyloModel.P14n.fill_q_diagonal"
			let tot = ref (Expr.Val 0.)
			for j = 0 to n-1 do
				if i <> j then
					tot := Expr.Add (q.(i).(j), !tot)
			q.(i).(i) <- Expr.simplify (Expr.Sub (Expr.Val 0., !tot))

	let instantiate_tree shape exprs domains settings =
		Array.iteri (fun i domain -> if not (Fit.check_domain domain settings.(i)) then invalid_arg ("CamlPaml.P14n.instantiate_tree: domain violation on variable " ^ (string_of_int i))) domains
		
		let tree = T.copy shape
		for br = 0 to T.root tree - 1 do
			T.put_branch tree br (Expr.eval exprs.(br) settings)
		tree
		
	let instantiate_q exprs scale_expr domains settings =
		Array.iteri (fun i domain -> if not (Fit.check_domain domain settings.(i)) then invalid_arg ("CamlPaml.P14n.instantiate_q: domain violation on variable " ^ (string_of_int i))) domains

		let scale = Expr.eval scale_expr settings
		
		if scale <= 0. then
			failwith "CamlPaml.P14n.instantiate_q: Q scale evaluated to a non-positive value"
		
		let qm = exprs |> Array.map (Array.map (fun expr -> Expr.eval expr settings /. scale))
											
		let q = Q.Diag.of_Q qm
		
		q
		
	let instantiate_qs p14ns scale_p14ns domains settings =
		if Array.length p14ns <> Array.length scale_p14ns then invalid_arg ("CamlPaml.P14n.instantiate: different numbers of rate matrix and scale p14ns")
		let previous = ref [] (* memoized results from previous branches...I'm assuming the number of independent rate matrix parameterizations to be sublinear in the size of the tree, otherwise this memoization is quadratic... *)
		Array.init (Array.length p14ns)
			fun br ->
				try
					let (_,_,q) =
						find
							function
								| (q_p14n,scale_p14n,q) when q_p14n == p14ns.(br) && scale_p14n == scale_p14ns.(br) -> true
								| _ -> false
							!previous
					q
				with
					| Not_found ->
						let q = instantiate_q p14ns.(br) scale_p14ns.(br) domains settings
						previous := (p14ns.(br),scale_p14ns.(br),q) :: !previous
						q

	let instantiate ?prior p14n ~q_settings ~tree_settings =
		let qms = instantiate_qs p14n.q_p14ns p14n.q_scale_p14ns p14n.q_domains q_settings
		let tree = instantiate_tree p14n.tree_shape p14n.tree_p14n p14n.tree_domains tree_settings
		{ model = make ?prior tree qms; p14n = p14n; q_settings = Array.copy q_settings; tree_settings = Array.copy tree_settings }
			
	let update ?prior ?q_settings ?tree_settings inst =
		let pms_changed = ref false
		let newq = match q_settings with
			| Some q_settings ->
				pms_changed := true
				instantiate_qs inst.p14n.q_p14ns inst.p14n.q_scale_p14ns inst.p14n.q_domains q_settings
			| None -> inst.model.qms
		let newtree = match tree_settings with
			| Some tree_settings ->
				pms_changed := true
				instantiate_tree inst.p14n.tree_shape inst.p14n.tree_p14n inst.p14n.tree_domains tree_settings
			| None -> inst.model.tree
		let model =
			if !pms_changed then make ?prior newtree newq
			else { inst.model with prior = prior }
		{ inst with model = model;
					q_settings = (match q_settings with Some set -> Array.copy set | None -> inst.q_settings);
					tree_settings = (match tree_settings with Some set -> Array.copy set | None -> inst.tree_settings) }
					
	let model { model = model } = model
	let p14n { p14n = p14n } = p14n
	let q_settings { q_settings = q_settings } = Array.copy q_settings
	let tree_settings { tree_settings = tree_settings } = Array.copy tree_settings
	

