/* -*- C++ -*-
 * File: huffmandec.cpp
 * Copyright (C) 2024-2025 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).

 */

#include "../../internal/losslessjpeg.h"
#include <string.h>

#define ZERO(a) do { memset(a,0,sizeof(a));} while(0)

bool ByteStreamBE::skip_to_marker() // true: success, false - no marker
{
	if (pos + 2 > size) return false;
	while (!(buffer[pos] == 0xff && buffer[pos + 1] != 0 && buffer[pos + 1] != 0xff))
	{
		pos++;
		if (pos + 2 > size)
			return false;
	}
	pos++;
	return true;
}

LibRaw_LjpegDecompressor::LibRaw_LjpegDecompressor(uint8_t *b, unsigned bs, bool dngbug, bool csfix): buffer(b,bs),
	state(State::NotInited)
{
	initialize(dngbug,csfix);
}

LibRaw_LjpegDecompressor::LibRaw_LjpegDecompressor(uint8_t *b, unsigned bs): buffer(b,bs),
	state(State::NotInited)
{
	initialize(false,false);
}


void LibRaw_LjpegDecompressor::initialize(bool dngbug, bool csfix)
{
	sof.csfix = csfix;
	bool dht_init[4] = { false,false,false,false };
	uint32_t dht_bits[4][17];
	uint32_t dht_huffval[4][256];
	ZERO(dht_bits);
    ZERO(dht_huffval);
    if (next_marker(false) != Marker::SOI)
    {
      state = State::NoSOI;
      return;
    }
	while (1)
	{
		uint8_t marker = next_marker(true);
		if (marker == Marker::SOF3)
		{
			if (!sof.parse_sof(buffer))
			{
              state = State::InvalidSOF;
              return;
			}
			if (sof.precision > 16 || sof.precision < 12)
			{
				state = State::IncorrectPrecision;
				return;
			}
		}
        else if (marker == Marker::DHT)
        {
          bool hres = parse_dht(dht_init, dht_bits, dht_huffval);
		  if(!hres)
		  {
			  state = State::InvalidDHT;
			  return;
		  }
        }
        else if ( marker == Marker::SOS)
        {
          uint32_t val = sof.parse_sos(buffer);
          if (val < 0x10000)
          {
            predictor = (val >> 8) & 0xff;
            point_transform = val & 0xff;
          }
          else
          {
            state = State::InvalidSOS;
            return;
          }
		  break;
        }
		else if (marker == Marker::EOI)
		{
			state = State::EOIReached;
			return;
		}
		else if (marker == Marker::DQT)
		{
          state = State::DQTPresent;
          return;
		}
		else if(marker == Marker::Fill)
		{
          state = State::EOIReached;
          return;
		}
	}

	dhts.resize(4);
	for (int i = 0; i < 4; i++)
		if (dht_init[i])
			dhts[i].initval(dht_bits[i], dht_huffval[i], dngbug);
	datastart = buffer.pos;
    state = State::OK;
}

uint8_t LibRaw_LjpegDecompressor::next_marker(bool allowskip)
{
	if (!allowskip)
	{
		if (buffer.get_u8() != 0xff)
			return Marker::Fill; // Error;
		uint8_t mark = buffer.get_u8();
		return mark;
	}
	if (buffer.skip_to_marker())
		return buffer.get_u8();
	else
		return Marker::Fill;
}

bool LibRaw_LjpegDecompressor::parse_dht(bool init[4], uint32_t bits[4][17], uint32_t huffval[4][256])
{
  uint16_t length = buffer.get_u16() - 2u;

  while (length > 0)
  {
    uint8_t b = buffer.get_u8();
    uint8_t tc = b >> 4;
    uint8_t th = b & 0xf;

    if (tc != 0)
      return false;

    if (th > 3)
      return false;

    uint32_t acc = 0;
    for (int i = 0; i < 16; i++)
    {
      bits[th][i + 1] = buffer.get_u8();
      acc += bits[th][i + 1];
    }
    bits[th][0] = 0;

    if (acc > 256)
      return false;

    if (length < 1 + 16 + acc)
      return false;
	for (uint32_t i = 0; i < acc; i++)
		huffval[th][i] = buffer.get_u8();

    init[th] = true;
    length -= 1 + 16 + acc;
  }
  return true;
}

static
#ifdef _MSC_VER
__forceinline
#else
inline
#endif
void copy_yuv_422(uint16_t *out, uint32_t row, uint32_t col, uint32_t width, int32_t y1, int32_t y2,
	 int32_t cb, int32_t cr)
{
  uint32_t pix1 = row * width + col;
  uint32_t pix2 = pix1 + 3;
  out[pix1 + 0] = uint16_t(y1);
  out[pix1 + 1] = uint16_t(cb);
  out[pix1 + 2] = uint16_t(cr);
  out[pix2 + 0] = uint16_t(y2);
  out[pix2 + 1] = uint16_t(cb);
  out[pix2 + 2] = uint16_t(cr);
}


bool LibRaw_LjpegDecompressor::decode_ljpeg_422(std::vector<uint16_t> &_dest, int width, int height)
{
  if (sof.width * 3u != unsigned(width) || sof.height != unsigned(height))
    return false;
  if (width % 2 || width % 6 || height % 2)
    return false;

  if (_dest.size() < size_t(width * height))
    return false;

  if(width < 1 || height < 1)
    return false;

  uint16_t *dest = _dest.data();

  HuffTable &h1 = dhts[sof.components[0].dc_tbl];
  HuffTable &h2 = dhts[sof.components[1].dc_tbl];
  HuffTable &h3 = dhts[sof.components[2].dc_tbl];

  if (!h1.initialized || !h2.initialized || !h3.initialized)
    return false;

  BitPumpJpeg pump(buffer);

  int32_t base = 1 << (sof.precision - point_transform - 1);
  int32_t y1 = base + h1.decode(pump);
  int32_t y2 = y1 + h1.decode(pump);
  int32_t cb = base + h2.decode(pump);
  int32_t cr = base + h3.decode(pump);
  copy_yuv_422(dest, 0, 0, width, y1, y2, cb, cr);

  for (uint32_t row = 0; row < uint32_t(height); row++)
  {
	  uint32_t startcol = row == 0 ? 6 : 0;
	  for (uint32_t col = startcol; col < uint32_t(width); col += 6)
	  {
        uint32_t pos = (col == 0) ? (row - 1) * width : row * width + col - 3;
        int32_t py = dest[pos],
			pcb = dest[pos + 1],
			pcr = dest[pos + 2];
        int32_t _y1 = py + h1.decode(pump);
        int32_t _y2 = _y1 + h1.decode(pump);
        int32_t _cb = pcb + h2.decode(pump);
        int32_t _cr = pcr + h3.decode(pump);
        copy_yuv_422(dest, row, col, width, _y1, _y2, _cb, _cr);
	  }
  }
  return true;
}

bool LibRaw_SOFInfo::parse_sof(ByteStreamBE& input)
{
	uint32_t header_length = input.get_u16();
	precision = input.get_u8();
    height = input.get_u16();
	width = input.get_u16();
	cps = input.get_u8();

	if (precision > 16)
		return false;
	if (cps > 4 || cps < 1)
		return false;
	if (header_length != 8 + cps * 3)
		return false;

	components.clear();
	for (unsigned i = 0; i < cps; i++)
	{
      unsigned id = input.get_u8();
	  unsigned subs = input.get_u8();
	  components.push_back(LibRaw_JpegComponentInfo(id, i, 0, (subs >> 4), (subs & 0xf) ));
      input.get_u8(); 
	}
	return true;
}

uint32_t LibRaw_SOFInfo::parse_sos(ByteStreamBE& input)
{
  if (width == 0)
    return 0x10000;

  input.get_u16();

  uint32_t soscps = input.get_u8();
  if (cps != soscps)
	  return 0x10000;
  for (uint32_t csi = 0; csi < cps; csi++)
  {
    uint32_t readcs = input.get_u8();
	uint32_t cs = csfix ? csi : readcs; // csfix might be used in MOS decoder
	int cid = -1;
	for(unsigned c = 0; c < components.size(); c++)
		if (components[c].id == cs)
		{
			cid = c;
			break;
		}
	if (cid < 0)
		return 0x10000;

    uint8_t td = input.get_u8() >> 4;
	if (td > 3)
		return 0x10000;
	components[cid].dc_tbl = td;
  }
  uint8_t pred = input.get_u8();
  input.get_u8();
  uint8_t pt = input.get_u8() & 0xf;
  return (pred << 8 | pt);
}

HuffTable::HuffTable()
{
	ZERO(bits);
	ZERO(huffval);
	ZERO(shiftval);
	dng_bug = false;
	disable_cache = false;
	nbits = 0;
	initialized = false;
}

struct PseudoPump : public BitPump
{
	uint64_t bits;
	int32_t nbits;
	PseudoPump() : bits(0), nbits(0) {}

	void set(uint32_t _bits, uint32_t nb)
	{
		bits = uint64_t(_bits) << 32;
		nbits = nb + 32;
	}
	int32_t valid() { return nbits - 32; }

	uint32_t peek(uint32_t num)
	{
		return uint32_t((bits >> (nbits - num)) & 0xffffffffUL);
	}

    void consume(uint32_t num)
    {
      nbits -= num;
      bits &= (uint64_t(1) << nbits) - 1UL;
    }
};

void HuffTable::initval(uint32_t _bits[17], uint32_t _huffval[256], bool _dng_bug)
{
	memmove(bits, _bits, sizeof(bits));
	memmove(huffval, _huffval, sizeof(huffval));
	dng_bug = _dng_bug;

	nbits = 16;
    for(int i = 0; i < 16; i++)
      {
        if(bits[16 - i] != 0)
			break;
		nbits--;
      }
	hufftable.resize( size_t(1ULL << nbits));
	uint32_t tsize = 1 << nbits;
	for (unsigned i = 0; i < hufftable.size(); i++) hufftable[i] = 0;

	int h = 0;
    int pos = 0;
    for (uint8_t len = 0; len < nbits; len++)
    {
      for (uint32_t i = 0; i < bits[len + 1]; i++)
      {
        for (int j = 0; j < (1 << (nbits - len - 1)); j++)
        {
          if (h >= tsize)
            throw LIBRAW_EXCEPTION_IO_CORRUPT;
          hufftable[h] = ((len + 1) << 16) | (uint8_t(huffval[pos] & 0xff) << 8) | uint8_t(shiftval[pos] & 0xff);
          h++;
        }
        pos++;
		if(pos >= 256)
          throw LIBRAW_EXCEPTION_IO_CORRUPT;
      }
    }
	if (!disable_cache)
	{
		PseudoPump pump;
		decodecache = std::vector<uint64_t>(1 << LIBRAW_DECODE_CACHE_BITS,0);
		for(uint32_t i = 0; i < 1 << LIBRAW_DECODE_CACHE_BITS; i++)
		{
          pump.set(i, LIBRAW_DECODE_CACHE_BITS);
		  uint32_t len;
		  int16_t val16 = int16_t(decode_slow2(pump,len));
		  if (pump.valid() >= 0)
			  decodecache[i] = LIBRAW_CACHE_PRESENT_FLAG | uint64_t(((len & 0xff) << 16) | uint16_t(val16));
		}
	}
    initialized = true;
}
