using System;
using System.Diagnostics;

namespace Ryujinx.Graphics.Texture.Astc
{
    class AstcPixel
    {
        public short R { get; set; }
        public short G { get; set; }
        public short B { get; set; }
        public short A { get; set; }

        byte[] _bitDepth = new byte[4];

        public AstcPixel(short a, short r, short g, short b)
        {
            A = a;
            R = r;
            G = g;
            B = b;

            for (int i = 0; i < 4; i++)
                _bitDepth[i] = 8;
        }

        public void ClampByte()
        {
            R = Math.Min(Math.Max(R, (short)0), (short)255);
            G = Math.Min(Math.Max(G, (short)0), (short)255);
            B = Math.Min(Math.Max(B, (short)0), (short)255);
            A = Math.Min(Math.Max(A, (short)0), (short)255);
        }

        public short GetComponent(int index)
        {
            switch(index)
            {
                case 0: return A;
                case 1: return R;
                case 2: return G;
                case 3: return B;
            }

            return 0;
        }

        public void SetComponent(int index, int value)
        {
            switch (index)
            {
                case 0:
                    A = (short)value;
                    break;
                case 1:
                    R = (short)value;
                    break;
                case 2:
                    G = (short)value;
                    break;
                case 3:
                    B = (short)value;
                    break;
            }
        }

        public void ChangeBitDepth(byte[] depth)
        {
            for (int i = 0; i< 4; i++)
            {
                int value = ChangeBitDepth(GetComponent(i), _bitDepth[i], depth[i]);

                SetComponent(i, value);
                _bitDepth[i] = depth[i];
            }
        }

        short ChangeBitDepth(short value, byte oldDepth, byte newDepth)
        {
            Debug.Assert(newDepth <= 8);
            Debug.Assert(oldDepth <= 8);

            if (oldDepth == newDepth)
            {
                // Do nothing
                return value;
            }
            else if (oldDepth == 0 && newDepth != 0)
            {
                return (short)((1 << newDepth) - 1);
            }
            else if (newDepth > oldDepth)
            {
                return (short)BitArrayStream.Replicate(value, oldDepth, newDepth);
            }
            else
            {
                // oldDepth > newDepth
                if (newDepth == 0)
                {
                    return 0xFF;
                }
                else
                {
                    byte bitsWasted = (byte)(oldDepth - newDepth);
                    short tempValue = value;

                    tempValue = (short)((tempValue + (1 << (bitsWasted - 1))) >> bitsWasted);
                    tempValue = Math.Min(Math.Max((short)0, tempValue), (short)((1 << newDepth) - 1));

                    return (byte)(tempValue);
                }
            }
        }

        public int Pack()
        {
            AstcPixel newPixel   = new AstcPixel(A, R, G, B);
            byte[] eightBitDepth = { 8, 8, 8, 8 };

            newPixel.ChangeBitDepth(eightBitDepth);

            return (byte)newPixel.A << 24 |
                   (byte)newPixel.B << 16 |
                   (byte)newPixel.G << 8  |
                   (byte)newPixel.R << 0;
        }

        // Adds more precision to the blue channel as described
        // in C.2.14
        public static AstcPixel BlueContract(int a, int r, int g, int b)
        {
            return new AstcPixel((short)(a),
                                 (short)((r + b) >> 1),
                                 (short)((g + b) >> 1),
                                 (short)(b));
        }
    }
}