using System;
using UnityEngine;
using UnityEditor.Graphing;
using UnityEditor.ShaderGraph.Drawing.Controls;

namespace UnityEditor.ShaderGraph
{
    [Title("Math", "Matrix", "Matrix Construction")]
    class MatrixConstructionNode : AbstractMaterialNode, IGeneratesBodyCode, IGeneratesFunction
    {
        const string kInputSlotM0Name = "M0";
        const string kInputSlotM1Name = "M1";
        const string kInputSlotM2Name = "M2";
        const string kInputSlotM3Name = "M3";
        const string kOutput4x4SlotName = "4x4";
        const string kOutput3x3SlotName = "3x3";
        const string kOutput2x2SlotName = "2x2";

        public const int InputSlotM0Id = 0;
        public const int InputSlotM1Id = 1;
        public const int InputSlotM2Id = 2;
        public const int InputSlotM3Id = 3;
        public const int Output4x4SlotId = 4;
        public const int Output3x3SlotId = 5;
        public const int Output2x2SlotId = 6;

        public MatrixConstructionNode()
        {
            name = "Matrix Construction";
            UpdateNodeAfterDeserialization();
        }


        [SerializeField]
        MatrixAxis m_Axis;

        [EnumControl("")]
        MatrixAxis axis
        {
            get { return m_Axis; }
            set
            {
                if (m_Axis.Equals(value))
                    return;
                m_Axis = value;
                Dirty(ModificationScope.Graph);
            }
        }

        string GetFunctionName()
        {
            return $"Unity_MatrixConstruction_{axis}_{concretePrecision.ToShaderString()}";
        }

        public sealed override void UpdateNodeAfterDeserialization()
        {
            AddSlot(new Vector4MaterialSlot(InputSlotM0Id, kInputSlotM0Name, kInputSlotM0Name, SlotType.Input, Vector4.zero));
            AddSlot(new Vector4MaterialSlot(InputSlotM1Id, kInputSlotM1Name, kInputSlotM1Name, SlotType.Input, Vector4.zero));
            AddSlot(new Vector4MaterialSlot(InputSlotM2Id, kInputSlotM2Name, kInputSlotM2Name, SlotType.Input, Vector4.zero));
            AddSlot(new Vector4MaterialSlot(InputSlotM3Id, kInputSlotM3Name, kInputSlotM3Name, SlotType.Input, Vector4.zero));
            AddSlot(new Matrix4MaterialSlot(Output4x4SlotId, kOutput4x4SlotName, kOutput4x4SlotName, SlotType.Output));
            AddSlot(new Matrix3MaterialSlot(Output3x3SlotId, kOutput3x3SlotName, kOutput3x3SlotName, SlotType.Output));
            AddSlot(new Matrix2MaterialSlot(Output2x2SlotId, kOutput2x2SlotName, kOutput2x2SlotName, SlotType.Output));
            RemoveSlotsNameNotMatching(new int[] { InputSlotM0Id, InputSlotM1Id, InputSlotM2Id, InputSlotM3Id, Output4x4SlotId, Output3x3SlotId, Output2x2SlotId });
        }

        public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
        {
            var inputM0Value = GetSlotValue(InputSlotM0Id, generationMode);
            var inputM1Value = GetSlotValue(InputSlotM1Id, generationMode);
            var inputM2Value = GetSlotValue(InputSlotM2Id, generationMode);
            var inputM3Value = GetSlotValue(InputSlotM3Id, generationMode);

            sb.AppendLine("{0} {1};", FindOutputSlot<MaterialSlot>(Output4x4SlotId).concreteValueType.ToShaderString(), GetVariableNameForSlot(Output4x4SlotId));
            sb.AppendLine("{0} {1};", FindOutputSlot<MaterialSlot>(Output3x3SlotId).concreteValueType.ToShaderString(), GetVariableNameForSlot(Output3x3SlotId));
            sb.AppendLine("{0} {1};", FindOutputSlot<MaterialSlot>(Output2x2SlotId).concreteValueType.ToShaderString(), GetVariableNameForSlot(Output2x2SlotId));
            sb.AppendLine("{0}({1}, {2}, {3}, {4}, {5}, {6}, {7});",
                GetFunctionName(),
                inputM0Value,
                inputM1Value,
                inputM2Value,
                inputM3Value,
                GetVariableNameForSlot(Output4x4SlotId),
                GetVariableNameForSlot(Output3x3SlotId),
                GetVariableNameForSlot(Output2x2SlotId));
        }

        public void GenerateNodeFunction(FunctionRegistry registry, GenerationMode generationMode)
        {
            registry.ProvideFunction(GetFunctionName(), s =>
                {
                    s.AppendLine("void {0} ({1} M0, {1} M1, {1} M2, {1} M3, out {2} Out4x4, out {3} Out3x3, out {4} Out2x2)",
                        GetFunctionName(),
                        FindInputSlot<MaterialSlot>(InputSlotM0Id).concreteValueType.ToShaderString(),
                        FindOutputSlot<MaterialSlot>(Output4x4SlotId).concreteValueType.ToShaderString(),
                        FindOutputSlot<MaterialSlot>(Output3x3SlotId).concreteValueType.ToShaderString(),
                        FindOutputSlot<MaterialSlot>(Output2x2SlotId).concreteValueType.ToShaderString());
                    using (s.BlockScope())
                    {
                        switch (m_Axis)
                        {
                            case MatrixAxis.Column:
                                s.AppendLine("Out4x4 = $precision4x4(M0.x, M1.x, M2.x, M3.x, M0.y, M1.y, M2.y, M3.y, M0.z, M1.z, M2.z, M3.z, M0.w, M1.w, M2.w, M3.w);");
                                s.AppendLine("Out3x3 = $precision3x3(M0.x, M1.x, M2.x, M0.y, M1.y, M2.y, M0.z, M1.z, M2.z);");
                                s.AppendLine("Out2x2 = $precision2x2(M0.x, M1.x, M0.y, M1.y);");
                                break;
                            default:
                                s.AppendLine("Out4x4 = $precision4x4(M0.x, M0.y, M0.z, M0.w, M1.x, M1.y, M1.z, M1.w, M2.x, M2.y, M2.z, M2.w, M3.x, M3.y, M3.z, M3.w);");
                                s.AppendLine("Out3x3 = $precision3x3(M0.x, M0.y, M0.z, M1.x, M1.y, M1.z, M2.x, M2.y, M2.z);");
                                s.AppendLine("Out2x2 = $precision2x2(M0.x, M0.y, M1.x, M1.y);");
                                break;
                        }
                    }
                });
        }
    }
}
