open Globals
open Ast
open DisplayTypes.DisplayMode
open DisplayPosition

let find_before_pos was_auto_triggered dm e =
	let display_pos = ref (DisplayPosition.display_position#get) in
	let was_annotated = ref false in
	let is_annotated,is_completion = match dm with
		| DMDefault -> (fun p -> not !was_annotated && encloses_position !display_pos p),true
		| DMHover -> (fun p -> not !was_annotated && encloses_position_gt !display_pos p),false
		| _ -> (fun p -> not !was_annotated && encloses_position !display_pos p),false
	in
	let annotate e dk =
		was_annotated := true;
		(EDisplay(e,dk),pos e)
	in
	let annotate_marked e = annotate e DKMarked in
	let mk_null p = annotate_marked ((EConst(Ident "null")),p) in
	let loop_el el =
		let pr = DisplayPosition.display_position#with_pos (pos e) in
		let rec loop el = match el with
			| [] -> [mk_null pr]
			| e :: el ->
				if (pos e).pmin >= pr.pmax then (mk_null pr) :: e :: el
				else e :: loop el
		in
		(* print_endline (Printf.sprintf "%i-%i: PR" pr.pmin pr.pmax);
		List.iter (fun e ->
			print_endline (Printf.sprintf "%i-%i: %s" (pos e).pmin (pos e).pmax (Ast.s_expr e));
		) el; *)
		match el with
		| [] -> [mk_null pr]
		| e :: el ->
			if (pos e).pmin >= pr.pmax then (mk_null pr) :: e :: el
			else loop (e :: el)
	in
	let in_pattern = ref false in
	let loop e =
		(* print_endline (Printf.sprintf "%i-%i: %s" (pos e).pmin (pos e).pmax (Ast.s_expr e)); *)
		match fst e with
		| EFunction(FKNamed((_,p),_),_) when is_annotated p && is_completion ->
			raise Exit
		| EVars vl when is_annotated (pos e) && is_completion ->
			let rec loop2 acc mark vl = match vl with
				| v :: vl ->
					if mark then
						loop2 (v :: acc) mark vl
					else if is_annotated (snd v.ev_name) then
						(* If the name is the display position, mark the expression *)
						loop2 (v :: acc) true vl
					else begin match v.ev_expr with
						| None ->
							(* If there is no expression, we don't have to do anything.
							   Should the display position be on the type-hint, it will
							   be picked up while loading the type. *)
							loop2 (v :: acc) mark vl
						| Some e ->
							(* Determine the area between the `|` in `var x| = | e`. This is not really
							   correct because we don't want completion on the left side of the `=`, but
							   we cannot determine that correctly without knowing its position.
							   Note: We know `e` itself isn't the display position because this entire
							   algorithm is bottom-up and it would be marked already if it was. *)
							let p0 = match v.ev_type with
								| Some (_,pt) -> pt
								| None -> snd v.ev_name
							in
							let p = {p0 with pmax = (pos e).pmin} in
							let e = if is_annotated p then annotate_marked e else e in
							loop2 ({ v with ev_expr = Some e } :: acc) mark vl
					end
				| [] ->
					List.rev acc,mark
			in
			let vl,mark = loop2 [] false vl in
			let e = EVars (List.rev vl),pos e in
			if !was_annotated then e else raise Exit
		| EBinop((OpAssign | OpAssignOp _) as op,e1,e2) when is_annotated (pos e) && is_completion ->
			(* Special case for assign ops: If the expression is marked, but none of its operands are,
			   we are "probably" interested in the rhs. Like with EVars, this isn't accurate because we
			   could be on the left side of the `=`. I don't think there's a reason for requesting
			   completion there though. *)
			(EBinop(op,e1,annotate_marked e2)),(pos e)
		| EBinop(OpOr,e1,(EIf(_,(EConst(Ident "null"),_),None),p1)) when is_annotated (pos e) && is_completion && !in_pattern ->
			(* This HAS TO come from an attempted `case pattern | guard:` completion (issue #7068). *)
			let p = { p1 with pmin = (pos e1).pmax; pmax = p1.pmin } in
			EBinop(OpOr,e1,mk_null p),(pos e)
		| EIf(_,(EConst(Ident "null"),_),None) when is_completion && !in_pattern ->
			(* This is fine. *)
			mk_null (pos e)
		| EBlock [] when is_annotated (pos e) ->
			annotate e DKStructure
		| EBlock [EDisplay((EConst(Ident s),pn),DKMarked),_] when is_completion ->
			let e = EObjectDecl [(s,pn,NoQuotes),(EConst (Ident "null"),null_pos)],(pos e) in
			annotate e DKStructure
		| EBlock el when is_annotated (pos e) && is_completion ->
			let el = loop_el el in
			EBlock el,(pos e)
		| ECall(e1,el) when is_annotated (pos e) && is_completion ->
			let el = loop_el el in
			ECall(e1,el),(pos e)
		| ENew(ptp,el) when is_annotated (pos e) && is_completion ->
			if is_annotated ptp.pos_full || ptp.pos_full.pmax >= (DisplayPosition.display_position#get).pmax then
				annotate_marked e
			else begin
				let el = loop_el el in
				ENew(ptp,el),(pos e)
			end
		| EArrayDecl el when is_annotated (pos e) && is_completion ->
			let el = loop_el el in
			EArrayDecl el,(pos e)
		| EObjectDecl fl when is_annotated (pos e) && is_completion ->
			annotate e DKStructure
		| ESwitch(e1,cases,def) when is_annotated (pos e) ->
			(* We must be "between" two cases, or at the end of the last case.
			   Let's find the last case which has a position that is < the display
			   position and mark it. *)
			let did_mark = ref false in
			let mark_case ec p =
				did_mark := true;
				let ep = mk_null p in
				match ec with
				| Some ec ->
					let ec = match fst ec with
						| EBlock el -> (EBlock (el @ [ep]),p)
						| _ -> (EBlock [ec;ep],p)
					in
					Some ec
				| None ->
					Some (mk_null p)
			in
			let rec loop cases = match cases with
				| [el,eg,ec,p1] ->
					let ec = match def with
					| None when (pos e).pmax > !display_pos.pmin -> (* this is so we don't trigger if we're on the } *)
						mark_case ec p1 (* no default, must be the last case *)
					| Some (_,p2) when p1.pmax <= !display_pos.pmin && p2.pmin >= !display_pos.pmax ->
						mark_case ec p1 (* default is beyond display position, mark *)
					| _ ->
						ec (* default contains display position, don't mark *)
					in
					[el,eg,ec,p1]
				| (el1,eg1,ec1,p1) :: (el2,eg2,ec2,p2) :: cases ->
					if p1.pmax <= !display_pos.pmin && p2.pmin >= !display_pos.pmax then
						(el1,eg1,mark_case ec1 p1,p1) :: (el2,eg2,ec2,p2) :: cases
					else
						(el1,eg1,ec1,p1) :: loop ((el2,eg2,ec2,p2) :: cases)
				| [] ->
					[]
			in
			let cases = loop cases in
			let def = if !did_mark then
				def
			else match def with
				| Some(eo,p) when (pos e).pmax > !display_pos.pmin -> Some (mark_case eo p,p)
				| _ -> def
			in
			ESwitch(e1,cases,def),pos e
		| EDisplay _ ->
			raise Exit
		| EMeta((Meta.Markup,_,_),(EConst(String _),p)) when is_annotated p ->
			annotate_marked e
		| EConst (String (_,q)) when ((q <> SSingleQuotes) || was_auto_triggered) && is_annotated (pos e) && is_completion ->
			(* TODO: check if this makes any sense *)
			raise Exit
		| EConst(Regexp _) when is_annotated (pos e) && is_completion ->
			raise Exit
		| EVars vl when is_annotated (pos e) ->
			(* We only want to mark EVars if we're on a var name. *)
			if List.exists (fun v -> is_annotated (snd v.ev_name)) vl then
				annotate_marked e
			else
				raise Exit
		| _ ->
			if is_annotated (pos e) then
				annotate_marked e
			else
				e
	in
	let opt f o =
		match o with None -> None | Some v -> Some (f v)
	in
	let rec map e = match fst e with
		| ESwitch(e1,cases,def) when is_annotated (pos e) ->
			let e1 = map e1 in
			let cases = List.map (fun (el,eg,e,p) ->
				let old = !in_pattern in
				in_pattern := true;
				let el = List.map map el in
				in_pattern := old;
				let eg = opt map eg in
				let e = opt map e in
				el,eg,e,p
			) cases in
			let def = opt (fun (eo,p) -> opt map eo,p) def in
			loop (ESwitch (e1, cases, def),(pos e))
		| _ ->
			loop (Ast.map_expr map e)
	in
	try map e with Exit -> e

let find_display_call was_auto_triggered e =
	let found = ref false in
	let handle_el e el =
		let call_arg_is_marked () =
			el = [] || List.exists (fun (e,_) -> match e with EDisplay(_,DKMarked) -> true | _ -> false) el
		in
		if not was_auto_triggered || call_arg_is_marked () then begin
		found := true;
		Parser.mk_display_expr e DKCall
		end else
			e
	in
	let loop e = match fst e with
		| ECall(_,el) | ENew(_,el) when not !found && display_position#enclosed_in (pos e) ->
			handle_el e el
		| EArray(e1,e2) when not !found && display_position#enclosed_in (pos e2) ->
			handle_el e [e2]
		| EDisplay(_,DKCall) ->
			raise Exit
		| _ -> e
	in
	let rec map e = loop (Ast.map_expr map e) in
	try map e with Exit -> e