-- Steps for decoding a JPEG image
--
-- 1. Huffman decompression
-- 2. Inverse quantization
-- 3. Inverse cosine transform
-- 4. Upsampling
-- 5. Color transformation
-- 6. Image reconstruction
--
-- The JPEG decoder is largely inspired
-- by the NanoJPEG code by Martin J. Fiedler
--   http://keyj.s2000.ws/?p=137
-- With the author's permission. Many thanks!
--
-- Other informations:
-- http://en.wikipedia.org/wiki/JPEG

-- !! ** Some optimizations to consider **
-- !! ssx, ssy ,ssxmax, ssymax
--      as generic parameters + specialized instances
-- !! consider only power-of-two upsampling factors ?
-- !! simplify upsampling loops in case of power-of-two upsampling factors
--      using Shift_Right
-- !! Col_IDCT output direct to "flat", or something similar to NanoJPEG

with GID.Buffering;
with Ada.Text_IO, Ada.Exceptions, Ada.IO_Exceptions;

package body GID.Decoding_JPG is

  use GID.Buffering;
  use Ada.Text_IO, Ada.Exceptions;

  generic
    type Number is mod <>;
  procedure Big_endian_number(
    from : in out Input_buffer;
    n    :    out Number
  );
  pragma Inline(Big_endian_number);

  procedure Big_endian_number(
    from : in out Input_buffer;
    n    :    out Number
  )
  is
    b: U8;
  begin
    n:= 0;
    for i in 1..Number'Size/8 loop
      Get_Byte(from, b);
      n:= n * 256 + Number(b);
    end loop;
  end Big_endian_number;

  procedure Big_endian is new Big_endian_number( U16 );

  procedure Read( image: in out image_descriptor; sh: out Segment_head) is
    b: U8;
    id: constant array(JPEG_marker) of U8:=
    ( SOI      => 16#D8#,
      --
      SOF_0  => 16#C0#, SOF_1  => 16#C1#, SOF_2  => 16#C2#, SOF_3  => 16#C3#,
      SOF_5  => 16#C5#, SOF_6  => 16#C6#, SOF_7  => 16#C7#, SOF_8  => 16#C8#,
      SOF_9  => 16#C9#, SOF_10 => 16#CA#, SOF_11 => 16#CB#, SOF_13 => 16#CD#,
      SOF_14 => 16#CE#, SOF_15 => 16#CF#,
      --
      DHT      => 16#C4#,
      DAC      => 16#CC#,
      DQT      => 16#DB#,
      DRI      => 16#DD#,
      --
      APP_0  => 16#E0#, APP_1  => 16#E1#, APP_2  => 16#E2#, APP_3  => 16#E3#,
      APP_4  => 16#E4#, APP_5  => 16#E5#, APP_6  => 16#E6#, APP_7  => 16#E7#,
      APP_8  => 16#E8#, APP_9  => 16#E9#, APP_10 => 16#EA#, APP_11 => 16#EB#,
      APP_12 => 16#EC#, APP_13 => 16#ED#, APP_14 => 16#EE#,
      --
      COM      => 16#FE#,
      SOS      => 16#DA#,
      EOI      => 16#D9#
    );
  begin
    Get_Byte(image.buffer, b);
    if b /= 16#FF# then
      Raise_exception(
        error_in_image_data'Identity,
        "JPEG: expected marker here"
      );
    end if;
    Get_Byte(image.buffer, b);
    for m in id'Range loop
      if id(m)= b then
        sh.kind:= m;
        Big_endian(image.buffer, sh.length);
        sh.length:= sh.length - 2;
        -- We consider length of contents, without the FFxx marker.
        if some_trace then
          Put_Line(
            "Segment [" & JPEG_marker'Image(sh.kind) &
            "], length:" & U16'Image(sh.length));
        end if;
        return;
      end if;
    end loop;
    Raise_exception(
      error_in_image_data'Identity,
      "JPEG: unknown marker here: FF, " & U8'Image(b)
    );
  end Read;

  shift_arg: constant array(0..15) of Integer:=
    (1 => 0, 2 => 1, 4 => 2, 8 => 3, others => -1);

  -- SOF - Start Of Frame (the real header)
  procedure Read_SOF(image: in out Image_descriptor; sh: Segment_head) is
    use Bounded_255;
    b, bits_pp_primary: U8;
    w, h: U16;
    compo: JPEG_defs.Component;
  begin
    case sh.kind is
      when SOF_0 =>
        image.detailed_format:= To_Bounded_String("JPEG, Baseline DCT (SOF_0)");
      when SOF_2 =>
        image.detailed_format:= To_Bounded_String("JPEG, Progressive DCT (SOF_2)");
        image.interlaced:= True;
      when others =>
        Raise_exception(
          unsupported_image_subformat'Identity,
          "JPEG: image type not yet supported: " & JPEG_marker'Image(sh.kind)
        );
    end case;
    Get_Byte(image.buffer, bits_pp_primary);
    if bits_pp_primary /= 8 then
      Raise_exception(
        unsupported_image_subformat'Identity,
        "Bits per primary color=" & U8'Image(bits_pp_primary)
      );
    end if;
    image.bits_per_pixel:= 3 * Positive(bits_pp_primary);
    Big_endian(image.buffer, h);
    Big_endian(image.buffer, w);
    image.width:= Natural(w);
    image.height:= Natural(h);
    -- number of components:
    Get_Byte(image.buffer, b);
    image.subformat_id:= Integer(b);
    --
    image.JPEG_stuff.max_samples_hor:= 0;
    image.JPEG_stuff.max_samples_ver:= 0;
    -- for each component: 3 bytes
    for i in 1..image.subformat_id loop
      -- component id (1 = Y, 2 = Cb, 3 = Cr, 4 = I, 5 = Q)
      Get_Byte(image.buffer, b);
      compo:= JPEG_defs.Component'Val(b - 1);
      image.JPEG_stuff.components(compo):= True;
      declare
        stuff: JPEG_stuff_type renames image.JPEG_stuff;
        info: JPEG_defs.info_per_component_A renames stuff.info(compo);
      begin
        -- sampling factors (bit 0-3 vert., 4-7 hor.)
        Get_Byte(image.buffer, b);
        info.samples_ver:= Natural(b mod 16);
        info.samples_hor:= Natural(b  /  16);
        stuff.max_samples_hor:=
          Integer'Max(stuff.max_samples_hor, info.samples_hor);
        stuff.max_samples_ver:=
          Integer'Max(stuff.max_samples_ver, info.samples_ver);
        -- quantization table number
        Get_Byte(image.buffer, b);
        info.qt_assoc:= Natural(b);
      end;
    end loop;
    for c in Component loop
      if image.JPEG_stuff.components(c) then
        declare
          stuff: JPEG_stuff_type renames image.JPEG_stuff;
          info: JPEG_defs.info_per_component_A renames stuff.info(c);
        begin
          info.up_factor_x:= stuff.max_samples_hor / info.samples_hor;
          info.up_factor_y:= stuff.max_samples_ver / info.samples_ver;
          info.shift_x:= shift_arg(info.up_factor_x);
          info.shift_y:= shift_arg(info.up_factor_y);
        end;
      end if;
    end loop;
    if Natural(sh.length) < 6 + 3 * image.subformat_id then
      Raise_exception(
        error_in_image_data'Identity,
        "JPEG: SOF segment too short"
      );
    end if;
    if some_trace then
      Put_Line("Frame has following components:");
      for c in JPEG_defs.component loop
        Put_Line(
          JPEG_defs.Component'Image(c) & " -> " &
          Boolean'Image(image.JPEG_stuff.components(c))
        );
      end loop;
    end if;
    if image.JPEG_stuff.components = YCbCr_set then
      image.JPEG_stuff.color_space:= YCbCr;
    elsif image.JPEG_stuff.components = Y_Grey_set then
      image.JPEG_stuff.color_space:= Y_Grey;
      image.greyscale:= True;
    elsif image.JPEG_stuff.components = CMYK_set then
      image.JPEG_stuff.color_space:= CMYK;
    else
      Raise_exception(
        unsupported_image_subformat'Identity,
        "JPEG: only YCbCr, Y_Grey and CMYK color spaces are currently supported"
      );
    end if;
    image.detailed_format:= image.detailed_format & ", " &
      JPEG_defs.Supported_color_space'Image(image.JPEG_stuff.color_space);
    if some_trace then
      Put_Line(
        "Color space: " &
        JPEG_defs.Supported_color_space'Image(image.JPEG_stuff.color_space)
      );
    end if;
    if image.JPEG_stuff.color_space = CMYK then
      Raise_exception(
        unsupported_image_subformat'Identity,
        "JPEG: CMYK color space is currently not properly decoded"
      );
    end if;
  end Read_SOF;

  procedure Read_DHT(image: in out Image_descriptor; data_length: Natural) is
    remaining: Integer:= data_length; -- data remaining in segment
    b: U8;
    ht_idx: Natural;
    kind: AC_DC;
    counts: array(1..16) of Natural;
    spread, currcnt, idx: Natural;
    remain_vlc: Integer;
  begin
    multi_tables:
    loop
      Get_Byte(image.buffer, b);
      remaining:= remaining - 1;
      if b >= 8 then
        kind:= AC;
      else
        kind:= DC;
      end if;
      ht_idx:= Natural(b and 7);
      if some_trace then
        Put_Line(
          "Huffman Table (HT) #" &
          Natural'Image(ht_idx) & ", " & AC_DC'Image(kind)
        );
      end if;
      if image.JPEG_stuff.vlc_defs(kind, ht_idx) = null then
        image.JPEG_stuff.vlc_defs(kind, ht_idx):= new VLC_table;
      end if;
      for i in counts'range loop
        Get_Byte(image.buffer, b);
        remaining:= remaining - 1;
        counts(i):= Natural(b);
      end loop;
      remain_vlc:= 65_536;
      spread:= 65_536;
      idx:= 0;
      for codelen in counts'Range loop
        spread:= spread / 2;
        currcnt:= counts(codelen);
        if currcnt > 0 then
          if remaining < currcnt then
            Raise_exception(
              error_in_image_data'Identity,
              "JPEG: DHT data too short"
            );
          end if;
          remain_vlc:= remain_vlc - currcnt * spread;
          if remain_vlc < 0 then
            Raise_exception(
              error_in_image_data'Identity,
              "JPEG: DHT table too short for data"
            );
          end if;
          for i in reverse 1..currcnt loop
            Get_Byte(image.buffer, b);
            for j in reverse 1..spread loop
              image.JPEG_stuff.vlc_defs(kind, ht_idx)(idx):=
                (bits => U8(codelen), code => b);
              idx:= idx + 1;
            end loop;
          end loop;
          remaining:= remaining - currcnt;
        end if;
      end loop;
      while remain_vlc > 0 loop
        remain_vlc:= remain_vlc - 1;
        image.JPEG_stuff.vlc_defs(kind, ht_idx)(idx).bits:= 0;
        idx:= idx + 1;
      end loop;
      exit when remaining <= 0;
    end loop multi_tables;
  end Read_DHT;

  procedure Read_DQT(image: in out Image_descriptor; data_length: Natural) is
    remaining: Integer:= data_length; -- data remaining in segment
    b, q8: U8; q16: U16;
    qt_idx: Natural;
    high_prec: Boolean;
  begin
    multi_tables:
    loop
      Get_Byte(image.buffer, b);
      remaining:= remaining - 1;
      high_prec:= b >= 8;
      qt_idx:= Natural(b and 7);
      if some_trace then
        Put_Line("Quantization Table (QT) #" & U8'Image(b));
      end if;
      for i in QT'Range loop
        if high_prec then
          Big_endian(image.buffer, q16);
          remaining:= remaining - 2;
          image.JPEG_stuff.qt_list(qt_idx)(i):= Natural(q16);
        else
          Get_Byte(image.buffer, q8);
          remaining:= remaining - 1;
          image.JPEG_stuff.qt_list(qt_idx)(i):= Natural(q8);
        end if;
      end loop;
      exit when remaining <= 0;
    end loop multi_tables;
  end Read_DQT;

  procedure Read_DRI(image: in out Image_descriptor) is
    ri: U16;
  begin
    Big_endian(image.buffer, ri);
    if some_trace then
      Put_Line("  Restart interval set to:" & U16'Image(ri));
    end if;
    image.JPEG_stuff.restart_interval:= Natural(ri);
  end Read_DRI;

  procedure Read_EXIF(image: in out Image_descriptor; data_length: Natural) is
    b, orientation_value: U8;
    x: Natural;
    Exif_signature: constant String:= "Exif" & ASCII.NUL & ASCII.NUL;
    signature: String(1..6);
    IFD_tag: U16;
  begin
    if some_trace then
      Put_Line("APP1");
    end if;
    if data_length < 6 then
      -- Skip segment data
      for i in 1..data_length loop
        Get_Byte(image.buffer, b);
      end loop;
    else
      for i in 1..6 loop
        Get_Byte(image.buffer, b);
        signature(i):= Character'Val(b);
      end loop;
      if signature /= Exif_signature then
        for i in 7..data_length loop
          Get_Byte(image.buffer, b);
        end loop;
        if some_trace then
          Put_Line("APP1 is not Exif");
        end if;
        return;
      end if;
      if some_trace then
        Put_Line("APP1 is Exif");
      end if;
      for i in 7..16 loop -- TIFF 6.0 header + IFD0 entries
        Get_Byte(image.buffer, b);
      end loop;
      x:= 17;
      while x <= data_length - 12 loop
        Get_Byte(image.buffer, b);
        IFD_tag:= U16(b);
        Get_Byte(image.buffer, b);
        IFD_tag:= IFD_tag + 16#100# * U16(b);
        for i in 3..8 loop
          Get_Byte(image.buffer, b);
        end loop;
        Get_Byte(image.buffer, orientation_value);
        for i in 10..12 loop
          Get_Byte(image.buffer, b);
        end loop;
        x:= x + 12;
        if IFD_tag = 16#112# then
          case orientation_value is
            when 1 =>
              image.display_orientation:= Unchanged;
            when 8 =>
              image.display_orientation:= Rotation_90;
            when 3 =>
              image.display_orientation:= Rotation_180;
            when 6 =>
              image.display_orientation:= Rotation_270;
            when others =>
              image.display_orientation:= Unchanged;
          end case;
          if some_trace then
            Put_Line(
              "IFD tag: Orientation set to: " &
              Orientation'Image(image.display_orientation)
            );
          end if;
          exit;
        end if;
      end loop;
      -- Skip rest of data
      for i in x..data_length loop
        Get_Byte(image.buffer, b);
      end loop;
    end if;
  end Read_EXIF;

  --------------------
  -- Image decoding --
  --------------------

  procedure Load (
    image     : in out Image_descriptor;
    next_frame:    out Ada.Calendar.Day_Duration
  )
  is
    --
    -- Bit buffer
    --
    buf: U32:= 0;
    bufbits: Natural:= 0;

    function Show_bits(bits: Natural) return Natural is
      newbyte, marker: U8;
    begin
      if bits=0 then
        return 0;
      end if;
      while bufbits < bits loop
        begin
          Get_Byte(image.buffer, newbyte);
          bufbits:= bufbits + 8;
          buf:= buf * 256 + U32(newbyte);
          if newbyte = 16#FF# then
            Get_Byte(image.buffer, marker);
            case marker is
              when 0 =>
                null;
              when 16#D9# =>
                null; -- !! signal end
              when 16#D0# .. 16#D7# =>
                bufbits:= bufbits + 8;
                buf:= buf * 256 + U32(marker);
              when others =>
                Raise_exception(
                  error_in_image_data'Identity,
                  "JPEG: Invalid code (bit buffer)"
                );
            end case;
          end if;
        exception
          when Ada.IO_Exceptions.End_Error =>
            newbyte:= 16#FF#;
            bufbits:= bufbits + 8;
            buf:= buf * 256 + U32(newbyte);
        end;
      end loop;
      return Natural(
          Shift_Right(buf, bufbits - bits)
        and
          (Shift_Left(1, bits)-1)
      );
    end Show_bits;

    procedure Skip_bits(bits: Natural) is
    pragma Inline(Skip_bits);
      dummy: Integer;
      pragma Warnings(off, dummy);
    begin
      if bufbits < bits then
        dummy:= Show_bits(bits);
      end if;
      bufbits:= bufbits - bits;
    end Skip_bits;

    function Get_bits(bits: Natural) return Integer is
    pragma Inline(Get_bits);
      res: constant Integer:= Show_bits(bits);
    begin
      Skip_bits(bits);
      return res;
    end Get_bits;

    --

    type Info_per_component_B is record
      ht_idx_AC : Natural;
      ht_idx_DC : Natural;
      width, height, stride: Natural;
      dcpred: Integer:= 0;
    end record;

    info_A: Component_info_A renames image.JPEG_stuff.info;
    info_B: array(Component) of Info_per_component_B;

    procedure Get_VLC(
      vlc: VLC_table;
      code: out U8;
      value_ret: out Integer
    )
    is
      -- Step 1 happens here: Huffman decompression
      value: Integer:= Show_bits(16);
      bits : Natural:= Natural(vlc(value).bits);
    begin
      if bits = 0 then
        Raise_exception(
          error_in_image_data'Identity,
          "JPEG: VLC table: bits = 0"
        );
      end if;
      Skip_bits(bits);
      value:= Integer(vlc(value).code);
      code:= U8(value);
      bits:= Natural(U32(value) and 15);
      value_ret:= 0;
      if bits /= 0 then
        value:= Get_bits(bits);
        if value < Integer(Shift_Left(U32'(1), bits - 1)) then
          value:= value + 1 - Integer(Shift_Left(U32'(1), bits));
        end if;
        value_ret:= value;
      end if;
    end Get_VLC;

    function Clip(x: Integer) return Integer is
    pragma Inline(Clip);
    begin
      if x < 0 then
        return 0;
      elsif x > 255 then
        return 255;
      else
        return x;
      end if;
    end Clip;

    type Block_8x8 is array(0..63) of Integer;

    -- Ordering within a 8x8 block, in zig-zag
    zig_zag: constant Block_8x8:=
     ( 0,  1,  8, 16,  9,  2,  3, 10, 17, 24, 32, 25, 18,
      11,  4,  5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
      13,  6,  7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43,
      36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45,
      38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63 );

    procedure Decode_Block(c: Component; block: in out Block_8x8) is
      value, coef: Integer;
      code: U8;
      qt: JPEG_defs.QT renames image.JPEG_stuff.qt_list(info_A(c).qt_assoc);
      --
      W1: constant:= 2841;
      W2: constant:= 2676;
      W3: constant:= 2408;
      W5: constant:= 1609;
      W6: constant:= 1108;
      W7: constant:=  565;
      --
      procedure Row_IDCT(start: Integer) is
      pragma Inline(Row_IDCT);
        x0, x1, x2, x3, x4, x5, x6, x7, x8, val: Integer;
      begin
        x1:= block(start + 4) * 2**11;
        x2:= block(start + 6);
        x3:= block(start + 2);
        x4:= block(start + 1);
        x5:= block(start + 7);
        x6:= block(start + 5);
        x7:= block(start + 3);
        if x1=0 and x2=0 and x3=0 and x4=0 and x5=0 and x6=0 and x7=0 then
          val:= block(start + 0) * 8;
          block(start + 0 .. start + 7):= (others => val);
        else
          x0:= (block(start + 0) * 2**11) + 128;
          x8:= W7 * (x4 + x5);
          x4:= x8 + (W1 - W7) * x4;
          x5:= x8 - (W1 + W7) * x5;
          x8:= W3 * (x6 + x7);
          x6:= x8 - (W3 - W5) * x6;
          x7:= x8 - (W3 + W5) * x7;
          x8:= x0 + x1;
          x0:= x0 - x1;
          x1:= W6 * (x3 + x2);
          x2:= x1 - (W2 + W6) * x2;
          x3:= x1 + (W2 - W6) * x3;
          x1:= x4 + x6;
          x4:= x4 - x6;
          x6:= x5 + x7;
          x5:= x5 - x7;
          x7:= x8 + x3;
          x8:= x8 - x3;
          x3:= x0 + x2;
          x0:= x0 - x2;
          x2:= (181 * (x4 + x5) + 128) / 256;
          x4:= (181 * (x4 - x5) + 128) / 256;
          block(start + 0):= (x7 + x1) / 256;
          block(start + 1):= (x3 + x2) / 256;
          block(start + 2):= (x0 + x4) / 256;
          block(start + 3):= (x8 + x6) / 256;
          block(start + 4):= (x8 - x6) / 256;
          block(start + 5):= (x0 - x4) / 256;
          block(start + 6):= (x3 - x2) / 256;
          block(start + 7):= (x7 - x1) / 256;
        end if;
      end Row_IDCT;

      procedure Col_IDCT(start: Integer) is
      pragma Inline(Col_IDCT);
        x0, x1, x2, x3, x4, x5, x6, x7, x8, val: Integer;
      begin
        x1:= block(start + 8*4) * 256;
        x2:= block(start + 8*6);
        x3:= block(start + 8*2);
        x4:= block(start + 8*1);
        x5:= block(start + 8*7);
        x6:= block(start + 8*5);
        x7:= block(start + 8*3);
        if x1=0 and x2=0 and x3=0 and x4=0 and x5=0 and x6=0 and x7=0 then
          val:= Clip(((block(start) + 32) / 2**6) + 128);
          for row in reverse 0..7 loop
            block(start + row * 8):= val;
          end loop;
        else
          x0:= (block(start) * 256) + 8192;
          x8:= W7 * (x4 + x5) + 4;
          x4:= (x8 + (W1 - W7) * x4) / 8;
          x5:= (x8 - (W1 + W7) * x5) / 8;
          x8:= W3 * (x6 + x7) + 4;
          x6:= (x8 - (W3 - W5) * x6) / 8;
          x7:= (x8 - (W3 + W5) * x7) / 8;
          x8:= x0 + x1;
          x0:= x0 - x1;
          x1:= W6 * (x3 + x2) + 4;
          x2:= (x1 - (W2 + W6) * x2) / 8;
          x3:= (x1 + (W2 - W6) * x3) / 8;
          x1:= x4 + x6;
          x4:= x4 - x6;
          x6:= x5 + x7;
          x5:= x5 - x7;
          x7:= x8 + x3;
          x8:= x8 - x3;
          x3:= x0 + x2;
          x0:= x0 - x2;
          x2:= (181 * (x4 + x5) + 128) / 256;
          x4:= (181 * (x4 - x5) + 128) / 256;
          block(start + 8*0):= Clip(((x7 + x1) / 2**14) + 128);
          block(start + 8*1):= Clip(((x3 + x2) / 2**14) + 128);
          block(start + 8*2):= Clip(((x0 + x4) / 2**14) + 128);
          block(start + 8*3):= Clip(((x8 + x6) / 2**14) + 128);
          block(start + 8*4):= Clip(((x8 - x6) / 2**14) + 128);
          block(start + 8*5):= Clip(((x0 - x4) / 2**14) + 128);
          block(start + 8*6):= Clip(((x3 - x2) / 2**14) + 128);
          block(start + 8*7):= Clip(((x7 - x1) / 2**14) + 128);
        end if;
      end Col_IDCT;

    begin -- Decode_Block
      --
      -- Step 2 happens here: Inverse quantization
      Get_VLC(image.JPEG_stuff.vlc_defs(DC, info_B(c).ht_idx_DC).all, code, value);
      -- First value in block (0: top left) uses a predictor.
      info_B(c).dcpred:= info_B(c).dcpred + value;
      block:= (0 => info_B(c).dcpred * qt(0), others => 0);
      coef:= 0;
      loop
        Get_VLC(image.JPEG_stuff.vlc_defs(AC, info_B(c).ht_idx_AC).all, code, value);
        exit when code = 0; -- EOB
        if (code and 16#0F#) = 0 and code /= 16#F0# then
          Raise_exception(
            error_in_image_data'Identity,
            "JPEG: error in VLC AC code for de-quantization"
          );
        end if;
        coef:= coef + Integer(Shift_Right(code, 4)) + 1;
        if coef > 63 then
          Raise_exception(
            error_in_image_data'Identity,
            "JPEG: coefficient for de-quantization is > 63"
          );
        end if;
        block(zig_zag(coef)):= value * qt(coef);
        exit when coef = 63;
      end loop;
      -- Step 3 happens here: Inverse cosine transform
      for row in 0..7 loop
        Row_IDCT(row * 8);
      end loop;
      for col in 0..7 loop
        Col_IDCT(col);
      end loop;
    end Decode_Block;

    type Macro_block is array(
      Component range <>, -- component
      Positive range <>,  -- x sample range
      Positive range <>   -- y sample range
    ) of Block_8x8;

      procedure Out_Pixel_8(br, bg, bb: U8) is
      pragma Inline(Out_Pixel_8);
        ba: constant:= 255;
      begin
        case Primary_color_range'Modulus is
          when 256 =>
            Put_Pixel(
              Primary_color_range(br),
              Primary_color_range(bg),
              Primary_color_range(bb),
              Primary_color_range(ba)
            );
          when 65_536 =>
            Put_Pixel(
              16#101# * Primary_color_range(br),
              16#101# * Primary_color_range(bg),
              16#101# * Primary_color_range(bb),
              16#101# * Primary_color_range(ba)
              -- 16#101# because max intensity FF goes to FFFF
            );
          when others =>
            raise invalid_primary_color_range;
        end case;
      end Out_Pixel_8;

    -- !! might be generic parameters
    ssxmax: constant Natural:= image.JPEG_stuff.max_samples_hor;
    ssymax: constant Natural:= image.JPEG_stuff.max_samples_ver;

    procedure Upsampling_and_output(
      m: Macro_block;
      x0, y0: Natural
    )
    is
      flat: array(Component, 0..8*ssxmax-1, 0..8*ssymax-1) of Integer;

      generic
        color_space: Supported_color_space;
      procedure Color_transformation_and_output;
      --
      procedure Color_transformation_and_output is
        y_val, cb_val, cr_val, c_val, m_val, w_val: Integer;
        y_val_8: U8;
      begin
        for ymb in flat'Range(3) loop
          exit when y0+ymb >= image.height;
          Set_X_Y(x0, image.height-1-(y0+ymb));
          for xmb in flat'Range(2) loop
            exit when x0+xmb >= image.width;
            case color_space is
              when YCbCR =>
                y_val := flat(Y,  xmb, ymb) * 256;
                cb_val:= flat(Cb, xmb, ymb) - 128;
                cr_val:= flat(Cr, xmb, ymb) - 128;
                Out_pixel_8(
                  br => U8(Clip((y_val                + 359 * cr_val + 128) / 256)),
                  bg => U8(Clip((y_val -  88 * cb_val - 183 * cr_val + 128) / 256)),
                  bb => U8(Clip((y_val + 454 * cb_val                + 128) / 256))
                );
              when Y_Grey =>
                y_val_8:= U8(flat(Y,  xmb, ymb));
                Out_pixel_8(y_val_8, y_val_8, y_val_8);
              when CMYK =>
                -- !! find a working conversion formula.
                --    perhaps it is more complicated (APP_2
                --    color profile must be used ?)
                c_val:= flat(Y,  xmb, ymb);
                m_val:= flat(Cb, xmb, ymb);
                y_val:= flat(Cr, xmb, ymb);
                w_val:= flat(I,  xmb, ymb)-255;
                Out_pixel_8(
                  br => U8(255-Clip(c_val+w_val)),
                  bg => U8(255-Clip(m_val+w_val)),
                  bb => U8(255-Clip(y_val+w_val))
                );
            end case;
          end loop;
        end loop;
      end Color_transformation_and_output;
      --
      procedure Ct_YCbCr  is new Color_transformation_and_output(YCbCr);
      procedure Ct_Y_Grey is new Color_transformation_and_output(Y_Grey);
      procedure Ct_CMYK   is new Color_transformation_and_output(CMYK);

      blk_idx: Integer;
      upsx, upsy: Natural;
    begin
      -- Step 4 happens here: Upsampling
      for c in Component loop
        if image.JPEG_stuff.components(c) then
          upsx:= info_A(c).up_factor_x;
          upsy:= info_A(c).up_factor_y;
          for x in reverse 1..info_A(c).samples_hor loop
            for y in reverse 1..info_A(c).samples_ver loop
              -- We are at the 8x8 block level
              blk_idx:= 63;
              for y8 in reverse 0..7 loop
                for x8 in reverse 0..7 loop
                  declare
                    val: constant Integer:= m(c,x,y)(blk_idx);
                    big_pixel_x: constant Natural:= upsx * (x8 + 8*(x-1));
                    big_pixel_y: constant Natural:= upsy * (y8 + 8*(y-1));
                  begin
                    -- Repeat pixels for component c, sample (x,y),
                    -- position (x8,y8).
                    for rx in reverse 0..upsx-1 loop
                      for ry in reverse 0..upsy-1 loop
                        flat(c, rx + big_pixel_x, ry + big_pixel_y):= val;
                      end loop;
                    end loop;
                  end;
                  blk_idx:= blk_idx - 1;
                end loop;
              end loop;
            end loop;
          end loop;
        end if;
      end loop;
      -- Step 5 and 6 happen here: Color transformation and output
      case image.JPEG_stuff.color_space is
        when YCbCr =>
          Ct_YCbCr;
        when Y_Grey =>
          Ct_Y_Grey;
        when CMYK =>
          Ct_CMYK;
      end case;
    end Upsampling_and_output;

    -- Start Of Scan (and image data which follow)
    --
    procedure Read_SOS is
      components, b: U8;
      compo: Component;
      mbx, mby: Natural:= 0;
      mbsizex, mbsizey, mbwidth, mbheight: Natural;
      rstcount: Natural:= image.JPEG_stuff.restart_interval;
      nextrst: U16:= 0;
      w: U16;
      start_spectral_selection,
      end_spectral_selection,
      successive_approximation: U8;
    begin
      Get_Byte(image.buffer, components);
      if some_trace then
        Put_Line(
          "Start of Scan (SOS), with" & U8'Image(components) & " components"
        );
      end if;
      if image.subformat_id /= Natural(components) then
        Raise_exception(
          error_in_image_data'Identity,
          "JPEG: components mismatch in Scan segment"
        );
      end if;
      for i in 1..components loop
        Get_Byte(image.buffer, b);
        compo:= Component'Val(b - 1);
        if not image.JPEG_stuff.components(compo) then
          Raise_exception(
            error_in_image_data'Identity,
            "JPEG: component " & Component'Image(compo) &
            " has not been defined in the header (SOF) segment"
          );
        end if;
        -- Huffman table selection
        Get_Byte(image.buffer, b);
        info_B(compo).ht_idx_AC:= Natural(b mod 16);
        info_B(compo).ht_idx_DC:= Natural(b  /  16);
      end loop;
      -- Parameters for progressive display format (SOF_2)
      Get_Byte(image.buffer, start_spectral_selection);
      Get_Byte(image.buffer, end_spectral_selection);
      Get_Byte(image.buffer, successive_approximation);
      --
      -- End of SOS segment, image data follow.
      --
      mbsizex:= ssxmax * 8; -- pixels in a row of a macro-block
      mbsizey:= ssymax * 8; -- pixels in a column of a macro-block
      mbwidth := (image.width + mbsizex - 1) / mbsizex;
      -- width in macro-blocks
      mbheight:= (image.height + mbsizey - 1) / mbsizey;
      -- height in macro-blocks
      if some_trace then
        Put_Line(" mbsizex = " & Integer'Image(mbsizex));
        Put_Line(" mbsizey = " & Integer'Image(mbsizey));
        Put_Line(" mbwidth  = " & Integer'Image(mbwidth));
        Put_Line(" mbheight = " & Integer'Image(mbheight));
      end if;
      for c in Component loop
        if image.JPEG_stuff.components(c) then
          info_B(c).width := (image.width  * info_A(c).samples_hor + ssxmax - 1) / ssxmax;
          info_B(c).height:= (image.height * info_A(c).samples_ver + ssymax - 1) / ssymax;
          info_B(c).stride:= (mbwidth * mbsizex * info_A(c).samples_hor) / ssxmax;
          if some_trace then
            Put_Line("  Details for component " & Component'Image(c));
            Put_Line("    samples in x " & Integer'Image(info_A(c).samples_hor));
            Put_Line("    samples in y " & Integer'Image(info_A(c).samples_ver));
            Put_Line("    width " & Integer'Image(info_B(c).width));
            Put_Line("    height " & Integer'Image(info_B(c).height));
            Put_Line("    stride " & Integer'Image(info_B(c).stride));
            Put_Line(
              "    AC/DC table index " &
              Integer'Image(info_B(compo).ht_idx_AC) & ", " &
              Integer'Image(info_B(compo).ht_idx_DC)
            );
          end if;
          if (info_B(c).width < 3 and info_A(c).samples_hor /= ssxmax) or
             (info_B(c).height < 3 and info_A(c).samples_ver /= ssymax)
          then
            Raise_exception(
              error_in_image_data'Identity,
              "JPEG: component " & Component'Image(c) &
              ": sample dimension mismatch"
            );
          end if;
        end if;
      end loop;
      --
      if image.interlaced then
        Raise_exception(
          unsupported_image_subformat'Identity,
          "JPEG: progressive format not yet functional"
        );
      end if;
      declare
        mb: Macro_block(Component, 1..ssxmax, 1..ssymax);
        x0, y0: Integer:= 0;
      begin
        macro_blocks_loop:
        loop
          components_loop:
          for c in Component loop
            if image.JPEG_stuff.components(c) then
              samples_y_loop:
              for sby in 1..info_A(c).samples_ver loop
                samples_x_loop:
                for sbx in 1..info_A(c).samples_hor loop
                  Decode_Block(c, mb(c, sbx, sby));
                end loop samples_x_loop;
              end loop samples_y_loop;
            end if;
          end loop components_loop;
          -- All components of the current macro-block are decoded.
          -- Step 4, 5, 6 happen here: Upsampling, color transformation, output
          Upsampling_and_output(mb, x0, y0);
          --
          mbx:= mbx + 1;
          x0:= x0 + ssxmax * 8;
          if mbx >= mbwidth then
            mbx:= 0;
            x0:= 0;
            mby:= mby + 1;
            y0:= y0 + ssymax * 8;
            Feedback((100*mby)/mbheight);
            exit macro_blocks_loop when mby >= mbheight;
          end if;
          if image.JPEG_stuff.restart_interval > 0 then
            rstcount:= rstcount - 1;
            if rstcount = 0 then
              -- Here begins the restart.
              bufbits:= Natural(U32(bufbits) and 16#F8#); -- byte alignment
              -- Now the restart marker. We expect a
              w:= U16(Get_bits(16));
              if some_trace then
                Put_Line(
                  "  Restart #" & U16'Image(nextrst) &
                  "  Code " & U16'Image(w) &
                  " after" & Natural'Image(image.JPEG_stuff.restart_interval) &
                  " macro blocks"
                );
              end if;
              if w not in 16#FFD0# .. 16#FFD7# or (w and 7) /= nextrst then
                Raise_exception(
                  error_in_image_data'Identity,
                  "JPEG: expected RST (restart) marker Nb " & U16'Image(nextrst)
                );
              end if;
              nextrst:= (nextrst + 1) and 7;
              rstcount:= image.JPEG_stuff.restart_interval;
              -- Block-to-block predictor variables are reset.
              for c in Component loop
                info_B(c).dcpred:= 0;
              end loop;
            end if;
          end if;
        end loop macro_blocks_loop;
      end;
    end Read_SOS;

    --
    sh: Segment_head;
    b: U8;
  begin -- Load
    loop
      Read(image, sh);
      case sh.kind is
        when DQT => -- Quantization Table
          Read_DQT(image, Natural(sh.length));
        when DHT => -- Huffman Table
          Read_DHT(image, Natural(sh.length));
        when DRI => -- Restart Interval
          Read_DRI(image);
        when EOI => -- End Of Input
          exit;
        when SOS => -- Start Of Scan
          Read_SOS;
          exit;
        when others =>
          -- Skip segment data
          for i in 1..sh.length loop
            Get_Byte(image.buffer, b);
          end loop;
      end case;
    end loop;
    next_frame:= 0.0; -- still picture
  end Load;

end GID.Decoding_JPG;
