#include "util/format/u_format.h"
#include "zink_format.h"
#include "util/u_math.h"

enum pipe_format
zink_decompose_vertex_format(enum pipe_format format)
{
   const struct util_format_description *desc = util_format_description(format);
   unsigned first_non_void = util_format_get_first_non_void_channel(format);
   enum pipe_format new_format;
   assert(first_non_void == 0);
   if (!desc->is_array)
      return PIPE_FORMAT_NONE;
   if (desc->is_unorm) {
      enum pipe_format unorm_formats[] = {
         PIPE_FORMAT_R8_UNORM,
         PIPE_FORMAT_R16_UNORM,
         PIPE_FORMAT_R32_UNORM
      };
      return unorm_formats[desc->channel[first_non_void].size >> 4];
   } else if (desc->is_snorm) {
      enum pipe_format snorm_formats[] = {
         PIPE_FORMAT_R8_SNORM,
         PIPE_FORMAT_R16_SNORM,
         PIPE_FORMAT_R32_SNORM
      };
      return snorm_formats[desc->channel[first_non_void].size >> 4];
   } else {
      enum pipe_format uint_formats[][3] = {
         {PIPE_FORMAT_R8_USCALED, PIPE_FORMAT_R16_USCALED, PIPE_FORMAT_R32_USCALED},
         {PIPE_FORMAT_R8_UINT, PIPE_FORMAT_R16_UINT, PIPE_FORMAT_R32_UINT},
      };
      enum pipe_format sint_formats[][3] = {
         {PIPE_FORMAT_R8_SSCALED, PIPE_FORMAT_R16_SSCALED, PIPE_FORMAT_R32_SSCALED},
         {PIPE_FORMAT_R8_SINT, PIPE_FORMAT_R16_SINT, PIPE_FORMAT_R32_SINT},
      };
      switch (desc->channel[first_non_void].type) {
      case UTIL_FORMAT_TYPE_UNSIGNED:
         return uint_formats[desc->channel[first_non_void].pure_integer][desc->channel[first_non_void].size >> 4];
      case UTIL_FORMAT_TYPE_SIGNED:
         return sint_formats[desc->channel[first_non_void].pure_integer][desc->channel[first_non_void].size >> 4];
      case UTIL_FORMAT_TYPE_FLOAT:
         return desc->channel[first_non_void].size == 16 ? PIPE_FORMAT_R16_FLOAT : PIPE_FORMAT_R32_FLOAT;
         break;
      default:
         return PIPE_FORMAT_NONE;
      }
   }
   return new_format;
}

bool
zink_format_is_red_alpha(enum pipe_format format)
{
   switch (format) {
   case PIPE_FORMAT_R4A4_UNORM:
   case PIPE_FORMAT_R8A8_SINT:
   case PIPE_FORMAT_R8A8_SNORM:
   case PIPE_FORMAT_R8A8_UINT:
   case PIPE_FORMAT_R8A8_UNORM:
   case PIPE_FORMAT_R16A16_SINT:
   case PIPE_FORMAT_R16A16_SNORM:
   case PIPE_FORMAT_R16A16_UINT:
   case PIPE_FORMAT_R16A16_UNORM:
   case PIPE_FORMAT_R16A16_FLOAT:
   case PIPE_FORMAT_R32A32_SINT:
   case PIPE_FORMAT_R32A32_UINT:
   case PIPE_FORMAT_R32A32_FLOAT:
      return true;
   default: break;
   }
   return false;
}

bool
zink_format_is_emulated_alpha(enum pipe_format format)
{
   return util_format_is_alpha(format) ||
          util_format_is_luminance(format) ||
          util_format_is_luminance_alpha(format) ||
          zink_format_is_red_alpha(format);
}

static enum pipe_format
emulate_alpha(enum pipe_format format)
{
   if (format == PIPE_FORMAT_A8_UNORM)
      return PIPE_FORMAT_R8_UNORM;
   if (format == PIPE_FORMAT_A8_UINT)
      return PIPE_FORMAT_R8_UINT;
   if (format == PIPE_FORMAT_A8_SNORM)
      return PIPE_FORMAT_R8_SNORM;
   if (format == PIPE_FORMAT_A8_SINT)
      return PIPE_FORMAT_R8_SINT;
   if (format == PIPE_FORMAT_A16_UNORM)
      return PIPE_FORMAT_R16_UNORM;
   if (format == PIPE_FORMAT_A16_UINT)
      return PIPE_FORMAT_R16_UINT;
   if (format == PIPE_FORMAT_A16_SNORM)
      return PIPE_FORMAT_R16_SNORM;
   if (format == PIPE_FORMAT_A16_SINT)
      return PIPE_FORMAT_R16_SINT;
   if (format == PIPE_FORMAT_A16_FLOAT)
      return PIPE_FORMAT_R16_FLOAT;
   if (format == PIPE_FORMAT_A32_UINT)
      return PIPE_FORMAT_R32_UINT;
   if (format == PIPE_FORMAT_A32_SINT)
      return PIPE_FORMAT_R32_SINT;
   if (format == PIPE_FORMAT_A32_FLOAT)
      return PIPE_FORMAT_R32_FLOAT;
   return format;
}

static enum pipe_format
emulate_red_alpha(enum pipe_format format)
{
   switch (format) {
   case PIPE_FORMAT_R8A8_SINT:
      return PIPE_FORMAT_R8G8_SINT;
   case PIPE_FORMAT_R8A8_SNORM:
      return PIPE_FORMAT_R8G8_SNORM;
   case PIPE_FORMAT_R8A8_UINT:
      return PIPE_FORMAT_R8G8_UINT;
   case PIPE_FORMAT_R8A8_UNORM:
      return PIPE_FORMAT_R8G8_UNORM;
   case PIPE_FORMAT_R16A16_SINT:
      return PIPE_FORMAT_R16G16_SINT;
   case PIPE_FORMAT_R16A16_SNORM:
      return PIPE_FORMAT_R16G16_SNORM;
   case PIPE_FORMAT_R16A16_UINT:
      return PIPE_FORMAT_R16G16_UINT;
   case PIPE_FORMAT_R16A16_UNORM:
      return PIPE_FORMAT_R16G16_UNORM;
   case PIPE_FORMAT_R16A16_FLOAT:
      return PIPE_FORMAT_R16G16_FLOAT;
   case PIPE_FORMAT_R32A32_SINT:
      return PIPE_FORMAT_R32G32_SINT;
   case PIPE_FORMAT_R32A32_UINT:
      return PIPE_FORMAT_R32G32_UINT;
   case PIPE_FORMAT_R32A32_FLOAT:
      return PIPE_FORMAT_R32G32_FLOAT;
   default: break;
   }
   return format;
}

enum pipe_format
zink_format_get_emulated_alpha(enum pipe_format format)
{
   if (util_format_is_alpha(format))
      return emulate_alpha(format);
   if (util_format_is_luminance(format))
      return util_format_luminance_to_red(format);
   if (util_format_is_luminance_alpha(format)) {
      if (util_format_is_srgb(format))
         return format;
      if (format == PIPE_FORMAT_LATC2_UNORM)
         return PIPE_FORMAT_RGTC2_UNORM;
      if (format == PIPE_FORMAT_LATC2_SNORM)
         return PIPE_FORMAT_RGTC2_SNORM;

      format = util_format_luminance_to_red(format);
   }
   if (util_format_is_intensity(format))
      return util_format_intensity_to_red(format);

   return emulate_red_alpha(format);
}


enum pipe_format
zink_format_emulate_x8(enum pipe_format format)
{
   /* convert missing Xn variants to An */
   switch (format) {
   case PIPE_FORMAT_B8G8R8X8_UNORM:
      return PIPE_FORMAT_B8G8R8A8_UNORM;

   case PIPE_FORMAT_B8G8R8X8_SRGB:
      return PIPE_FORMAT_B8G8R8A8_SRGB;
   case PIPE_FORMAT_R8G8B8X8_SRGB:
      return PIPE_FORMAT_R8G8B8A8_SRGB;

   case PIPE_FORMAT_R8G8B8X8_SINT:
      return PIPE_FORMAT_R8G8B8A8_SINT;
   case PIPE_FORMAT_R8G8B8X8_SNORM:
      return PIPE_FORMAT_R8G8B8A8_SNORM;
   case PIPE_FORMAT_R8G8B8X8_UNORM:
      return PIPE_FORMAT_R8G8B8A8_UNORM;

   case PIPE_FORMAT_R16G16B16X16_FLOAT:
      return PIPE_FORMAT_R16G16B16A16_FLOAT;
   case PIPE_FORMAT_R16G16B16X16_SINT:
      return PIPE_FORMAT_R16G16B16A16_SINT;
   case PIPE_FORMAT_R16G16B16X16_SNORM:
      return PIPE_FORMAT_R16G16B16A16_SNORM;
   case PIPE_FORMAT_R16G16B16X16_UNORM:
      return PIPE_FORMAT_R16G16B16A16_UNORM;

   case PIPE_FORMAT_R32G32B32X32_FLOAT:
      return PIPE_FORMAT_R32G32B32A32_FLOAT;
   case PIPE_FORMAT_R32G32B32X32_SINT:
      return PIPE_FORMAT_R32G32B32A32_SINT;

   case PIPE_FORMAT_R10G10B10X2_SINT:
      return PIPE_FORMAT_R10G10B10A2_SINT;
   case PIPE_FORMAT_R10G10B10X2_SNORM:
      return PIPE_FORMAT_R10G10B10A2_SNORM;
   case PIPE_FORMAT_R10G10B10X2_UNORM:
      return PIPE_FORMAT_R10G10B10A2_UNORM;

   case PIPE_FORMAT_B10G10R10X2_SINT:
      return PIPE_FORMAT_B10G10R10A2_SINT;
   case PIPE_FORMAT_B10G10R10X2_SNORM:
      return PIPE_FORMAT_B10G10R10A2_SNORM;
   case PIPE_FORMAT_B10G10R10X2_UNORM:
      return PIPE_FORMAT_B10G10R10A2_UNORM;

   case PIPE_FORMAT_R5G5B5X1_UNORM:
      return PIPE_FORMAT_R5G5B5A1_UNORM;
   case PIPE_FORMAT_B5G5R5X1_UNORM:
      return PIPE_FORMAT_B5G5R5A1_UNORM;

   default:
      return format;
   }
}

bool
zink_format_is_voidable_rgba_variant(enum pipe_format format)
{
   return format != zink_format_emulate_x8(format);
}

void
zink_format_clamp_channel_color(const struct util_format_description *desc, union pipe_color_union *dst, const union pipe_color_union *src, unsigned i)
{
   int non_void = util_format_get_first_non_void_channel(desc->format);
   unsigned channel = desc->swizzle[i];

   if (channel > PIPE_SWIZZLE_W || desc->channel[channel].type == UTIL_FORMAT_TYPE_VOID) {
      if (non_void != -1) {
         if (desc->channel[non_void].type == UTIL_FORMAT_TYPE_FLOAT) {
            dst->f[i] = uif(UINT32_MAX);
         } else {
            if (desc->channel[non_void].normalized)
               dst->f[i] = 1.0;
            else if (desc->channel[non_void].type == UTIL_FORMAT_TYPE_SIGNED)
               dst->i[i] = INT32_MAX;
            else
               dst->ui[i] = UINT32_MAX;
         }
      } else {
         dst->ui[i] = src->ui[i];
      }
      return;
   }

   switch (desc->channel[channel].type) {
   case UTIL_FORMAT_TYPE_VOID:
      UNREACHABLE("handled above");
      break;
   case UTIL_FORMAT_TYPE_SIGNED:
      if (desc->channel[channel].normalized)
         dst->i[i] = src->i[i];
      else {
         dst->i[i] = MAX2(src->i[i], -(1<<(desc->channel[channel].size - 1)));
         dst->i[i] = MIN2(dst->i[i], (1 << (desc->channel[channel].size - 1)) - 1);
      }
      break;
   case UTIL_FORMAT_TYPE_UNSIGNED:
      if (desc->channel[channel].normalized)
         dst->ui[i] = src->ui[i];
      else
         dst->ui[i] = MIN2(src->ui[i], BITFIELD_MASK(desc->channel[channel].size));
      break;
   case UTIL_FORMAT_TYPE_FIXED:
   case UTIL_FORMAT_TYPE_FLOAT:
      dst->ui[i] = src->ui[i];
      break;
   }
}

void
zink_format_clamp_channel_srgb(const struct util_format_description *desc, union pipe_color_union *dst, const union pipe_color_union *src, unsigned i)
{
   unsigned channel = desc->swizzle[i];
   if (desc->colorspace == UTIL_FORMAT_COLORSPACE_SRGB &&
       channel <= PIPE_SWIZZLE_W) {
      switch (desc->channel[channel].type) {
      case UTIL_FORMAT_TYPE_SIGNED:
      case UTIL_FORMAT_TYPE_UNSIGNED:
         dst->f[i] = CLAMP(src->f[i], 0.0, 1.0);
         return;
      default:
         break;
      }
   }

   dst->ui[i] = src->ui[i];
}
