/******************************************************************************/
/*
  Project   - MudBun
  Publisher - Long Bunny Labs
              http://LongBunnyLabs.com
  Author    - Ming-Lun "Allen" Chou
              http://AllenChou.net
*/
/******************************************************************************/

using System.Runtime.InteropServices;

using UnityEngine;

namespace MudBun
{
  public class Codec
  {
    // Vector2 between 0.0 and 1.0
    // https://stackoverflow.com/questions/17638800/storing-two-float-values-in-a-single-float-variable
    //-----------------------------------------------------------------------------

    public static float PackSaturated(float a, float b)
    {
      const int precision = 4096;
      a = Mathf.Floor(a * (precision - 1));
      b = Mathf.Floor(b * (precision - 1));
      return a * precision + b;
    }

    public static float PackSaturated(Vector2 v)
    {
      return PackSaturated(v.x, v.y);
    }

    public static Vector2 UnpackSaturated(float f)
    {
      const int precision = 4096;
      return new Vector2(Mathf.Floor(f / precision), Mathf.Repeat(f, precision)) / (precision - 1);
    }

    //-----------------------------------------------------------------------------
    // end: Vector2 between 0.0 and 1.0


    // normals
    // https://knarkowicz.wordpress.com/2014/04/16/octahedron-normal-vector-encoding/
    //-----------------------------------------------------------------------------

    public static Vector2 OctWrap(Vector2 v)
    {
      return
        (Vector2.one - new Vector2(Mathf.Abs(v.y), Mathf.Abs(v.x))) 
        * new Vector2(Mathf.Sign(v.x), Mathf.Sign(v.y));
    }

    public static float PackNormal(Vector3 n)
    {
      n /= (Mathf.Abs(n.x) + Mathf.Abs(n.y) + Mathf.Abs(n.z));
      Vector2 n2 = n.z >= 0.0f ? new Vector2(n.x, n.y) : OctWrap(new Vector2(n.x, n.y));
      n2 = n2 * 0.5f + 0.5f * Vector2.one;
      return PackSaturated(n2);
    }

    public static Vector3 UnpackNormal(float f)
    {
      Vector2 v = UnpackSaturated(f);
      v = v * 2.0f - Vector2.one;
      Vector3 n = new Vector3(v.x, v.y, 1.0f - Mathf.Abs(v.x) - Mathf.Abs(v.y));
      float t = Mathf.Clamp01(-n.z);
      n.x += n.x >= 0.0f ? -t : t;
      n.y += n.y >= 0.0f ? -t : t;
      return n.normalized;
    }

    //-----------------------------------------------------------------------------
    // end: normals


    // colors
    //-----------------------------------------------------------------------------

    public static uint PackRgb(Color color)
    {
      return 
          (((uint) (color.b * 255)) << 16) 
        | (((uint) (color.g * 255)) <<  8) 
        | (((uint) (color.r * 255)) <<  0);
    }

    public static Color UnpackRgb(uint i)
    {
      return
        new Color
        (
          ((i & 0x000000FF) >>  0) / 255.0f, 
          ((i & 0x0000FF00) >>  8) / 255.0f, 
          ((i & 0x00FF0000) >> 16) / 255.0f
        );
    }

    public static uint PackRgba(Color color)
    {
      return 
          (((uint) (color.a * 255)) << 24) 
        | (((uint) (color.b * 255)) << 16) 
        | (((uint) (color.g * 255)) <<  8) 
        | (((uint) (color.r * 255)) <<  0);
    }

    public static Color UnpackRgba(uint i)
    {
      return
        new Color
        (
          ((i & 0x000000FF) >>  0) / 255.0f, 
          ((i & 0x0000FF00) >>  8) / 255.0f, 
          ((i & 0x00FF0000) >> 16) / 255.0f, 
          ((i & 0xFF000000) >> 24) / 255.0f
        );
    }

    //-----------------------------------------------------------------------------
    // end: colors


    // bits
    //-----------------------------------------------------------------------------

    public static uint Pack8888(uint x, uint y, uint z, uint w)
    {
      return 
          ((x & 0xFF) << 24) 
        | ((y & 0xFF) << 16) 
        | ((z & 0xFF) <<  8) 
        | ((w & 0xFF) <<  0);
    }

    public static void Unpack8888(uint i, out uint x, out uint y, out uint z, out uint w)
    {
      x = (i >> 24) & 0xFF;
      y = (i >> 16) & 0xFF;
      z = (i >>  8) & 0xFF;
      w = (i >>  0) & 0xFF;
    }

    //-----------------------------------------------------------------------------
    // end: bits


    // hash
    //-----------------------------------------------------------------------------

    public static readonly int FnvDefaultBasis = unchecked((int) 2166136261);
    public static readonly int FnvPrime = 16777619;

    [StructLayout(LayoutKind.Explicit)]
    private struct IntFloat
    {
      [FieldOffset(0)]
      public int IntValue;
      [FieldOffset(0)]
      public float FloatValue;
    }
    private static int IntReinterpret(float f)
    {
      return (new IntFloat { FloatValue = f }).IntValue;
    }

    public static int HashConcat(int hash, int i)
    {
      return (hash ^ i) * FnvPrime;
    }

    public static int HashConcat(int hash, long i)
    {
      hash = HashConcat(hash, (int) (i & 0xFFFFFFFF));
      hash = HashConcat(hash, (int) (i >> 32));
      return hash;
    }

    public static int HashConcat(int hash, float f)
    {
      return HashConcat(hash, IntReinterpret(f));
    }

    public static int HashConcat(int hash, bool b)
    {
      return HashConcat(hash, b ? 1 : 0);
    }

    public static int HashConcat(int hash, params int [] ints)
    {
      foreach (int i in ints)
        hash = HashConcat(hash, i);
      return hash;
    }

    public static int HashConcat(int hash, params float [] floats)
    {
      foreach (float f in floats)
        hash = HashConcat(hash, f);
      return hash;
    }

    public static int HashConcat(int hash, Vector2 v)
    {
      return HashConcat(hash, v.x, v.y);
    }

    public static int HashConcat(int hash, Vector3 v)
    {
      return HashConcat(hash, v.x, v.y, v.z);
    }

    public static int HashConcat(int hash, Vector4 v)
    {
      return HashConcat(hash, v.x, v.y, v.z, v.w);
    }

    public static int HashConcat(int hash, Quaternion q)
    {
      return HashConcat(hash, q.x, q.y, q.z, q.w);
    }

    public static int HashConcat(int hash, Color c)
    {
      return HashConcat(hash, c.r, c.g, c.b, c.a);
    }

    public static int HashConcat(int hash, in MudMaterialBase m)
    {
      hash = HashConcat(hash, m.Color);
      hash = HashConcat(hash, m.Emission);
      hash = HashConcat(hash, m.Metallic);
      hash = HashConcat(hash, m.Smoothness);
      hash = HashConcat(hash, m.TextureIndex);
      hash = HashConcat(hash, m.SplatSize);
      hash = HashConcat(hash, m.BlendTightness);
      return hash;
    }

    public static int HashConcat(int hash, in MudSharedMaterialBase m)
    {
      hash = HashConcat(hash, m.Color);
      hash = HashConcat(hash, m.Emission);
      hash = HashConcat(hash, m.Metallic);
      hash = HashConcat(hash, m.Smoothness);
      hash = HashConcat(hash, m.TextureIndex);
      hash = HashConcat(hash, m.SplatSize);
      hash = HashConcat(hash, m.BlendTightness);
      return hash;
    }

    public static int HashConcat(int hash, in SdfBrushMaterial m)
    {
      hash = HashConcat(hash, m.Color);
      hash = HashConcat(hash, m.EmissionHash);
      hash = HashConcat(hash, m.MetallicSmoothnessSizeTightness);
      hash = HashConcat(hash, m.TextureWeight);
      return hash;
    }

    public static int Hash(int i)
    {
      return HashConcat(FnvDefaultBasis, i);
    }

    public static int Hash(long i)
    {
      return HashConcat(FnvDefaultBasis, i);
    }

    public static int Hash(float f)
    {
      return HashConcat(FnvDefaultBasis, f);
    }

    public static int Hash(bool b)
    {
      return HashConcat(FnvDefaultBasis, b);
    }

    public static int Hash(params int[] ints)
    {
      return HashConcat(FnvDefaultBasis, ints);
    }

    public static int Hash(params float[] floats)
    {
      return HashConcat(FnvDefaultBasis, floats);
    }

    public static int Hash(Vector2 v)
    {
      return HashConcat(FnvDefaultBasis, v);
    }

    public static int Hash(Vector3 v)
    {
      return HashConcat(FnvDefaultBasis, v);
    }

    public static int Hash(Vector4 v)
    {
      return HashConcat(FnvDefaultBasis, v);
    }

    public static int Hash(Quaternion q)
    {
      return HashConcat(FnvDefaultBasis, q);
    }

    public static int Hash(Color c)
    {
      return HashConcat(FnvDefaultBasis, c);
    }

    public static int Hash(in MudMaterialBase m)
    {
      return HashConcat(FnvDefaultBasis, m);
    }

    public static int Hash(in MudSharedMaterialBase m)
    {
      return HashConcat(FnvDefaultBasis, m);
    }

    public static int HashConcat(in SdfBrushMaterial m)
    {
      return HashConcat(FnvDefaultBasis, m);
    }

    //-----------------------------------------------------------------------------
    // end: hash
  }
}

