/* Copyright  (C) 2010-2020 The RetroArch team
 *
 * ---------------------------------------------------------------------------------------
 * The following license statement only applies to this file (image_texture.c).
 * ---------------------------------------------------------------------------------------
 *
 * Permission is hereby granted, free of charge,
 * to any person obtaining a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>

#include <boolean.h>
#include <formats/image.h>
#include <formats/data_transfer.h>

enum image_type_enum image_texture_get_type(const char *path)
{
   /* We are comparing against a fixed list of file
    * extensions, the longest (jpeg) being 4 characters
    * in length. We therefore only need to extract the first
    * 5 characters from the extension of the input path
    * to correctly validate a match */
   size_t len;
   const char *ext = NULL;
   if (!path || *path == '\0')
      return IMAGE_TYPE_NONE;

   ext = strrchr(path, '.');
   if (!ext || (*(++ext) == '\0'))
      return IMAGE_TYPE_NONE;

   len = strlen(ext);

   /* All supported extensions are 3 or 4 characters */
   if (len < 3 || len > 4)
      return IMAGE_TYPE_NONE;

   /* Compare with inline lowering — avoids copy + tolower pass */
   switch (len)
   {
      case 3:
#ifdef HAVE_RPNG
         if ((ext[0] | 0x20) == 'p' &&
             (ext[1] | 0x20) == 'n' &&
             (ext[2] | 0x20) == 'g')
            return IMAGE_TYPE_PNG;
#endif
#ifdef HAVE_RJPEG
         if ((ext[0] | 0x20) == 'j' &&
             (ext[1] | 0x20) == 'p' &&
             (ext[2] | 0x20) == 'g')
            return IMAGE_TYPE_JPEG;
#endif
#ifdef HAVE_RBMP
         if ((ext[0] | 0x20) == 'b' &&
             (ext[1] | 0x20) == 'm' &&
             (ext[2] | 0x20) == 'p')
            return IMAGE_TYPE_BMP;
#endif
#ifdef HAVE_RTGA
         if ((ext[0] | 0x20) == 't' &&
             (ext[1] | 0x20) == 'g' &&
             (ext[2] | 0x20) == 'a')
            return IMAGE_TYPE_TGA;
#endif
#ifdef HAVE_RDDS
         if ((ext[0] | 0x20) == 'd' &&
             (ext[1] | 0x20) == 'd' &&
             (ext[2] | 0x20) == 's')
            return IMAGE_TYPE_DDS;
#endif
#ifdef HAVE_RMP4
         if ((ext[0] | 0x20) == 'm' &&
              ext[1]         == '4' &&
             (ext[2] | 0x20) == 'v')
            return IMAGE_TYPE_MP4;
         if ((ext[0] | 0x20) == 'm' &&
             (ext[1] | 0x20) == 'p' &&
              ext[2]         == '4')
            return IMAGE_TYPE_MP4;
#endif
         break;

      case 4:
#ifdef HAVE_RJPEG
         if ((ext[0] | 0x20) == 'j' &&
             (ext[1] | 0x20) == 'p' &&
             (ext[2] | 0x20) == 'e' &&
             (ext[3] | 0x20) == 'g')
            return IMAGE_TYPE_JPEG;
#endif
#ifdef HAVE_RWEBP
         if ((ext[0] | 0x20) == 'w' &&
             (ext[1] | 0x20) == 'e' &&
             (ext[2] | 0x20) == 'b' &&
             (ext[3] | 0x20) == 'p')
            return IMAGE_TYPE_WEBP;
#endif
#ifdef HAVE_RWEBM
         if ((ext[0] | 0x20) == 'w' &&
             (ext[1] | 0x20) == 'e' &&
             (ext[2] | 0x20) == 'b' &&
             (ext[3] | 0x20) == 'm')
            return IMAGE_TYPE_WEBM;
#endif
         break;
   }

   return IMAGE_TYPE_NONE;
}

static bool image_texture_load_internal(
      enum image_type_enum type,
      void *ptr,
      size_t len,
      struct texture_image *out_img)
{
   int ret;
   void *img;

   out_img->compressed = NULL;

   img = image_transfer_new(type);
   if (!img)
      return false;

   image_transfer_set_buffer_ptr(img, type, (uint8_t*)ptr, len);

   if (!image_transfer_start(img, type))
   {
      image_transfer_free(img, type);
      return false;
   }

   while (image_transfer_iterate(img, type));

   if (!image_transfer_is_valid(img, type))
   {
      image_transfer_free(img, type);
      return false;
   }

   /* GPU-native fast path: if the loader can hand back BCn blocks for
    * direct upload, copy the source (so the mip pointers survive the
    * caller freeing its buffer) and defer any RGBA8 decode. */
   {
      struct image_gpu_layout lay;
      if (image_transfer_get_gpu_layout(img, type, len, &lay))
      {
         struct texture_compressed *tc = (struct texture_compressed*)
            calloc(1, sizeof(*tc));
         if (tc)
         {
            tc->mips    = (struct texture_mip*)
               malloc((size_t)lay.num_mips * sizeof(*tc->mips));
            tc->storage = malloc(len);
            if (tc->mips && tc->storage)
            {
               unsigned i;
               memcpy(tc->storage, ptr, len);
               tc->storage_len = len;
               tc->num_mips    = lay.num_mips;
               tc->format      = lay.format;
               tc->type        = type;
               for (i = 0; i < lay.num_mips; i++)
               {
                  tc->mips[i].data   = (const unsigned char*)tc->storage
                                     + lay.offset[i];
                  tc->mips[i].width  = lay.width[i];
                  tc->mips[i].height = lay.height[i];
                  tc->mips[i].size   = lay.size[i];
               }
               out_img->compressed = tc;
               out_img->pixels     = NULL;
               out_img->width      = lay.width[0];
               out_img->height     = lay.height[0];
               image_transfer_free(img, type);
               return true;
            }
            free(tc->storage);
            free(tc->mips);
            free(tc);
         }
         /* allocation failure: fall through to the CPU decode below */
      }
   }

   do
   {
      ret = image_transfer_process(img, type,
            (uint32_t**)&out_img->pixels, len, &out_img->width,
            &out_img->height, out_img->supports_rgba);
   } while (ret == IMAGE_PROCESS_NEXT);

   if (ret == IMAGE_PROCESS_ERROR || ret == IMAGE_PROCESS_ERROR_END)
   {
      image_transfer_free(img, type);
      return false;
   }

   image_transfer_free(img, type);
   return true;
}

void image_texture_free(struct texture_image *img)
{
   if (!img)
      return;

   if (img->compressed)
   {
      free(img->compressed->storage);
      free(img->compressed->mips);
      free(img->compressed);
      img->compressed = NULL;
   }
   if (img->pixels)
      free(img->pixels);
   img->width  = 0;
   img->height = 0;
   img->pixels = NULL;
}

bool image_texture_realize_rgba(struct texture_image *img)
{
   struct texture_compressed *tc;
   void    *decoded = NULL;
   void    *xfer;
   unsigned w       = 0;
   unsigned h       = 0;
   int      ret;

   if (!img)
      return false;
   if (img->pixels)               /* already decoded */
      return true;
   tc = img->compressed;
   if (!tc || !tc->storage)
      return false;

   xfer = image_transfer_new(tc->type);
   if (!xfer)
      return false;
   image_transfer_set_buffer_ptr(xfer, tc->type,
         (uint8_t*)tc->storage, tc->storage_len);
   if (!image_transfer_start(xfer, tc->type))
   {
      image_transfer_free(xfer, tc->type);
      return false;
   }
   while (image_transfer_iterate(xfer, tc->type));
   if (!image_transfer_is_valid(xfer, tc->type))
   {
      image_transfer_free(xfer, tc->type);
      return false;
   }
   do
   {
      ret = image_transfer_process(xfer, tc->type,
            (uint32_t**)&decoded, tc->storage_len, &w, &h,
            img->supports_rgba);
   } while (ret == IMAGE_PROCESS_NEXT);
   image_transfer_free(xfer, tc->type);

   if (     ret == IMAGE_PROCESS_ERROR
         || ret == IMAGE_PROCESS_ERROR_END
         || !decoded)
      return false;

   img->pixels = (uint32_t*)decoded;
   img->width  = w;
   img->height = h;
   return true;
}

void image_texture_narrow_10bit(struct texture_image *img)
{
   size_t n, i;
   uint32_t *px;
   if (!img || !img->pix10 || !img->pixels)
      return;
   px = img->pixels;
   n  = (size_t)img->width * img->height;
   /* Narrow packed XRGB2101010 (R[29:20] G[19:10] B[9:0]) to 8-bit ARGB8888
    * (0xAARRGGBB, opaque) in place, for drivers without native 10-bit
    * texture support. Matches the >> 2 narrowing used elsewhere. */
   for (i = 0; i < n; i++)
   {
      uint32_t p = px[i];
      uint32_t r = (p >> 20) & 0x3ff;
      uint32_t g = (p >> 10) & 0x3ff;
      uint32_t b =  p        & 0x3ff;
      px[i] = 0xff000000u
            | ((r >> 2) << 16)
            | ((g >> 2) <<  8)
            |  (b >> 2);
   }
   img->pix10 = false;
}

bool image_texture_load_buffer(struct texture_image *out_img,
   enum image_type_enum type, void *buffer, size_t buffer_len)
{
   if (type != IMAGE_TYPE_NONE)
   {
      if (image_texture_load_internal(
         type, buffer, buffer_len, out_img))
         return true;
   }

   out_img->supports_rgba = false;
   out_img->pixels = NULL;
   out_img->width = 0;
   out_img->height = 0;
   out_img->compressed = NULL;

   return false;
}

bool image_texture_load(struct texture_image *out_img,
      const char *path)
{
   enum image_type_enum type  = image_texture_get_type(path);

   if (type != IMAGE_TYPE_NONE)
   {
      /* The synchronous read rides the data_transfer prefix spine
       * like every other loader: filestream/VFS routing (overlays
       * and driver assets from archive members or content://
       * documents), 64-bit lengths, the hardware guard behind the
       * read, and honest short-read detection.  A zero budget fills
       * to completion in one blocking call, which is this API's
       * contract. */
      struct data_transfer *dt = data_transfer_open_prefix(path, 0);
      if (dt)
      {
         size_t file_len    = 0;
         const uint8_t *ptr = NULL;

         data_transfer_iterate(dt, 0);
         ptr = data_transfer_ptr(dt, &file_len);
         if (data_transfer_complete(dt) && ptr && file_len
               && image_texture_load_internal(
                     type,
                     (void*)ptr, file_len, out_img))
         {
            data_transfer_free(dt);
            return true;
         }
         data_transfer_free(dt);
      }
   }

   out_img->supports_rgba = false;
   out_img->pixels        = NULL;
   out_img->width         = 0;
   out_img->height        = 0;
   out_img->compressed    = NULL;

   return false;
}
