/* -*- C++ -*-
 * Copyright 2019-2025 LibRaw LLC (info@libraw.org)
 *

 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/libraw_cxx_defs.h"
#include <vector>

void LibRaw::dng_ycbcr_thumb_loader()
{
	// Placeholder: the only known camera w/ YCC previews in DNG provides broken files
	throw LIBRAW_EXCEPTION_UNSUPPORTED_FORMAT;
}

void LibRaw::kodak_thumb_loader()
{
  INT64 est_datasize =
      T.theight * T.twidth / 3; // is 0.3 bytes per pixel good estimate?
  if (ID.toffset < 0)
    throw LIBRAW_EXCEPTION_IO_CORRUPT;

  if (ID.toffset + est_datasize > ID.input->size() + THUMB_READ_BEYOND)
    throw LIBRAW_EXCEPTION_IO_EOF;

  if(INT64(T.theight) * INT64(T.twidth) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB)
      throw LIBRAW_EXCEPTION_IO_CORRUPT;

  if (INT64(T.theight) * INT64(T.twidth) < 64ULL)
      throw LIBRAW_EXCEPTION_IO_CORRUPT;

  if(T.twidth < 16 || T.twidth > 8192 || T.theight < 16 || T.theight > 8192)
    throw LIBRAW_EXCEPTION_IO_CORRUPT;

  // some kodak cameras
  ushort s_height = S.height, s_width = S.width, s_iwidth = S.iwidth,
         s_iheight = S.iheight;
  ushort s_flags = libraw_internal_data.unpacker_data.load_flags;
  libraw_internal_data.unpacker_data.load_flags = 12;
  int s_colors = P1.colors;
  unsigned s_filters = P1.filters;
  ushort(*s_image)[4] = imgdata.image;

  S.height = T.theight;
  S.width = T.twidth;
  P1.filters = 0;

#define Tformat libraw_internal_data.unpacker_data.thumb_format


  if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_KODAK_YCBCR)
  {
    S.height += S.height & 1;
    S.width += S.width & 1;
  }

  S.iheight = S.height;
  S.iwidth = S.width;

  imgdata.image =
      (ushort(*)[4])calloc(S.iheight * S.iwidth, sizeof(*imgdata.image));

  ID.input->seek(ID.toffset, SEEK_SET);
  // read kodak thumbnail into T.image[]
  try
  {
      if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_KODAK_YCBCR)
          kodak_ycbcr_load_raw();
      else if(Tformat == LIBRAW_INTERNAL_THUMBNAIL_KODAK_RGB)
        kodak_rgb_load_raw();
      else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_KODAK_THUMB)
        kodak_thumb_load_raw();
  }
  catch (...)
  {
    free(imgdata.image);
    imgdata.image = s_image;

    T.twidth = 0;
    S.width = s_width;

    S.iwidth = s_iwidth;
    S.iheight = s_iheight;

    T.theight = 0;
    S.height = s_height;

    T.tcolors = 0;
    P1.colors = s_colors;

    P1.filters = s_filters;
    T.tlength = 0;
    libraw_internal_data.unpacker_data.load_flags = s_flags;
    return;
  }

  // from scale_colors
  {
    double dmax;
    float scale_mul[4];
    int c, val;
    for (dmax = DBL_MAX, c = 0; c < 3; c++)
      if (dmax > C.pre_mul[c])
        dmax = C.pre_mul[c];

    for (c = 0; c < 3; c++)
      scale_mul[c] = float((C.pre_mul[c] / dmax) * 65535. / C.maximum);
    scale_mul[3] = scale_mul[1];

    size_t size = S.height * S.width;
    for (unsigned i = 0; i < size * 4; i++)
    {
      val = imgdata.image[0][i];
      if (!val)
        continue;
      val = int(val * scale_mul[i & 3]);
      imgdata.image[0][i] = CLIP(val);
    }
  }

  // from convert_to_rgb
  ushort *img;
  int row, col;

  int(*t_hist)[LIBRAW_HISTOGRAM_SIZE] =
      (int(*)[LIBRAW_HISTOGRAM_SIZE])calloc(sizeof(*t_hist), 4);

  if (imgdata.idata.maker_index == LIBRAW_CAMERAMAKER_Canon) // Skip color conversion for canon PPM tiffs
  {
    for (img = imgdata.image[0], row = 0; row < S.height; row++)
      for (col = 0; col < S.width; col++, img += 4)
        for (int c = 0; c < P1.colors; c++)
          t_hist[c][img[c] >> 3]++;
  }
  else
  {
    float out[3], out_cam[3][4] = {{2.81761312f, -1.98369181f, 0.166078627f, 0},
                                   {-0.111855984f, 1.73688626f, -0.625030339f, 0},
                                   {-0.0379119813f, -0.891268849f, 1.92918086f, 0}};
    
	for (img = imgdata.image[0], row = 0; row < S.height; row++)
      for (col = 0; col < S.width; col++, img += 4)
      {
        out[0] = out[1] = out[2] = 0;
        int c;
        for (c = 0; c < 3; c++)
        {
          out[0] += out_cam[0][c] * img[c];
          out[1] += out_cam[1][c] * img[c];
          out[2] += out_cam[2][c] * img[c];
        }
        for (c = 0; c < 3; c++)
          img[c] = CLIP((int)out[c]);
        for (c = 0; c < P1.colors; c++)
          t_hist[c][img[c] >> 3]++;
      }
  }

  // from gamma_lut
  int(*save_hist)[LIBRAW_HISTOGRAM_SIZE] =
      libraw_internal_data.output_data.histogram;
  libraw_internal_data.output_data.histogram = t_hist;

  // make curve output curve!
  ushort *t_curve = (ushort *)calloc(sizeof(C.curve), 1);
  memmove(t_curve, C.curve, sizeof(C.curve));
  memset(C.curve, 0, sizeof(C.curve));
  {
    int perc, val, total, t_white = 0x2000, c;

    perc = int(S.width * S.height * 0.01f); /* 99th percentile white level */
    if (IO.fuji_width)
      perc /= 2;
    if (!((O.highlight & ~2) || O.no_auto_bright))
      for (t_white = c = 0; c < P1.colors; c++)
      {
        for (val = 0x2000, total = 0; --val > 32;)
          if ((total += libraw_internal_data.output_data.histogram[c][val]) >
              perc)
            break;
        if (t_white < val)
          t_white = val;
      }
    gamma_curve(O.gamm[0], O.gamm[1], 2, int((t_white << 3) / O.bright));
  }

  libraw_internal_data.output_data.histogram = save_hist;
  free(t_hist);

  // from write_ppm_tiff - copy pixels into bitmap

  int s_flip = imgdata.sizes.flip;
  if (imgdata.rawparams.options & LIBRAW_RAWOPTIONS_NO_ROTATE_FOR_KODAK_THUMBNAILS)
    imgdata.sizes.flip = 0;

  S.iheight = S.height;
  S.iwidth = S.width;
  if (S.flip & 4)
    SWAP(S.height, S.width);

  if (T.thumb)
    free(T.thumb);
  T.thumb = (char *)calloc(S.width * S.height, P1.colors);
  T.tlength = S.width * S.height * P1.colors;

  // from write_tiff_ppm
  {
    int soff = flip_index(0, 0);
    int cstep = flip_index(0, 1) - soff;
    int rstep = flip_index(1, 0) - flip_index(0, S.width);

    for (int rr = 0; rr < S.height; rr++, soff += rstep)
    {
      char *ppm = T.thumb + rr * S.width * P1.colors;
      for (int cc = 0; cc < S.width; cc++, soff += cstep)
        for (int c = 0; c < P1.colors; c++)
          ppm[cc * P1.colors + c] =
              imgdata.color.curve[imgdata.image[soff][c]] >> 8;
    }
  }

  memmove(C.curve, t_curve, sizeof(C.curve));
  free(t_curve);

  // restore variables
  free(imgdata.image);
  imgdata.image = s_image;

  if (imgdata.rawparams.options & LIBRAW_RAWOPTIONS_NO_ROTATE_FOR_KODAK_THUMBNAILS)
    imgdata.sizes.flip = s_flip;

  T.twidth = S.width;
  S.width = s_width;

  S.iwidth = s_iwidth;
  S.iheight = s_iheight;

  T.theight = S.height;
  S.height = s_height;

  T.tcolors = P1.colors;
  P1.colors = s_colors;

  P1.filters = s_filters;
  libraw_internal_data.unpacker_data.load_flags = s_flags;
}


int LibRaw::thumbOK(INT64 maxsz)
{
  if (!ID.input)
    return 0;
  if (!ID.toffset && !(imgdata.thumbnail.tlength > 0 &&
                       load_raw == &LibRaw::broadcom_load_raw) // RPi
#ifdef USE_6BY9RPI
      && !(imgdata.thumbnail.tlength > 0 && libraw_internal_data.unpacker_data.load_flags & 0x4000 &&
           (load_raw == &LibRaw::rpi_load_raw8 || load_raw == &LibRaw::nokia_load_raw ||
            load_raw == &LibRaw::rpi_load_raw12 || load_raw == &LibRaw::rpi_load_raw14))
#endif
  )
    return 0;
  INT64 fsize = ID.input->size();
  if (fsize > 0xffffffffU)
    return 0; // No thumb for raw > 4Gb-1
  int tsize = 0;
  int tcol = (T.tcolors > 0 && T.tcolors < 4) ? T.tcolors : 3;
  if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_DNG_YCBCR)
	  return 0; // Temp: unless we get correct DNG w/ YCBCR preview
  else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_JPEG)
    tsize = T.tlength;
  else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_PPM)
    tsize = tcol * T.twidth * T.theight;
  else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_PPM16)
    tsize = tcol * T.twidth * T.theight *
            ((imgdata.rawparams.options & LIBRAW_RAWOPTIONS_USE_PPM16_THUMBS) ? 2 : 1);
#ifdef USE_X3FTOOLS
  else if (Tformat == LIBRAW_INTERNAL_THUMBNAIL_X3F)
  {
    tsize = x3f_thumb_size();
  }
#endif
  else // Kodak => no check
    tsize = 1;
  if (tsize < 0)
    return 0;
  if (maxsz > 0 && tsize > maxsz)
    return 0;
  return (tsize + ID.toffset <= fsize) ? 1 : 0;
}

int LibRaw::dcraw_thumb_writer(const char *fname)
{
  //    CHECK_ORDER_LOW(LIBRAW_PROGRESS_THUMB_LOAD);

  if (!fname)
    return ENOENT;

  FILE *tfp = fopen(fname, "wb");

  if (!tfp)
    return errno;

  if (!T.thumb)
  {
    fclose(tfp);
    return LIBRAW_OUT_OF_ORDER_CALL;
  }

  try
  {
    switch (T.tformat)
    {
    case LIBRAW_THUMBNAIL_JPEGXL:
      fwrite(T.thumb, 1, T.tlength, tfp);
      break;
    case LIBRAW_THUMBNAIL_JPEG:
      jpeg_thumb_writer(tfp, T.thumb, T.tlength);
      break;
    case LIBRAW_THUMBNAIL_BITMAP:
      fprintf(tfp, "P%d\n%d %d\n255\n", T.tcolors == 1 ? 5 : 6,  T.twidth, T.theight);
      fwrite(T.thumb, 1, T.tlength, tfp);
      break;
    default:
      fclose(tfp);
      return LIBRAW_UNSUPPORTED_THUMBNAIL;
    }
    fclose(tfp);
    return 0;
  }
  catch (const std::bad_alloc&)
  {
      fclose(tfp);
      EXCEPTION_HANDLER(LIBRAW_EXCEPTION_ALLOC);
  }
  catch (const LibRaw_exceptions& err)
  {
    fclose(tfp);
    EXCEPTION_HANDLER(err);
  }
}
