(*naloga instruction modula je ustvarjanje instructionLista, torej vsebine T zapisa*)
(*sem spada izbira nacina naslavljanja, dolocanje ali je operand simbol itd..*)

open SemanticAnalyzer
open Simtab
open OpcodeTable

(*podatki o base naslavljanju*)
type base_info = {
  is_base : bool;
  valueB : int
}

type operand = 
  | R of register
  | RR of register * register
  | RN of register * int (*register in število*)
  | N of int  (*številski operand*)
  | S of string (*simbol*) 
  | None


let string_is_symbol (s : string) : bool * operand = 
  try
    let n = int_of_string s in 
    false, N n
  with Failure _  -> true, S s

(*preveri ali je operand iz mnemonic_type simbol ali stevilo*)
let operand_is_symbol (x : lineSemantic) : (bool * operand * int) option = (*vrne isSymbol, operand in dolzino ukaza, ali none, če ukaza/direktive ne bomo zapisali v T file*)
  match x.mnem with
  | MnemonicD -> None
  | MnemonicDn s -> let (issym, operand) = string_is_symbol s in Some (issym, operand, x.len)
  | MnemonicF1 -> Some (false, None, x.len)
  | MnemonicF2n n -> Some (false, N n, x.len)
  | MnemonicF2r r1 -> Some (false, R r1, x.len)
  | MnemonicF2rn (r1, n) -> Some (false, RN (r1, n), x.len)
  | MnemonicF2rr (r1, r2) -> Some (false, RR (r1, r2), x.len)
  | MnemonicF3 -> Some (false, None, x.len)
  | MnemonicF3m s -> let (issym, operand) = string_is_symbol s in Some (issym, operand, x.len)
  | MnemonicF4m s -> let (issym, operand) = string_is_symbol s in Some (issym, operand, x.len)
  | MnemonicSd s -> let (issym, operand) = string_is_symbol s in Some (issym, operand, x.len)
  | MnemonicSn _ -> None(*RESW in RESB že drugje obdelamo*)
  | COMMENT -> None (*Comment ze obdelamo drugje*)

(*helper funkcija za registre*)
let int_of_reg (reg : register) : int =
  match reg with
  | A -> 0
  | X -> 1
  | L -> 2
  | B -> 3
  | S -> 4
  | T -> 5
  | F -> 6
  | PC -> 8
  | SW -> 9

(*helper funkcija*)
let int_to_hex_width (width : int) (n : int) : string =
  let s = Printf.sprintf "%X" n in
  let len = String.length s in
  if len >= width then (String.sub s (len - width) width)
  else String.make (width - len) '0' ^ s

let char_to_hex c =
  Printf.sprintf "%02X" (Char.code c)

(*preveri ali laho pc relativno nasljavljas in vrni disp*)
let is_pc_relative (loc : int) (target : int) : bool * int =
  let pc = loc + 3 in
  let disp = target - pc in
  if disp >= -2048 && disp <= 2047 then
    (true, disp)
  else
    (false, 0)

(*preveri ali lahko bazno naslavljamo*)
let is_base_relative (target : int) (valueB : int) : bool * int = 
  let disp = valueB - target in
  if disp >= 0 && disp <= 4095 then
    (true, disp)
  else
    (false, 0)

(*preveri ali gre v direktno naslavljanje 0 <= addr <= 4095*)    
let is_direct (target: int) : bool * int =
  if target >= 0 && target <= 4095 then 
    true, target
  else
    false, 0 

(*vrne xbpe in disp*)
let get_xbpe_disp (x : bool) (loc : int option) (target : int) (base_info : base_info) : int * int =
  let location = match loc with | None -> failwith "location of instructin in None" | Some i -> i in
  (*probamo pc relativno*)
  let p, disp = is_pc_relative location target in
  if p then 
    let x_int = if x then 8 else 0 in 
    let p_int = 2 in
    let xbpe = x_int + p_int in
    xbpe, disp
  (*ce je bazno omogoceno in deluje*)
  else

  let b, disp = is_base_relative base_info.valueB target in
  if base_info.is_base && b then
  let x_int = if x then 8 else 0 in
  let b_int = 4 in 
  let xbpe = x_int + b_int in 
  xbpe, disp 
  (*če gre direktno*)
  else
  let direct, disp = is_direct target in
  if direct then
    let x_int = if x then 8 else 0 in 
    let xbpe = x_int in
    xbpe, disp
  else
    failwith (Printf.sprintf "No adressing mode is appropriate for target %d" target) 



(*naredi binaren zapis F1 oz string nibblov celotnega ukaza*)
let get_bin_instruction_F1 (opcode : int) : string = 
  int_to_hex_width 2 opcode

(*naredi binaren zapis F2 oz string nibblov celotnega ukaza*) 
let get_bin_instruction_F2 (opcode : int) (operand : operand) : string = 
  match operand with
  | RR (r1, r2) -> (int_to_hex_width 2 opcode) ^ (int_to_hex_width 1 (int_of_reg r1)) ^ (int_to_hex_width 1 (int_of_reg r2))
  | RN (r1, n) ->  (int_to_hex_width 2 opcode) ^ (int_to_hex_width 1 (int_of_reg r1)) ^ (int_to_hex_width 1 n)
  | R r1 ->  (int_to_hex_width 2 opcode) ^ (int_to_hex_width 1 (int_of_reg r1)) ^ (int_to_hex_width 1 0) (*r2 -> 0*)
  | N n ->  (int_to_hex_width 2 opcode) ^ (int_to_hex_width 1 n) ^ (int_to_hex_width 1 0)
  | _ -> failwith "invalid operand type for F2 instruction"

let get_bin_instruction_F3 (opcode : int) (simtab : symtab) (operand : operand)
   (loc : int option) (x : bool) (n : bool) (i : bool) (base_info : base_info) : string = 
  let oppcode_with_ni = match (n, i) with
  | (false, false) -> opcode + 0
  | (false, true) -> opcode + 1
  | (true, false) -> opcode + 2
  | (true, true ) -> opcode + 3
  in
  match operand with
  | None -> (int_to_hex_width 2 oppcode_with_ni) ^ (int_to_hex_width 4 0) (*RSUB damo z disp =  0*) 
  | N n -> 
    let xbpe = if x then 8 else 0 in (*ce imamo stevilsko vrednost uporabimo direktno naslavljanje*)
    (int_to_hex_width 2 oppcode_with_ni) ^ (int_to_hex_width 1 xbpe) ^ (int_to_hex_width 3 n)
  | S s -> 
    (*poiscemo naslov simbola, dolocimo xbpe in disp*)
    let operand = Hashtbl.find simtab s in
    let xbpe, disp = get_xbpe_disp x loc operand base_info in
    (int_to_hex_width 2 oppcode_with_ni) ^ (int_to_hex_width 1 xbpe) ^ (int_to_hex_width 3 disp)
  | _ -> ""

let get_bin_instruction_F4 (opcode : int) (simtab : symtab) (operand : operand) (x : bool) (n : bool) (i : bool) : string = 
  let oppcode_with_ni = match (n, i) with
  | (false, false) -> opcode + 0
  | (false, true) -> opcode + 1
  | (true, false) -> opcode + 2
  | (true, true ) -> opcode + 3
  in
  let xbpe = if x then 9 else 1 in (*pri F4 imamo le direktno, zato je p = 0, b = 0, e=1*)
  match operand with
  | None -> (int_to_hex_width 2 oppcode_with_ni) ^ (int_to_hex_width 6 0) (*RSUB damo z disp =  0*) 
  | N n -> (int_to_hex_width 2 oppcode_with_ni) ^ (int_to_hex_width 1 xbpe) ^ (int_to_hex_width 5 n)
  | S s -> let operand = Hashtbl.find simtab s in (int_to_hex_width 2 oppcode_with_ni) ^ (int_to_hex_width 1 xbpe) ^ (int_to_hex_width 5 operand)
  | _ -> ""

(*vrni bin zapis instrukcij tipa F1, F2, F3, F4*)
let create_opcode (simtab : symtab) (opcodeTab : opcodeTab) (opcode : mnemonic)
  (operand : operand) (formatLen : int) (loc : int option) (x : bool) (n : bool) (i : bool) (base_info: base_info): string = 
  (*izberi nacin naslavljanja in naredi string ukaza*)
  match formatLen with
  | 1 -> get_bin_instruction_F1 (find_opcode opcodeTab opcode) 
  | 2 -> get_bin_instruction_F2 (find_opcode opcodeTab opcode) operand
  | 3 -> get_bin_instruction_F3 (find_opcode opcodeTab opcode) simtab operand loc x n i base_info
  | 4 -> get_bin_instruction_F4 (find_opcode opcodeTab opcode) simtab operand x n i
  | _ -> ""

(*opcode BYTE direktive*)
let create_opcode_byte (operand : string) : string =
  if String.length operand < 3 then
    failwith "Invalid BYTE operand, use X'...' or C'...'!"
  else
    match operand.[0] with
    | 'C' -> 
      (* e.g., C'EOF' *)
      let content = String.sub operand 2 (String.length operand - 3) in
      let len = String.length content in 
      if len <> 1 then failwith "BYTE C'...' has to have a operand of length 1 inside hte '...' brackets!" else
      String.concat "" (List.init (String.length content) (fun i -> char_to_hex content.[i]))
    | 'X' ->
      (* e.g., X'F1' *)
        let hex = String.sub operand 2 (String.length operand - 3) in
        if String.length hex >= 6 then
          failwith "WORD X'...' has to have a operand of length <= 6 inside hte '...' brackets!" 
        else String.make (2 - String.length hex) '0' ^ hex
    | _ ->
        let n = int_of_string operand in
        Printf.sprintf "%02X" n

let create_opcode_word (operand : string) : string =
  if String.length operand = 0 then
    failwith "Invalid WORD operand use X'...' or C'...'!"
  else
    match operand.[0] with
    | 'C' ->
        (* WORD as C'...' *)
        let content = String.sub operand 2 (String.length operand - 3) in
        let len = String.length content in 
        if len <> 3 then failwith "WORD C'...' has to have a operand of length 3 inside hte '...' brackets!" else
        let hex = String.concat "" (List.init len (fun i -> char_to_hex content.[i])) in
        (* pad to 6 hex digits for WORD *)
        if String.length hex >= 6 then hex else String.make (6 - String.length hex) '0' ^ hex
    | 'X' ->
        (* WORD as X'...' *)
        let hex = String.sub operand 2 (String.length operand - 3) in
        if String.length hex >= 6 then
          failwith "WORD X'...' has to have a operand of length <= 6 inside hte '...' brackets!" 
        else String.make (6 - String.length hex) '0' ^ hex
    | _ ->
        (* numeric constant *)
        let n = int_of_string operand in
        Printf.sprintf "%06X" n


let print_text_line_instruction (opcode : mnemonic) (mnem : mnemonic_type) (newTextLine : string) : unit =
  Printf.printf "zbirali %s, %s -> %s\n" (string_of_mnemonic opcode) (string_of_mnemonic_type mnem) newTextLine
