/* -*- C++ -*-
 * File: huffmandec.h
 * Copyright (C) 2024 Alex Tutubalin, LibRaw LLC
 *
   Lossless JPEG decoder

   Code partially backported from DNGLab's rust code
   DNGLab was originally created in 2021 by Daniel Vogelbacher.

LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:

1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
   (See file LICENSE.LGPL provided in LibRaw distribution archive for details).

2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
   (See file LICENSE.CDDL provided in LibRaw distribution archive for details).

 */
#pragma  once
#include <stdint.h>
#include <vector>

struct BitPump // generic bit source
{
  virtual uint32_t peek(uint32_t num) = 0;
  virtual void consume(uint32_t num) = 0;

  uint32_t get(uint32_t num)
  {
    if(num == 0) { return 0u; }
    uint32_t val = peek(num);
    consume(num);
	return val;
  }
};

struct ByteStreamBE // Jpeg is always big endian
{
  enum Exceptions
  {
    OK = 0,
    EndOfBuffer = 1
  };

  uint8_t *buffer;
  unsigned size, pos;
  ByteStreamBE(uint8_t *b, unsigned s) : buffer(b), size(s), pos(0) {}
  ByteStreamBE() : buffer(0),size(0),pos(0){}
  bool skip_to_marker();

  uint8_t get_u8()
  {
    if (pos >= size)
      throw EndOfBuffer;
    uint8_t ret = buffer[pos];
    pos++;
    return ret;
  }
  uint16_t get_u16()
  {
    if (pos + 2 > size)
      throw EndOfBuffer;
    uint8_t r1 = buffer[pos];
    uint8_t r2 = buffer[pos + 1];
    pos += 2;
    return (r1 << 8) | r2;
  }
};

struct BitPumpJpeg : public BitPump
{
	uint8_t *buffer;
	unsigned size, pos;
	uint64_t bits;
	uint32_t nbits;
	bool finished;

	void consume(uint32_t num)
	{
		if (num <= nbits)
		{
			nbits -= num;
			bits &= (uint64_t(1) << nbits) - 1UL;
		}
	}
    uint32_t peek(uint32_t num)
    {
		if (num > nbits && !finished)
		{
			if ((size >= 4) && pos < size && buffer[pos] != 0xff && buffer[pos + 1] != 0xff && buffer[pos + 2] != 0xff &&
				buffer[pos + 3] != 0xff)
			{
				uint64_t inbits = (uint32_t(buffer[pos]) << 24) | (uint32_t(buffer[pos + 1]) << 16) |
					(uint32_t(buffer[pos + 2]) << 8) | (uint32_t(buffer[pos + 3]));
				bits = (bits << 32) | inbits;
				pos += 4;
				nbits += 32;
			}
			else
			{
				int read_bytes = 0;
				while (read_bytes < 4 && !finished)
				{
					uint8_t byte = 0;
					if (pos >= size)
						finished = true;
					else
					{
						uint8_t nextbyte = buffer[pos];
						if (nextbyte != 0xff)
							byte = nextbyte;
						else if (buffer[pos + 1] == 0x00)
						{
							pos += 1;
							byte = nextbyte;
						}
						else
							finished = true;
					};
					bits = (bits << 8) | uint64_t(byte);
					pos += 1;
					nbits += 8;
					read_bytes += 1;
				}
			}
		}
	  if(num > nbits && finished)
        {
          bits <<= 32;
          nbits += 32;
        }
	  return uint32_t(bits >> (nbits - num));
    }

	BitPumpJpeg(ByteStreamBE& s): buffer(s.buffer+s.pos),size(s.size-s.pos),pos(0),bits(0),nbits(0),finished(false){}
};


const uint32_t LIBRAW_DECODE_CACHE_BITS = 13;
const uint64_t LIBRAW_CACHE_PRESENT_FLAG = 0x100000000L;

struct HuffTable
{
	uint32_t bits[17];
	uint32_t huffval[256];
	uint32_t shiftval[256];
	bool dng_bug;
	bool disable_cache;
	uint32_t nbits;
    std::vector<uint32_t> hufftable; // len:8 << 16| huffval:8  << 8 | shift:8
	std::vector<uint64_t> decodecache;
	bool initialized;
	HuffTable();
	void initval(uint32_t bits[17], uint32_t huffval[256], bool dng_bug);

	int32_t decode(BitPump& pump)
    {
      uint64_t cached = disable_cache ? 0 : decodecache[pump.peek(LIBRAW_DECODE_CACHE_BITS)];
      if (cached & LIBRAW_CACHE_PRESENT_FLAG)
      {
        uint32_t _bits = (cached >> 16) & 0xff;
        int16_t val = int16_t(cached & 0xffff);
        if (val == -32768 && dng_bug)
        {
          if (_bits > 16)
            pump.consume(_bits - 16);
        }
        else
          pump.consume(_bits);
        return val;
      }
      else
        return decode_slow1(pump);
    }

	int32_t decode_slow1(BitPump &pump)
    {
      int32_t _diff = diff(pump, len(pump));
      return _diff;
    }

    int32_t decode_slow2(BitPump & pump, uint32_t& outlen) // output:  (len+shift):8, code:16
    {
      uint32_t _len = len(pump);
      int32_t _diff = diff(pump, _len);
      uint8_t bits8 = (_len >> 16) & 0xff;
      uint8_t len8 = (_len >> 8) & 0xff;
      outlen = bits8 + len8;
      return _diff;
    }

    uint32_t len(BitPump & pump) //bits:8, len:8, shift:8
    {
      uint32_t code = pump.peek(nbits);
      uint32_t huffdata = hufftable[code];
      uint32_t _bits = (huffdata >> 16) & 0xff;
      pump.consume(_bits);
      return huffdata;
    }

    int32_t diff(BitPump & pump, uint32_t hentry) // input: bits:8, len:8, shift:8; output:  diff:i32
    {
      uint32_t len = (hentry >> 8) & 0xff;
      if (len == 0)
        return 0;
      if (len == 16)
      {
        if (dng_bug)
          pump.get(16);
        return -32768;
      }
      uint32_t shift = hentry & 0xff;
	  uint32_t fulllen = len + shift;
      uint32_t _bits = pump.get(len);
      int32_t diff = ((_bits << 1) + 1) << shift >> 1;
      if ((diff & (1 << (fulllen - 1))) == 0)
      {
        diff -= int32_t((1 << fulllen) - ((shift == 0)));
      }
      return diff;
    }
};

struct LibRaw_JpegComponentInfo
{
	unsigned id;
	unsigned index;
	unsigned dc_tbl;
	unsigned subsample_h, subsample_v;
	LibRaw_JpegComponentInfo() : id(0), index(0), dc_tbl(0), subsample_h(0), subsample_v(0) {};
	LibRaw_JpegComponentInfo(unsigned _id, unsigned _index, unsigned _dcn, unsigned _sh, unsigned _sv)
		: id(_id), index(_index), dc_tbl(_dcn), subsample_h(_sh), subsample_v(_sv){};
	LibRaw_JpegComponentInfo(const LibRaw_JpegComponentInfo& q): id(q.id), index(q.index), dc_tbl(q.dc_tbl),
			subsample_h(q.subsample_h),subsample_v(q.subsample_v){}
};

struct LibRaw_SOFInfo
{
	unsigned width, height;
	unsigned cps, precision;
	std::vector<LibRaw_JpegComponentInfo> components;
	bool csfix;
	LibRaw_SOFInfo(): width(0),height(0),cps(0),precision(0),csfix(false){}
	bool parse_sof(ByteStreamBE&); // true => OK, false => not OK
	uint32_t parse_sos(ByteStreamBE&); // returns (predictor << 8 | point transform); if above 0xffff => error
};

struct LibRaw_LjpegDecompressor 
{
	ByteStreamBE buffer;
	LibRaw_SOFInfo sof;
	uint32_t predictor, point_transform;
	uint32_t datastart;
	std::vector<HuffTable> dhts;
	LibRaw_LjpegDecompressor(uint8_t *b, unsigned s);
	LibRaw_LjpegDecompressor(uint8_t *b, unsigned bs, bool dngbug, bool csfix);
	void  initialize(bool dngbug, bool csfix);
    uint8_t next_marker(bool allowskip);
	bool  parse_dht(bool init[4], uint32_t dht_bits[4][17], uint32_t dht_huffval[4][256]); // return true on OK;
	bool decode_ljpeg_422(std::vector<uint16_t> &dest, int width, int height);

	struct State {
      enum States
      {
        OK = 0,
		NotInited = 1,
		NoSOI = 2,
		IncorrectPrecision = 3,
		EOIReached = 4,
		InvalidDHT = 5,
		InvalidSOF = 6,
        InvalidSOS = 7,
		DQTPresent = 8
      };

	};
	struct Marker {
		enum Markers {
			Stuff = 0x00,
			SOF3 = 0xc3, // lossless
			DHT = 0xc4,  // huffman tables
			SOI = 0xd8,  // start of image
			EOI = 0xd9,  // end of image
			SOS = 0xda,  // start of scan
			DQT = 0xdb,  // quantization tables
			Fill = 0xff,
		};
	};
	enum State::States state;
};

