with GID.Buffering;                     use GID.Buffering;
with GID.Color_tables;

package body GID.Decoding_TGA is

  ----------
  -- Load --
  ----------

  procedure Load (image: in out Image_descriptor) is

    procedure Row_start(y: Natural) is
    begin
      if image.flag_1 then -- top first
        Set_X_Y(0, image.height-1-y);
      else
        Set_X_Y(0, y);
      end if;
    end Row_Start;

    -- Run Length Encoding --
    RLE_pixels_remaining: Natural:= 0;
    is_run_packet: Boolean;

    type Pixel is record
      color: RGB_Color;
      alpha: U8;
    end record;

    pix, pix_mem: Pixel;

    generic
      bpp: Positive;
      pal: Boolean;
    procedure Get_pixel;
    pragma Inline(Get_Pixel);
    --
    procedure Get_pixel is
      idx: Natural;
      p1, p2, c, d: U8;
    begin
      if pal then
        if image.palette'Length <= 256 then
          Get_Byte(image.buffer, p1);
          idx:= Natural(p1);
        else
          Get_Byte(image.buffer, p1);
          Get_Byte(image.buffer, p2);
          idx:= Natural(p1) + Natural(p2) * 256;
        end if;
        idx:= idx + image.palette'First;
        pix.color:= image.palette(idx);
      else
        case bpp is
          when 32 => -- BGRA
            Get_Byte(image.buffer, pix.color.blue);
            Get_Byte(image.buffer, pix.color.green);
            Get_Byte(image.buffer, pix.color.red);
            Get_Byte(image.buffer, pix.alpha);
          when 24 => -- BGR
            Get_Byte(image.buffer, pix.color.blue);
            Get_Byte(image.buffer, pix.color.green);
            Get_Byte(image.buffer, pix.color.red);
          when 16 | 15 => -- 5 bit per channel
            Get_Byte(image.buffer,  c);
            Get_Byte(image.buffer,  d);
            Color_tables.Convert(c, d, pix.color);
            if bpp=16 then
              pix.alpha:= U8((U16(c and 128) * 255)/128);
            end if;
          when 8  => -- Gray
            Get_Byte(image.buffer, pix.color.green);
            pix.color.red:= pix.color.green;
            pix.color.blue:= pix.color.green;
          when others =>
            null;
        end case;
      end if;
    end Get_pixel;

    generic
      bpp: Positive;
      pal: Boolean;
    procedure RLE_Pixel;
    pragma Inline(RLE_Pixel);
    --
    procedure RLE_Pixel is
      tmp: U8;
      procedure Get_pixel_for_RLE is new Get_pixel(bpp, pal);
    begin
      if RLE_pixels_remaining = 0 then -- load RLE code
        Get_Byte(image.buffer, tmp );
        Get_pixel_for_RLE;
        RLE_pixels_remaining:= U8'Pos(tmp and 16#7F#);
        is_run_packet:= (tmp and 16#80#) /= 0;
        if is_run_packet then
          pix_mem:= pix;
        end if;
      else
        if is_run_packet then
          pix:= pix_mem;
        else
          Get_pixel_for_RLE;
        end if;
        RLE_pixels_remaining:= RLE_pixels_remaining - 1;
      end if;
    end RLE_Pixel;

    procedure RLE_pixel_32      is new RLE_pixel(32, False);
    procedure RLE_pixel_24      is new RLE_pixel(24, False);
    procedure RLE_pixel_16      is new RLE_pixel(16, False);
    procedure RLE_pixel_15      is new RLE_pixel(15, False);
    procedure RLE_pixel_8       is new RLE_pixel(8,  False);
    procedure RLE_pixel_palette is new RLE_pixel(1,  True); -- 1: dummy

    procedure Output_Pixel is
    pragma Inline(Output_Pixel);
    begin
      case Primary_color_range'Modulus is
        when 256 =>
          Put_Pixel(
            Primary_color_range(pix.color.red),
            Primary_color_range(pix.color.green),
            Primary_color_range(pix.color.blue),
            Primary_color_range(pix.alpha)
          );
        when 65_536 =>
          Put_Pixel(
            16#101#  * Primary_color_range(pix.color.red),
            16#101#  * Primary_color_range(pix.color.green),
            16#101#  * Primary_color_range(pix.color.blue),
            16#101#  * Primary_color_range(pix.alpha)
            -- 16#101# because max intensity FF goes to FFFF
          );
        when others =>
          raise invalid_primary_color_range;
      end case;
    end Output_Pixel;

    procedure Get_RGBA is -- 32 bits
      procedure Get_pixel_32 is new Get_pixel(32, False);
    begin
      for y in 0..image.height-1 loop
        Row_start(y);
        for x in 0..image.width-1 loop
          Get_pixel_32;
          Output_Pixel;
        end loop;
        Feedback(((y+1)*100)/image.height);
      end loop;
    end Get_RGBA;

    procedure Get_RGB is -- 24 bits
      procedure Get_pixel_24 is new Get_pixel(24, False);
    begin
      for y in 0..image.height-1 loop
        Row_start(y);
        for x in 0..image.width-1 loop
          Get_pixel_24;
          Output_Pixel;
        end loop;
        Feedback(((y+1)*100)/image.height);
      end loop;
    end Get_RGB;

    procedure Get_16 is -- 16 bits
      procedure Get_pixel_16 is new Get_pixel(16, False);
    begin
      for y in 0..image.height-1 loop
        Row_start(y);
        for x in 0..image.width-1 loop
          Get_pixel_16;
          Output_Pixel;
        end loop;
        Feedback(((y+1)*100)/image.height);
      end loop;
    end Get_16;

    procedure Get_15 is -- 15 bits
      procedure Get_pixel_15 is new Get_pixel(15, False);
    begin
      for y in 0..image.height-1 loop
        Row_start(y);
        for x in 0..image.width-1 loop
          Get_pixel_15;
          Output_Pixel;
        end loop;
        Feedback(((y+1)*100)/image.height);
      end loop;
    end Get_15;

    procedure Get_Gray is
      procedure Get_pixel_8  is new Get_pixel(8, False);
    begin
      for y in 0..image.height-1 loop
        Row_start(y);
        for x in 0..image.width-1 loop
          Get_pixel_8;
          Output_Pixel;
        end loop;
        Feedback(((y+1)*100)/image.height);
      end loop;
    end Get_Gray;

    procedure Get_with_palette is
      procedure Get_pixel_palette  is new Get_pixel(1, True); -- 1: dummy
    begin
      for y in 0..image.height-1 loop
        Row_start(y);
        for x in 0..image.width-1 loop
          Get_pixel_palette;
          Output_Pixel;
        end loop;
        Feedback(((y+1)*100)/image.height);
      end loop;
    end Get_with_palette;

  begin
    pix.alpha:= 255; -- opaque is default
    Attach_Stream(image.buffer, image.stream);
    --
    if image.RLE_encoded then
      -- One format check per row
      RLE_pixels_remaining:= 0;
      for y in 0..image.height-1 loop
        Row_start(y);
        if image.palette /= null then
          for x in 0..image.width-1 loop
            RLE_pixel_palette;
            Output_Pixel;
          end loop;
        else
          case image.bits_per_pixel is
            when 32 =>
              for x in 0..image.width-1 loop
                RLE_Pixel_32;
                Output_Pixel;
              end loop;
            when 24 =>
              for x in 0..image.width-1 loop
                RLE_Pixel_24;
                Output_Pixel;
              end loop;
            when 16 =>
              for x in 0..image.width-1 loop
                RLE_Pixel_16;
                Output_Pixel;
              end loop;
            when 15 =>
              for x in 0..image.width-1 loop
                RLE_Pixel_15;
                Output_Pixel;
              end loop;
            when 8  =>
              for x in 0..image.width-1 loop
                RLE_Pixel_8;
                Output_Pixel;
              end loop;
            when others => null;
          end case;
        end if;
        Feedback(((y+1)*100)/image.height);
      end loop;
    elsif image.palette /= null then
      Get_with_palette;
    else
      case image.bits_per_pixel is
        when 32 =>
          Get_RGBA;
        when 24 =>
          Get_RGB;
        when 16 =>
          Get_16;
        when 15 =>
          Get_15;
        when 8  =>
          Get_Gray;
        when others => null;
      end case;
    end if;
  end Load;

end GID.Decoding_TGA;
