-- computerman is multiway user tools.
-- Computerman is Copyright (C) 2024 Manuel De Girardi ; 
--
--   This program is free software; you can redistribute it and/or modify
--   it under the terms of the GNU General Public License as published by
--   the Free Software Foundation; either version 2 of the License, or
--   (at your option) any later version.
--
--   This program is distributed in the hope that it will be useful,
--   but WITHOUT ANY WARRANTY; without even the implied warranty of
--   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--   GNU General Public License for more details.
--
--   You should have received a copy of the GNU General Public License
--   along with this program; if not, write to the Free Software
--   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
--
-- Date := "2024-05-10 09:03:04"
-- Version := "0.0.0r"

with Computer.MIDI.Portmidi;
use Computer.MIDI;

with Text_Io;                           use Text_Io;

with System;

with Ada.Finalization;                  use Ada.Finalization;

package body Computer.Midi.Drivers is
      function Hex_Image(Num : Interfaces.C.Long) return String is
      Image : String(1..9) := (others => '0');
      Result : String(1..9) := (others => '0');
      Last  : positive := 9;
      Number : Interfaces.C.long := num;
      type Hexa_Digit is
        ('0', '1', '2', '3', '4', '5', '6', '7',
         '8', '9', 'A', 'B', 'C', 'D', 'E', 'F');

      Div, Rest : Interfaces.C.Long := 0;

   begin

      loop
         Div := Number/16;

         Rest := Number-(Div*16);
         image(Last) := Character'Value(Hexa_Digit'image(Hexa_Digit'val((Rest))));
         exit when (Number/16) = 0;
         Last := Last - 1;
         number := Number/16;

      end loop;
      Result(1..(Image'Length - (Last-1))) := Image(Last..Image'length);

      if Num < 16 then
         return '0' & Result (1..(Image'Length - (Last-1)));
      else
         return Result (1..(Image'Length - (Last-1)));
      end if;
   end Hex_Image;
   
   
   use Portmidi.ErrorText_Conversion, Portmidi.DeviceInfo_Conversion;
   
   
   function Inputs_List return Devices_List_Access is
      
      Input_Count : Natural := 0;
      Device : Device_Type;
   begin
      
      Device.The_DeviceInfo := new Portmidi.DeviceInfo;
      Device.Addr := new System.Address;
      for I in 0..Portmidi.Pm_CountDevices-1 loop
	 Device.The_DeviceInfo.all :=
	   To_pointer(Portmidi.Pm_GetDeviceInfo(I)).all;
	 if Device.The_Deviceinfo.Input = 1 then
	    Input_Count := Input_Count + 1;
	 end if;
      
      end loop;
      
      declare
	 Devices_List : constant Devices_List_Access := new Devices_List_Type(1..Input_Count);
	 List_Index : Natural := 0;
      begin
	 for I in 0..Portmidi.Pm_CountDevices-1 loop
	    declare
	       Name : Portmidi.T_ErrorText;
	    begin
	       Device.The_DeviceInfo.all :=
		 To_pointer(Portmidi.Pm_GetDeviceInfo(I)).all;
	       if Device.The_Deviceinfo.Input = 1 then
		  Name := To_Pointer(Device.The_Deviceinfo.name).all;
		  Devices_List(List_Index + 1).Name := new String ' (Interfaces.C.To_Ada(Name));
		  Devices_List(List_Index + 1).Id := I;
		  List_Index := List_Index + 1;
	       end if;
	    end;
	 end loop;
	 return Devices_List;
      end;
   end Inputs_List;
   
   
   protected body OutputDriver_Type is
      
      entry Receive(Message : in Long) when True is
	 
      begin 
	 
	 if Device_Driver.Output /= null then

	    Write(Device_Driver.Output.all, Message);
	    
	 end if;	 
      end Receive;
   end OutputDriver_Type;
   
   
   task body InputDriver_Type is            
      End_Of_Task : Boolean := False;
   begin
      while not End_Of_Task loop
	 loop
	    select
	       accept Stop;
	    or
	       accept Start;
	       exit;
	    or
	       accept Halt do
		  Text_Io.Put_Line("halting input driver");
	       end Halt;
	       End_Of_Task := True;
	       exit;	       
	    end select;
	 end loop;
	 Text_Io.Put_Line("Input_Driver starting...");
	 while not End_Of_Task loop
	    
	    select
	       accept Stop;
	       exit;
	    or
	       accept Halt do
		  Text_Io.Put_Line("halting input driver");
	       end Halt;
	       
	       End_Of_Task := True;
	       exit;
	    or 
	       delay 0.1;
	    
	       declare
		  The_Message : Long := 0;	    
	       begin
		  Read(Device_Driver.Input.all, The_Message);	    	    		  
		  --Text_Io.Put_Line("Message Received...");
		  --if (The_Message > 0) and (The_Message /= 248) then		     
		  --Text_Io.Put_Line("Message Ok !");
		     select
			accept Send(Message : out Long) do
			   Message := The_Message;
			end Send;   
			--Text_Io.Put_Line("Message Sended...");
		     or
			accept Halt do
			   Text_Io.Put_Line("halting input driver");
			end Halt;
			
			End_Of_Task := True;
			exit;

		     or
			delay 0.1;
			--Text_Io.Put_Line("Message lost...");
		     end select;
	       --end if;	    
		  
	       exception
		  when Constraint_Error =>
		     Text_Io.Put_Line("Constraint error");
	       end;
	    end select;
	 end loop; 
	 Text_Io.Put_Line("Input_Driver stopping...");
      end loop;
      Text_Io.Put_Line("Input_Driver Halted...");
   end InputDriver_Type;

   function Outputs_List return Devices_List_Access is
      
      output_Count : Natural := 0;
      Device : Device_Type;
   begin
      
      Device.The_DeviceInfo := new Portmidi.DeviceInfo;
      Device.Addr := new System.Address;
      for I in 0..Portmidi.Pm_CountDevices-1 loop
	 Device.The_DeviceInfo.all :=
	   To_pointer(Portmidi.Pm_GetDeviceInfo(I)).all;
	 
	 if Device.The_Deviceinfo.Output = 1 then
	    output_Count := output_Count + 1;
	 end if;      
      end loop;
      
      declare
	 Devices_List : constant Devices_List_Access := new Devices_List_Type(1..output_Count);
	 List_Index : Natural := 0;
      begin
	 for I in 0..Portmidi.Pm_CountDevices-1 loop
	    declare
	       Name : Portmidi.T_ErrorText;
	    begin
	       Device.The_DeviceInfo.all :=
		 To_pointer(Portmidi.Pm_GetDeviceInfo(I)).all;
	       if Device.The_Deviceinfo.Output = 1 then
		  Name := To_Pointer(Device.The_Deviceinfo.name).all;
		  Devices_List(List_Index + 1).Name := new String ' (Interfaces.C.To_Ada(Name));
		  Devices_List(List_Index + 1).Id := I;
		  List_Index := List_Index + 1;
	       end if;
	    end;
	 end loop;
	 return Devices_List;
      end;
   end Outputs_List;   
   
   use Interfaces;
   
   function Control_Of(Message : Interfaces.C.Long) return Control_Type is
      
      The_Status  : constant Unsigned_32 := Unsigned_32(Portmidi.Status(Message));
      Data        : constant Unsigned_32 := Unsigned_32(Portmidi.Data1(Message));
      The_control : Control_Type := Null_Item;
   begin
      if ((The_Status and 16#70#) = 0) then
	 The_Control := Note_Off;
      elsif ((The_Status and 16#60#) = 0) then
	 The_Control := Note_on;
      elsif ((The_Status and 16#40#) = 0) then
	 The_Control := Control;
      elsif ((The_Status and 16#30#) = 0) then
	 The_Control := Program_Change;
      elsif ((The_Status and 16#20#) = 0) then
	 The_Control := Channel_Pressure;
      elsif ((The_Status and 16#10#) = 0) then
	 The_Control := Pitchbend;
      end if;
      return The_Control;
   end Control_of;
   
   
   
   function Channel_Of(Message : in Long) return Channel_Type is      
   begin        
      return Channel_type(Unsigned_32(Portmidi.Status(Message)) and 16#0F#);
   end Channel_Of;

   
   procedure Initialize (Device : in out Device_Type; Mode : in Mode_Type; Info : in Device_Info_type) is
      
   begin
      case Mode is
         when MIDI_Out =>
            Device.The_DeviceInfo := new Portmidi.DeviceInfo;
            Device.Addr := new System.Address;
	    for I in 0..Portmidi.Pm_CountDevices-1 loop
	       declare
		  Name : Portmidi.T_ErrorText;
	       begin
		  Device.The_DeviceInfo.all :=
		    To_pointer(Portmidi.Pm_GetDeviceInfo(I)).all;
		  Name := To_Pointer(Device.The_Deviceinfo.name).all;
		  if Interfaces.C.To_Ada(Name) = Info.Name.all then
		     if Device.The_Deviceinfo.Output = 1 Then
			Device.ID := I;
			Device.Initialized := True;
			Device.Mode := MIDI_Out;
			
			
			
			exit;		     
		     	
		     end if;		 
		  end if;
	       end;      
	    end loop;
         when MIDI_In =>
            Device.The_DeviceInfo := new Portmidi.DeviceInfo;
            Device.Addr := new System.Address;	    
	    for I in 0..Portmidi.Pm_CountDevices-1 loop
	       declare
		  Name : Portmidi.T_ErrorText;
	       begin        
		  Device.The_DeviceInfo.all :=
		    To_pointer(Portmidi.Pm_GetDeviceInfo(I)).all;
		  Name := To_Pointer(Device.The_Deviceinfo.name).all;
		  if Interfaces.C.To_Ada(Name) = Info.Name.all then
		     if Device.The_Deviceinfo.Input = 1 Then
			
			Device.ID := I;
			Device.Initialized := True;
			Device.Mode := MIDI_In;
			Name := To_Pointer(Device.The_Deviceinfo.name).all;
			
			
			exit;
			
		     end if;
		  end if;
	       end;
	    end loop;
            
      end case;
   end Initialize;

   
   procedure Initialize_Input(Input_Device_Driver : in out Input_Device_Driver_Type; 
			      Device_Info : in Device_Info_Type) is
   begin
      if Device_Info = Null_Device_Info then
	 Input_Device_Driver.Input := null;
	 
      else
	 Input_Device_Driver.Input := new Device_Type;
	 
	 Initialize(Input_Device_Driver.Input.all, MIDI_In, Device_Info);
	 Input_Device_Driver.Device_Info := Device_Info;
      end if;
   end Initialize_Input;
   
   procedure Initialize_Output(Output_Device_Driver : in out Output_Device_Driver_Type;
			       Device_Info : in Device_Info_Type) is
   begin
      if Device_Info = Null_Device_Info then
	 Output_Device_Driver.Output := null;
      else
	 Output_Device_Driver.Output := new Device_Type;
	 Initialize(Output_Device_Driver.Output.all, MIDI_Out, Device_Info);
	 Output_Device_Driver.Device_Info := Device_Info;
      end if;
   end Initialize_Output;

end Computer.Midi.Drivers ;