using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEditor;

public class TextureConverter : ScriptableWizard
{

    public Material     TargetMaterial;

    [Header("Packing")]
    public TexturePackingTemplate template;
    [Space(20)]
    public Shader TargetShader;
    public PackingTargetLayout[] PackingArray;
    public ReassignTargetLayout[] ReassigningArray;

    TexturePackingTemplate PrevTemplate;



    [MenuItem("CONTEXT/Material/Convert Texture")]
    public static void CreateWizard(MenuCommand menuCommand)
    {
        var wiz = ScriptableWizard.DisplayWizard<TextureConverter>("Convert Textures",  "Convert & Close", "Convert Textures");
        var contexMat = menuCommand.context as Material;
        wiz.TargetMaterial = contexMat;       //Set material from menu context
        wiz.template = GrabDefault(contexMat.shader);   //Get default template
        wiz.LoadTemplate();           //Intial Load
    }

    public static TexturePackingTemplate GrabDefault(Shader shader)
    {
        string filename = "Template Settings";
        string path = "Assets/Settings/" + filename+".asset";

        AssetDatabase.SaveAssets();
        ConverterSettings templateSettings =  AssetDatabase.LoadAssetAtPath<ConverterSettings>(path);
        if (templateSettings == null) //Generate intial default settings
        {
            Debug.Log("No settings file found. Generating...");
            //FileUtil.CopyFileOrDirectory("Packages/com.unity.render-pipelines.universal/Editor/Converter/TextureConvert/DefaultTemplateSettings.asset", path); //Copys even the name :/
            ConverterSettings defaultset =  AssetDatabase.LoadAssetAtPath<ConverterSettings>("Packages/com.unity.render-pipelines.universal/Editor/Converter/TextureConvert/DefaultTemplateSettings.asset");
            ConverterSettings parseset = ScriptableObject.CreateInstance<ConverterSettings>();
            parseset.ShaderDefaultTemplate = defaultset.ShaderDefaultTemplate;//Copy data rather than ref
            AssetDatabase.CreateAsset(parseset, path);
            AssetDatabase.ImportAsset(path);
            templateSettings = AssetDatabase.LoadAssetAtPath<ConverterSettings>(path);

            if (templateSettings != null) Debug.Log("Created template settings file inside the settings folder");
        } 

        if (templateSettings.ShaderDefaultTemplate.Length < 1) return null;
        int tempint = WhereOnArray(templateSettings, shader);
        if (tempint < 0) return null;

        return  templateSettings.ShaderDefaultTemplate[tempint].DefaultTemplate ;
    }



    static int WhereOnArray(ConverterSettings templateSettings, Shader shader)
    {
        for (int i = 0; i < templateSettings.ShaderDefaultTemplate.Length; i++) //Extracting shaders to compare
        {
            if (templateSettings.ShaderDefaultTemplate[i].shader == shader)
            {
                return i;
            }
        }
        return -1;
    }

    void OnWizardUpdate()
    {
        if (PrevTemplate != template) //Checking currently loaded template and reloading if different
        {
            LoadTemplate();
        } 
    }

    void LoadTemplate()
    {
        if (template == null) return;

        TargetShader = template.TargetShader;
        if (template.Packing.Length != 0) PackingArray = new PackingTargetLayout[template.Packing.Length];
        if (template.Reassigning.Length != 0) ReassigningArray = new ReassignTargetLayout[template.Reassigning.Length];
        for (int i = 0; i < template.Packing.Length; i++)
        {
            PackingArray[i] = ParsePackingTemplate(template.Packing[i]);
        }
        for (int i = 0; i < template.Reassigning.Length; i++)
        {
            ReassigningArray[i] = ParseReassiningTemplate(template.Reassigning[i]);
        }

        PrevTemplate = template;

    }

    private void OnWizardOtherButton() //Convert
    {
        TextureConvert();
    }

    public void OnWizardCreate() //Convert and close
    {
        TextureConvert();
    }

    public PackingTargetLayout ParsePackingTemplate(PackingLayout layout) //Read template and load any connected textures
    {
        PackingTargetLayout targetLayout = new PackingTargetLayout();

        var props = TargetMaterial.GetTexturePropertyNames();

        targetLayout.PropertyName = layout.PropertyName;
        if (ArrayUtility.Contains(props, layout.RedInputProperty))
        {
            targetLayout.RedInputTexture = TargetMaterial.GetTexture(layout.RedInputProperty) as Texture2D;
        }
            targetLayout.RedOptions = layout.RedOptions;
        if (ArrayUtility.Contains(props, layout.GreenInputProperty)) targetLayout.GreenInputTexture = TargetMaterial.GetTexture(layout.GreenInputProperty) as Texture2D;
        targetLayout.GreenOptions = layout.GreenOptions;
        if (ArrayUtility.Contains(props, layout.BlueInputProperty)) targetLayout.BlueInputTexture = TargetMaterial.GetTexture(layout.BlueInputProperty) as Texture2D;
        targetLayout.BlueOptions = layout.BlueOptions;
        if (ArrayUtility.Contains(props, layout.AlphaInputProperty)) targetLayout.AlphaInputTexture = TargetMaterial.GetTexture(layout.AlphaInputProperty) as Texture2D;
        targetLayout.AlphaOptions = layout.AlphaOptions;

        targetLayout.packingOptions = layout.packingOptions;

        return targetLayout;
    }

    public ReassignTargetLayout ParseReassiningTemplate(ReassignLayout layout) //Read template and load any connected textures
    {
        ReassignTargetLayout targetLayout = new ReassignTargetLayout();

        var props = TargetMaterial.GetTexturePropertyNames();

        targetLayout.PropertyName = layout.PropertyName;
        if (ArrayUtility.Contains(props, layout.InputPropertyName)) targetLayout.InputTexture = TargetMaterial.GetTexture(layout.InputPropertyName) as Texture2D;
        return targetLayout;
    }


    string SavedPath;

    public void TextureConvert()
    {
        if (PackingArray.Length < 1 && ReassigningArray.Length < 1) return;


        //SHADER CONVERSION//
        //Set textures and blit using a custom shader

        for (int i = 0; i < PackingArray.Length; i++) //Convert Textures
        {
            SavedPath = null;

            UncompressBeforeTask(PackingArray[i]);

            Vector2Int maxSize;

            //Be a bit smarter about this
            maxSize = PackingArray[i].RedInputTexture.GetImageSize();
            maxSize = Vector2Int.Max(maxSize, PackingArray[i].GreenInputTexture.GetImageSize() );
            maxSize = Vector2Int.Max(maxSize, PackingArray[i].BlueInputTexture.GetImageSize()  );
            maxSize = Vector2Int.Max(maxSize, PackingArray[i].AlphaInputTexture.GetImageSize() );


            //Read Render texture and convert to texture2D

            var packedtexture = PackToTexture(PackingArray[i], maxSize).ConvertToTexture2D();

            //////

            //Save as new Texture

            if (PackingArray[i].packingOptions.OverrideTexture == OverrideSlot.New)
            {
                SaveNewTexture(packedtexture, PackingArray[i]);
            }

            else
            {
                string OverridePath;

                switch (PackingArray[i].packingOptions.OverrideTexture)
                {

                    case OverrideSlot.Red:
                        OverridePath = AssetDatabase.GetAssetPath(PackingArray[i].RedInputTexture);
                        break;
                    case OverrideSlot.Green:
                        OverridePath = AssetDatabase.GetAssetPath(PackingArray[i].GreenInputTexture);
                        break;
                    case OverrideSlot.Blue:
                        OverridePath = AssetDatabase.GetAssetPath(PackingArray[i].BlueInputTexture);
                        break;
                    case OverrideSlot.Alpha:
                        OverridePath = AssetDatabase.GetAssetPath(PackingArray[i].AlphaInputTexture);
                        break;
                    default:
                        OverridePath = null;
                        break;
                }

                if (OverridePath == null || OverridePath.Length == 0)
                {
                    Debug.Log("Texture override not valid. Making new texture instead.");
                    SaveNewTexture(packedtexture, PackingArray[i]);
                    break;
                }

                object extractedext;

                if (!System.Enum.TryParse(typeof(TextureFileExtension), Path.GetExtension(OverridePath).Substring(1), true, out extractedext)) {
                    Debug.Log("Texture type " + Path.GetExtension(OverridePath).Substring(1) + " is not supported. Making new texture instead.");
                    SaveNewTexture(packedtexture, PackingArray[i]);
                    break;
                }
                else
                {
                    TextureFileExtension ext = (TextureFileExtension)extractedext;

                    byte[] pixels = packedtexture.EncodeTexture(ext);

                    File.WriteAllBytes(OverridePath, pixels);
                    SavedPath = OverridePath;

                    AssetDatabase.Refresh();

                    DeleteOldTextures(PackingArray[i]);

                    ResetTextureCompressions(PackingArray[i]);

                    AssignProperty(PackingArray[i]);
                }
            }
        }

        for (int i = 0; i < ReassigningArray.Length; i++) //Reassign Textures
        {
            TargetMaterial.SetTexture(ReassigningArray[i].PropertyName, ReassigningArray[i].InputTexture);
        }

        if (TargetShader!=null) TargetMaterial.shader = TargetShader;

    }

    TextureImporterCompression cRed;
    TextureImporterCompression cGreen;
    TextureImporterCompression cBlue;
    TextureImporterCompression cAlpha;


    void UncompressBeforeTask(PackingTargetLayout targetLayout)
    {
        if (targetLayout.packingOptions.UncompressBeforeTask) return;

        cRed=UncompressFile(targetLayout.RedInputTexture);
        cGreen=UncompressFile(targetLayout.GreenInputTexture);
        cBlue=UncompressFile(targetLayout.BlueInputTexture);
        cAlpha=UncompressFile(targetLayout.AlphaInputTexture);
    }

    TextureImporterCompression UncompressFile(Texture2D texture)
    {
        TextureImporterCompression compression;

        var path = AssetDatabase.GetAssetPath(texture);
        if (path == null || path.Length == 0) return TextureImporterCompression.Uncompressed; 
        TextureImporter ti = (TextureImporter) TextureImporter.GetAtPath( path);
        compression = ti.textureCompression; //Storing compression
        ti.textureCompression = TextureImporterCompression.Uncompressed;
        ti.maxTextureSize = 8192;
        ti.SaveAndReimport();
        AssetDatabase.Refresh();
        return compression;
    }

    void AssignProperty(PackingTargetLayout packingTargetLayout)
    {
        AssetDatabase.Refresh();
        //Load new asset and set it to the correct slot
        TextureImporter ti = (TextureImporter)TextureImporter.GetAtPath(SavedPath);
        if (!packingTargetLayout.packingOptions.EnableAlphaChannel) ti.alphaSource = TextureImporterAlphaSource.None;
        ti.textureCompression = packingTargetLayout.packingOptions.textureCompression;
        ti.sRGBTexture = packingTargetLayout.packingOptions.sRGB;
        ti.SaveAndReimport();

        TargetMaterial.SetTexture(packingTargetLayout.PropertyName, AssetDatabase.LoadAssetAtPath<Texture2D>(SavedPath));
        AssetDatabase.Refresh();
    }

    void ResetTextureCompressions(PackingTargetLayout targetLayout)
    {
        if (targetLayout.packingOptions.UncompressBeforeTask) return;

        ResetCompression(targetLayout.RedInputTexture, cRed);
        ResetCompression(targetLayout.GreenInputTexture, cGreen);
        ResetCompression(targetLayout.BlueInputTexture, cBlue);
        ResetCompression(targetLayout.AlphaInputTexture, cAlpha);
    }

    void ResetCompression(Texture2D texture, TextureImporterCompression compression)
    {
        var path = AssetDatabase.GetAssetPath(texture);
        if (path == null || path.Length == 0) return;
        TextureImporter ti = (TextureImporter)TextureImporter.GetAtPath(path);
        ti.textureCompression = compression;
        ti.SaveAndReimport();
        AssetDatabase.Refresh();
    }

    void DeleteOldTextures(PackingTargetLayout targetLayout)
    {
        if (targetLayout.packingOptions.DeleteSource == false) return;

        deleteTexture(targetLayout.RedInputTexture);
        deleteTexture(targetLayout.GreenInputTexture);
        deleteTexture(targetLayout.BlueInputTexture);
        deleteTexture(targetLayout.AlphaInputTexture);

        AssetDatabase.Refresh();
    }

    void deleteTexture(Texture2D texture)
    {
        var path = AssetDatabase.GetAssetPath(texture);
        if (path == null || path.Length == 0 || SavedPath == path)  return;
        File.Delete(path);
        File.Delete(path+ ".meta");
        Debug.Log("Deleted " + path);
        AssetDatabase.Refresh();
    }

    public void SaveNewTexture(Texture2D packedtexture, PackingTargetLayout packingTargetLayout)
    {
        //    string fullPath = Path.GetDirectoryName(path) + "/" + Path.GetFileNameWithoutExtension(path) + "_MAS.png";

        string caughtFilePath = GetPathFromInputs(packingTargetLayout);

        //Path.GetDirectoryName(caughtFilePath);

        string UserVerifiedPath = EditorUtility.SaveFilePanel("Save Texture",
            Path.GetDirectoryName(caughtFilePath),
            Path.GetFileNameWithoutExtension(caughtFilePath) + packingTargetLayout.packingOptions.Suffix,
            packingTargetLayout.packingOptions.textureFileExtension.ToString());

        if (UserVerifiedPath == null || UserVerifiedPath.Length == 0) return;

        byte[] pixels = packedtexture.EncodeTexture(packingTargetLayout.packingOptions.textureFileExtension);
        //  File.WriteAllBytes(Application.dataPath + "/../" + fullPath, pixels);
        File.WriteAllBytes(UserVerifiedPath, pixels);

        string relativepath;
        if (UserVerifiedPath.StartsWith(Application.dataPath)) relativepath = "Assets" + UserVerifiedPath.Substring(Application.dataPath.Length);
        else relativepath = UserVerifiedPath;

        SavedPath = relativepath;
        Debug.Log("Saved texture at " + UserVerifiedPath);
        AssetDatabase.Refresh();
        DeleteOldTextures(packingTargetLayout);
        AssignProperty(packingTargetLayout);
        ResetTextureCompressions(packingTargetLayout);

    }

    public string GetPathFromInputs(PackingTargetLayout targetLayout)
    {
        if (targetLayout.RedInputTexture != null) return AssetDatabase.GetAssetPath(targetLayout.RedInputTexture);
        else if (targetLayout.GreenInputTexture != null) return AssetDatabase.GetAssetPath(targetLayout.GreenInputTexture);
        else if (targetLayout.BlueInputTexture != null) return AssetDatabase.GetAssetPath(targetLayout.BlueInputTexture);
        else if (targetLayout.AlphaInputTexture != null) return AssetDatabase.GetAssetPath(targetLayout.AlphaInputTexture);
        else return "/Asset/";

    }


    public RenderTexture PackToTexture(PackingTargetLayout targetLayout, Vector2Int Resolution )
    {
        //RenderTextureDescriptor descriptor = new RenderTextureDescriptor();
        //descriptor.width = (int)Resolution.x;
        //descriptor.height = (int)Resolution.y;
        //descriptor.colorFormat = RenderTextureFormat.ARGBHalf;
        //descriptor.graphicsFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat;
        //descriptor.sRGB = true;
        //descriptor.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;
        //descriptor.volumeDepth = 1;
        //descriptor.msaaSamples = 1;
        //RenderTexture render = new RenderTexture(descriptor);

         RenderTexture render = new RenderTexture((int)Resolution.x, (int)Resolution.y, 0, RenderTextureFormat.Default);


        render.dimension = UnityEngine.Rendering.TextureDimension.Tex2D;
        render.enableRandomWrite = true;
        render.wrapMode = TextureWrapMode.Clamp;
        render.Create();

       // Debug.Log(render.sRGB);
        ComputeShader Packer = AssetDatabase.LoadAssetAtPath<ComputeShader>("Packages/com.unity.render-pipelines.universal/Editor/Converter/TextureConvert/TexturePacker.compute"); //Todo: Fix build error. Find better way to load?

        int kernelIndex = Packer.FindKernel("CSMain");

        Packer.SetTexture(kernelIndex, "InputTextureRed", GrabChannelTexture(targetLayout, RGBA.Red) ) ;
        Packer.SetTexture(kernelIndex, "InputTextureGreen", GrabChannelTexture(targetLayout, RGBA.Green) );
        Packer.SetTexture(kernelIndex, "InputTextureBlue", GrabChannelTexture(targetLayout, RGBA.Blue) );
        Packer.SetTexture(kernelIndex, "InputTextureAlpha", GrabChannelTexture(targetLayout, RGBA.Alpha) ) ;

        Packer.SetBool("invertRed", targetLayout.RedOptions.invert);
        Packer.SetBool("invertGreen", targetLayout.GreenOptions.invert);
        Packer.SetBool("invertBlue", targetLayout.BlueOptions.invert);
        Packer.SetBool("invertAlpha", targetLayout.AlphaOptions.invert);

        Packer.SetInt("inputChannelRed", ChannelToInt(targetLayout.RedOptions.InputChannel));
        Packer.SetInt("inputChannelGreen", ChannelToInt(targetLayout.GreenOptions.InputChannel));
        Packer.SetInt("inputChannelBlue", ChannelToInt(targetLayout.BlueOptions.InputChannel));
        Packer.SetInt("inputChannelAlpha", ChannelToInt(targetLayout.AlphaOptions.InputChannel));

        //Packer.SetBool("RedSRGB", ChannelToInt(targetLayout.RedOptions.InputChannel));
        //Packer.SetBool("GreenSRGB", ChannelToInt(targetLayout.GreenOptions.InputChannel));
        //Packer.SetBool("BlueSRGB", ChannelToInt(targetLayout.BlueOptions.InputChannel));
        //Packer.SetBool("AlphaSRGB", ChannelToInt(targetLayout.BlueOptions.InputChannel));

        Packer.SetTexture(kernelIndex, "Result", render);

        Packer.Dispatch(kernelIndex, (int)Resolution.x, (int)Resolution.y, 1);

        return render;
    }

    Texture2D GrabChannelTexture(PackingTargetLayout targetLayout, RGBA rgba )
    {
        switch (rgba)
        {
            case RGBA.Red:
                if (targetLayout.RedInputTexture != null) return targetLayout.RedInputTexture;
                else return GrabDefault(targetLayout.RedOptions.defaultColor);
            case RGBA.Green:
                if (targetLayout.GreenInputTexture != null) return targetLayout.GreenInputTexture; 
                else return GrabDefault(targetLayout.GreenOptions.defaultColor);
            case RGBA.Blue:
                if (targetLayout.BlueInputTexture != null) return targetLayout.BlueInputTexture;
                 else return GrabDefault(targetLayout.BlueOptions.defaultColor);
            case RGBA.Alpha:
                if (targetLayout.AlphaInputTexture != null) return targetLayout.AlphaInputTexture;
                 else return GrabDefault(targetLayout.AlphaOptions.defaultColor);
        }
        return Texture2D.whiteTexture;
    }

    Texture2D GrabDefault(DefaultColor defaultColor)
    {
        switch (defaultColor)
        {
            case DefaultColor.Black:
                return Texture2D.blackTexture;
            case DefaultColor.White:
                return Texture2D.whiteTexture;
            case DefaultColor.Gray:
                return Texture2D.grayTexture;
            case DefaultColor.LinearGray:
                return Texture2D.linearGrayTexture;
            default:
                return Texture2D.blackTexture;
        }
    }

    int ChannelToInt(RGBA rgba)
    {
        switch (rgba)
        {
            case RGBA.Red:
                return 0;
            case RGBA.Green:
                return 1;
            case RGBA.Blue:
                return 2;
            case RGBA.Alpha:
                return 3;
            default:
                return -1;
        }
    }
}