with
     openGL.Tasks,
     openGL.Errors,
     GL.lean,
     GL.Pointers,

     ada.Characters.latin_1,
     ada.Strings.unbounded,
     ada.Text_IO,
     ada.IO_Exceptions,

     interfaces.C.Strings;

-- use ada.Text_IO;


package body openGL.Shader
is
   use GL.lean,
       Interfaces;

   -----------
   --  Utility
   --
   function read_text_File (Filename : in String) return C.char_array;




   ---------
   --  Forge
   --

   function to_C_char_array (shader_Filename : in String) return C.char_array
   is
      use type interfaces.C.char_array;
   begin
      return   read_text_File (shader_Filename)
             & (1 => C.char (ada.Characters.Latin_1.NUL));
   end to_C_char_array;




   function to_C_char_array (shader_Snippets : in asset_Names) return C.char_array
   is
      use type interfaces.C.char_array;

      snippet_Id : Natural := 0;

      function combine_Snippets return C.char_array
      is
      begin
         snippet_Id := snippet_Id + 1;

         if snippet_Id < shader_Snippets'Last
         then
            return read_text_File (to_String (shader_Snippets (snippet_Id))) & combine_Snippets;
         else
            return read_text_File (to_String (shader_Snippets (snippet_Id)));
         end if;
      end combine_Snippets;

   begin
      return   combine_Snippets
             & (1 => C.char (ada.Characters.Latin_1.NUL));
   end to_C_char_array;




   procedure create_Shader (Self : in out Item;   Kind   : in Shader.Kind;
                                                  Source : in C.char_array)
   is
      use GL.Pointers,
          C.Strings;
      --  use ada.Text_IO;

      use type interfaces.C.char_array;

      the_Source       : aliased          C.char_array    := Source;
      the_Source_ptr   : aliased constant chars_ptr       := to_chars_ptr (the_Source'unchecked_Access);
      the_Source_Array : aliased          chars_ptr_array := [1 => the_Source_ptr];

   begin
      Tasks.check;


      --  new_Line (20);
      --  for i in the_Source'Range
      --  loop
      --     put (Character (the_Source (i)));
      --  end loop;


      Self.Kind := Kind;

      if Kind = Vertex
      then   Self.gl_Shader := glCreateShader (GL_VERTEX_SHADER);
      else   Self.gl_Shader := glCreateShader (GL_FRAGMENT_SHADER);
      end if;

      Errors.log;

      glShaderSource (Self.gl_Shader,
                      1,
                      to_GLchar_Pointer_access (the_Source_array'Access),
                      null);
      Errors.log;

      glCompileShader (Self.gl_Shader);
      Errors.log;

      declare
         use interfaces.C;
         Status : aliased gl.glInt;
      begin
         glGetShaderiv (self.gl_Shader,
                        GL_COMPILE_STATUS,
                        Status'unchecked_Access);
         if Status = 0
         then
            declare
               compile_Log : constant String := Self.shader_info_Log;
            begin
               Self.destroy;
               raise Error with "'" & to_Ada (the_Source) & "' compilation failed ~ " & compile_Log;
            end;
         end if;
      end;
   end create_Shader;




   procedure define (Self : in out Item;   Kind            : in Shader.Kind;
                                           shader_Filename : in String)
   is
      the_Source : aliased constant C.char_array := to_C_char_array (shader_Filename);
   begin
      create_Shader (Self, Kind, the_Source);
   end define;




   procedure define (Self : in out Item;   Kind            : in Shader.Kind;
                                           shader_Snippets : in asset_Names)
   is
      the_Source : aliased constant C.char_array := to_C_char_array (shader_Snippets);
   begin
      create_Shader (Self, Kind, the_Source);
   end define;




   procedure destroy (Self : in out Item)
   is
   begin
      Tasks.check;
      glDeleteShader (self.gl_Shader);
   end destroy;



   --------------
   --  Attributes
   --

   function shader_info_Log (Self : in Item) return String
   is
      use C, GL;

      info_log_Length : aliased  glInt   := 0;
      chars_Written   : aliased  glSizei := 0;
   begin
      Tasks.check;

      glGetShaderiv (Self.gl_Shader,
                     GL_INFO_LOG_LENGTH,
                     info_log_Length'unchecked_Access);

      if info_log_Length = 0
      then
         return "";
      end if;

      declare
         use gl.Pointers;
         info_Log     : aliased  C.char_array        := C.char_array' [1 .. C.size_t (info_log_Length) => <>];
         info_Log_ptr : constant C.Strings.chars_Ptr := C.Strings.to_chars_ptr (info_Log'unchecked_Access);
      begin
         glGetShaderInfoLog (self.gl_Shader,
                             glSizei (info_log_Length),
                             chars_Written'unchecked_Access,
                             to_GLchar_access (info_Log_ptr));

         return C.to_Ada (info_Log);
      end;
   end shader_info_Log;




   ----------
   --  Privvy
   --

   function gl_Shader (Self : in Item) return a_gl_Shader
   is
   begin
      return Self.gl_Shader;
   end gl_Shader;




   -----------
   --  Utility
   --

   function read_text_File (Filename : in String) return C.char_array
   is
      use ada.Text_IO,
          ada.Strings.unbounded;

      use type interfaces.C.size_t;

      NL        : constant String      := "" & ada.characters.latin_1.LF;
      the_File  : ada.Text_IO.File_type;
      Pad       : unbounded_String;

   begin
      if Filename = ""
      then
         return C.char_array' (1 .. 0 => <>);
      else
         open (the_File, in_File, Filename);

         while not end_of_File (the_File)
         loop
            append (Pad, get_Line (the_File) & NL);
         end loop;

         close (the_File);

         if Length (Pad) = 0
         then
            return C.char_array' (1 .. 0 => <>);
         else
            declare
               the_Data : C.char_array (0 .. C.size_t (Length (Pad) - 1));
            begin
               for i in the_Data'Range
               loop
                  the_Data (i) := C.char (Element (Pad, Integer (i) + 1));
               end loop;

               --  the_Data (the_Data'Last) := C.char'Val (0);

               return the_Data;
            end;
         end if;
      end if;

   exception
      when ada.IO_Exceptions.name_Error =>
         raise Error with "Unable to locate shader asset named '" & Filename & "'.";
   end read_text_File;


end openGL.Shader;
