using Ryujinx.Graphics.Shader.IntermediateRepresentation;
using Ryujinx.Graphics.Shader.Translation;

using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;

namespace Ryujinx.Graphics.Shader.Instructions
{
    static class Lop3Expression
    {
        public static Operand GetFromTruthTable(EmitterContext context, Operand srcA, Operand srcB, Operand srcC, int imm)
        {
            Operand expr = null;

            // Handle some simple cases, or cases where
            // the KMap would yield poor results (like XORs).
            if (imm == 0x96 || imm == 0x69)
            {
                // XOR (0x96) and XNOR (0x69).
                if (imm == 0x69)
                {
                    srcA = context.BitwiseNot(srcA);
                }

                expr = context.BitwiseExclusiveOr(srcA, srcB);
                expr = context.BitwiseExclusiveOr(expr, srcC);

                return expr;
            }
            else if (imm == 0)
            {
                // Always false.
                return Const(IrConsts.False);
            }
            else if (imm == 0xff)
            {
                // Always true.
                return Const(IrConsts.True);
            }

            int map;

            // Encode into gray code.
            map = ((imm >> 0) & 1) << 0;
            map |= ((imm >> 1) & 1) << 4;
            map |= ((imm >> 2) & 1) << 1;
            map |= ((imm >> 3) & 1) << 5;
            map |= ((imm >> 4) & 1) << 3;
            map |= ((imm >> 5) & 1) << 7;
            map |= ((imm >> 6) & 1) << 2;
            map |= ((imm >> 7) & 1) << 6;

            // Solve KMap, get sum of products.
            int visited = 0;

            for (int index = 0; index < 8 && visited != 0xff; index++)
            {
                if ((map & (1 << index)) == 0)
                {
                    continue;
                }

                int mask = 0;

                for (int mSize = 4; mSize != 0; mSize >>= 1)
                {
                    mask = RotateLeft4((1 << mSize) - 1, index & 3) << (index & 4);

                    if ((map & mask) == mask)
                    {
                        break;
                    }
                }

                // The mask should wrap, if we are on the high row, shift to low etc.
                int mask2 = (index & 4) != 0 ? mask >> 4 : mask << 4;

                if ((map & mask2) == mask2)
                {
                    mask |= mask2;
                }

                if ((mask & visited) == mask)
                {
                    continue;
                }

                bool notA = (mask & 0x33) != 0;
                bool notB = (mask & 0x99) != 0;
                bool notC = (mask & 0x0f) != 0;

                bool aChanges = (mask & 0xcc) != 0 && notA;
                bool bChanges = (mask & 0x66) != 0 && notB;
                bool cChanges = (mask & 0xf0) != 0 && notC;

                Operand localExpr = null;

                void And(Operand source)
                {
                    if (localExpr != null)
                    {
                        localExpr = context.BitwiseAnd(localExpr, source);
                    }
                    else
                    {
                        localExpr = source;
                    }
                }

                if (!aChanges)
                {
                    And(context.BitwiseNot(srcA, notA));
                }

                if (!bChanges)
                {
                    And(context.BitwiseNot(srcB, notB));
                }

                if (!cChanges)
                {
                    And(context.BitwiseNot(srcC, notC));
                }

                if (expr != null)
                {
                    expr = context.BitwiseOr(expr, localExpr);
                }
                else
                {
                    expr = localExpr;
                }

                visited |= mask;
            }

            return expr;
        }

        private static int RotateLeft4(int value, int shift)
        {
            return ((value << shift) | (value >> (4 - shift))) & 0xf;
        }
    }
}