with Ada.Sequential_IO;
with Ada.Text_IO;
with Bit_Ops;

package body CPU is
   function Get_Opcode (Inst : in out Instance) return Opcode is
      Op : aliased Opcode_Raw;
      Op_Record : Opcode with Address => Op'Address;
   begin
      Op := Opcode_Raw (Inst.Memory (Inst.Program_Counter)) * 2 ** 8;
      Op := Op + Opcode_Raw (Inst.Memory (Inst.Program_Counter + 1));
      Inst.Program_Counter := Inst.Program_Counter + 2;
      return Op_Record;
   end Get_Opcode;

   procedure Reg_Store (Inst : in out Instance; VX : Register_Index) is
   begin
      for I in 0 .. VX loop
         Inst.Memory (Inst.Address_Register + Address (I)) :=
            Inst.Registers (I);
      end loop;
   end Reg_Store;

   procedure Reg_Load (Inst : in out Instance; VX : Register_Index) is
   begin
      for I in 0 .. VX loop
         Inst.Registers (I) := Inst.Memory
            (Inst.Address_Register + Address (I));
      end loop;
   end Reg_Load;

   procedure Ret (Inst : in out Instance) is
   begin
      Jump (Inst, Inst.Stack.Last_Element);
      Inst.Stack.Delete_Last;
   end Ret;

   procedure Call (Inst : in out Instance; A : Address) is
   begin
      Inst.Stack.Append (Inst.Program_Counter);
      Jump (Inst, A);
   end Call;

   procedure Jump (Inst : in out Instance; A : Address) is
   begin
      Inst.Program_Counter := A;
   end Jump;

   procedure Skip (Inst : in out Instance) is
   begin
      Inst.Program_Counter := Inst.Program_Counter + 2;
   end Skip;

   procedure Math (Inst : in out Instance; VX, VY : Register_Index; N : Byte)
   is begin
      case N is
         when 0 =>
            Inst.Registers (VX) := Inst.Registers (VY);
         when 1 =>
            Inst.Registers (VX) := Bit_Ops.Bitwise_Or
               (Inst.Registers (VX), Inst.Registers (VY));
         when 2 =>
            Inst.Registers (VX) := Bit_Ops.Bitwise_And
               (Inst.Registers (VX), Inst.Registers (VY));
         when 3 =>
            Inst.Registers (VX) := Bit_Ops.Bitwise_Xor
               (Inst.Registers (VX), Inst.Registers (VY));
         when 4 =>
            declare
               X : constant Byte := Inst.Registers (VX);
               Y : constant Byte := Inst.Registers (VY);
            begin
               Inst.Registers (VX) := X + Y;
               Inst.Registers (15) :=
                  (if Integer (X) + Integer (Y) > Integer (X + Y)
                     then 1 else 0);
            end;
         when 5 =>
            declare
               X : constant Byte := Inst.Registers (VX);
               Y : constant Byte := Inst.Registers (VY);
            begin
               Inst.Registers (VX) := X - Y;
               Inst.Registers (15) := (if X >= Y then 1 else 0);
            end;
         when 6 =>
            Inst.Registers (15) := Inst.Registers (VX) mod 2;
            Inst.Registers (VX) := Inst.Registers (VX) / 2;
         when 14 =>
            Inst.Registers (15) := Inst.Registers (VX) / (2 ** 7);
            Inst.Registers (VX) := Inst.Registers (VX) * 2;
         when others => begin
            Ada.Text_IO.Put_Line ("Uh oh!");
            Ada.Text_IO.Put_Line (Byte'Image (N));
         end;
      end case;
   end Math;

   procedure Load_File (Inst : in out Instance; File_Name : String) is
      package Byte_IO is new Ada.Sequential_IO (Byte);

      I : Address := Start_Address;
      File_Handle : Byte_IO.File_Type;
   begin
      Byte_IO.Open (File_Handle, Byte_IO.In_File, File_Name);

      while not Byte_IO.End_Of_File (File_Handle) loop
         Byte_IO.Read (File_Handle, Inst.Memory (I));
         I := I + 1;
      end loop;

      Byte_IO.Close (File_Handle);
   end Load_File;
end CPU;