/// <summary>
/// Shader Control - (C) Copyright 2016-2022 Ramiro Oliva (Kronnect)
/// </summary>
/// 
using UnityEngine;
using UnityEditor;
using System;
using System.IO;
using System.Collections.Generic;
using System.Text;

namespace ShaderControl {

    public partial class SCWindow : EditorWindow {

        
        void ScanShaderNonGraph(SCShader shader) {

            // Reads shader
            string[] shaderLines = File.ReadAllLines(shader.path);
            string[] separator = new string[] { " " };
            SCShaderPass currentPass = new SCShaderPass();
            SCShaderPass basePass = null;
            int pragmaControl = 0;
            int pass = -1;
            bool blockComment = false;
            SCKeywordLine keywordLine = new SCKeywordLine();
            for (int k = 0; k < shaderLines.Length; k++) {
                string line = shaderLines[k].Trim();
                if (line.Length == 0)
                    continue;

                int lineCommentIndex = line.IndexOf("//");
                int blocCommentIndex = line.IndexOf("/*");
                int endCommentIndex = line.IndexOf("*/");
                if (blocCommentIndex > 0 && (lineCommentIndex > blocCommentIndex || lineCommentIndex < 0)) {
                    blockComment = true;
                }
                if (endCommentIndex > blocCommentIndex && (lineCommentIndex > endCommentIndex || lineCommentIndex < 0)) {
                    blockComment = false;
                }
                if (blockComment)
                    continue;

                string lineUPPER = line.ToUpper();
                if (lineUPPER.Equals("PASS") || lineUPPER.StartsWith("PASS ")) {
                    if (pass >= 0) {
                        currentPass.pass = pass;
                        if (basePass != null)
                            currentPass.Add(basePass.keywordLines);
                        shader.Add(currentPass);
                    } else if (currentPass.keywordCount > 0) {
                        basePass = currentPass;
                    }
                    currentPass = new SCShaderPass();
                    pass++;
                    continue;
                }
                int j = line.IndexOf(PRAGMA_COMMENT_MARK);
                if (j >= 0) {
                    pragmaControl = 1;
                } else {
                    j = line.IndexOf(PRAGMA_DISABLED_MARK);
                    if (j >= 0)
                        pragmaControl = 3;
                }
                if (lineCommentIndex == 0 && pragmaControl != 1 && pragmaControl != 3) {
                    continue; // do not process lines commented by user
                }

                PragmaType pragmaType = PragmaType.Unknown;
                int offset = 0;
                j = line.IndexOf(SCKeywordLine.PRAGMA_MULTICOMPILE_GLOBAL);
                if (j >= 0) {
                    pragmaType = PragmaType.MultiCompileGlobal;
                    offset = SCKeywordLine.PRAGMA_MULTICOMPILE_GLOBAL.Length;
                } else {
                    j = line.IndexOf(SCKeywordLine.PRAGMA_FEATURE_GLOBAL);
                    if (j >= 0) {
                        pragmaType = PragmaType.FeatureGlobal;
                        offset = SCKeywordLine.PRAGMA_FEATURE_GLOBAL.Length;
                    } else {
                        j = line.IndexOf(SCKeywordLine.PRAGMA_MULTICOMPILE_LOCAL);
                        if (j >= 0) {
                            pragmaType = PragmaType.MultiCompileLocal;
                            offset = SCKeywordLine.PRAGMA_MULTICOMPILE_LOCAL.Length;
                        } else {
                            j = line.IndexOf(SCKeywordLine.PRAGMA_FEATURE_LOCAL);
                            if (j >= 0) {
                                pragmaType = PragmaType.FeatureLocal;
                                offset = SCKeywordLine.PRAGMA_FEATURE_LOCAL.Length;
                            }
                        }
                    }
                }
                if (j >= 0) {
                    if (pragmaControl != 2) {
                        keywordLine = new SCKeywordLine();
                    }
                    keywordLine.pragmaType = pragmaType;
                    // exclude potential comments inside the #pragma line
                    int lastStringPos = line.IndexOf("//", j + offset);
                    if (lastStringPos < 0) {
                        lastStringPos = line.Length;
                    }
                    int length = lastStringPos - j - offset;
                    string[] kk = line.Substring(j + offset, length).Split(separator, StringSplitOptions.RemoveEmptyEntries);
                    // Sanitize keywords
                    for (int i = 0; i < kk.Length; i++) {
                        kk[i] = kk[i].Trim();
                    }
                    // Act on keywords
                    switch (pragmaControl) {
                        case 1: // Edited by Shader Control line
                            shader.editedByShaderControl = true;
                            // Add original keywords to current line
                            for (int s = 0; s < kk.Length; s++) {
                                keywordLine.Add(shader.GetKeyword(kk[s]), k);
                            }
                            pragmaControl = 2;
                            break;
                        case 2:
                            // check enabled keywords
                            keywordLine.DisableKeywords();
                            for (int s = 0; s < kk.Length; s++) {
                                SCKeyword keyword = keywordLine.GetKeyword(kk[s]);
                                if (keyword != null)
                                    keyword.enabled = true;
                            }
                            currentPass.Add(keywordLine);
                            pragmaControl = 0;
                            break;
                        case 3: // disabled by Shader Control line
                            shader.editedByShaderControl = true;
                            // Add original keywords to current line
                            for (int s = 0; s < kk.Length; s++) {
                                SCKeyword keyword = shader.GetKeyword(kk[s]);
                                keyword.enabled = false;
                                keywordLine.Add(keyword, k);
                            }
                            currentPass.Add(keywordLine);
                            pragmaControl = 0;
                            break;
                        case 0:
                            // Add keywords to current line
                            for (int s = 0; s < kk.Length; s++) {
                                keywordLine.Add(shader.GetKeyword(kk[s]), k);
                            }
                            currentPass.Add(keywordLine);
                            break;
                    }
                }
            }
            currentPass.pass = Mathf.Max(pass, 0);
            if (basePass != null) {
                currentPass.Add(basePass.keywordLines);
            }
            shader.Add(currentPass);
            shader.UpdateVariantCount();
        }


        void UpdateShaderNonGraph(SCShader shader) {
            // Reads and updates shader from disk
            string[] shaderLines = File.ReadAllLines(shader.path);
            string[] separator = new string[] { " " };
            StringBuilder sb = new StringBuilder();
            int pragmaControl = 0;
            shader.editedByShaderControl = false;
            SCKeywordLine keywordLine = new SCKeywordLine();
            bool blockComment = false;
            for (int k = 0; k < shaderLines.Length; k++) {

                int lineCommentIndex = shaderLines[k].IndexOf("//");
                int blocCommentIndex = shaderLines[k].IndexOf("/*");
                int endCommentIndex = shaderLines[k].IndexOf("*/");
                if (blocCommentIndex > 0 && (lineCommentIndex > blocCommentIndex || lineCommentIndex < 0)) {
                    blockComment = true;
                }
                if (endCommentIndex > blocCommentIndex && (lineCommentIndex > endCommentIndex || lineCommentIndex < 0)) {
                    blockComment = false;
                }

                int j = -1;
                PragmaType pragmaType = PragmaType.Unknown;
                if (!blockComment) {
                    j = shaderLines[k].IndexOf(PRAGMA_COMMENT_MARK);
                    if (j >= 0) {
                        pragmaControl = 1;
                    }
                    j = shaderLines[k].IndexOf(SCKeywordLine.PRAGMA_MULTICOMPILE_GLOBAL);
                    if (j >= 0) {
                        pragmaType = PragmaType.MultiCompileGlobal;
                    } else {
                        j = shaderLines[k].IndexOf(SCKeywordLine.PRAGMA_FEATURE_GLOBAL);
                        if (j >= 0) {
                            pragmaType = PragmaType.FeatureGlobal;
                        } else {
                            j = shaderLines[k].IndexOf(SCKeywordLine.PRAGMA_MULTICOMPILE_LOCAL);
                            if (j >= 0) {
                                pragmaType = PragmaType.MultiCompileLocal;
                            } else {
                                j = shaderLines[k].IndexOf(SCKeywordLine.PRAGMA_FEATURE_LOCAL);
                                if (j >= 0) {
                                    pragmaType = PragmaType.FeatureLocal;
                                }

                            }
                        }
                    }
                    if (pragmaControl != 1 && lineCommentIndex == 0 && shaderLines[k].IndexOf(PRAGMA_DISABLED_MARK) < 0) {
                        // do not process a commented line
                        j = -1;
                    }
                }
                if (j >= 0) {
                    if (pragmaControl != 2) {
                        keywordLine.Clear();
                    }
                    keywordLine.pragmaType = pragmaType;
                    j = shaderLines[k].IndexOf(' ', j + 20) + 1; // first space after pragma declaration
                    if (j >= shaderLines[k].Length) continue;
                    // exclude potential comments inside the #pragma line
                    int lastStringPos = shaderLines[k].IndexOf("//", j);
                    if (lastStringPos < 0) {
                        lastStringPos = shaderLines[k].Length;
                    }
                    int length = lastStringPos - j;
                    string[] kk = shaderLines[k].Substring(j, length).Split(separator, StringSplitOptions.RemoveEmptyEntries);
                    // Sanitize keywords
                    for (int i = 0; i < kk.Length; i++) {
                        kk[i] = kk[i].Trim();
                    }
                    // Act on keywords
                    switch (pragmaControl) {
                        case 1:
                            // Read original keywords
                            for (int s = 0; s < kk.Length; s++) {
                                SCKeyword keyword = shader.GetKeyword(kk[s]);
                                keywordLine.Add(keyword);
                            }
                            pragmaControl = 2;
                            break;
                        case 0:
                        case 2:
                            if (pragmaControl == 0) {
                                for (int s = 0; s < kk.Length; s++) {
                                    SCKeyword keyword = shader.GetKeyword(kk[s]);
                                    keywordLine.Add(keyword);
                                }
                            }
                            int kCount = keywordLine.keywordCount;
                            int kEnabledCount = keywordLine.keywordsEnabledCount;
                            if (kEnabledCount < kCount) {
                                // write original keywords
                                if (kEnabledCount == 0) {
                                    sb.Append(PRAGMA_DISABLED_MARK);
                                } else {
                                    sb.Append(PRAGMA_COMMENT_MARK);
                                }
                                shader.editedByShaderControl = true;
                                sb.Append(keywordLine.GetPragma());
                                if (keywordLine.hasUnderscoreVariant)
                                    sb.Append(PRAGMA_UNDERSCORE);
                                for (int s = 0; s < kCount; s++) {
                                    SCKeyword keyword = keywordLine.keywords[s];
                                    sb.Append(keyword.name);
                                    if (s < kCount - 1)
                                        sb.Append(" ");
                                }
                                sb.AppendLine();
                            }

                            if (kEnabledCount > 0) {
                                // Write actual keywords
                                sb.Append(keywordLine.GetPragma());
                                if (keywordLine.hasUnderscoreVariant)
                                    sb.Append(PRAGMA_UNDERSCORE);
                                for (int s = 0; s < kCount; s++) {
                                    SCKeyword keyword = keywordLine.keywords[s];
                                    if (keyword.enabled) {
                                        sb.Append(keyword.name);
                                        if (s < kCount - 1)
                                            sb.Append(" ");
                                    }
                                }
                                sb.AppendLine();
                            }
                            pragmaControl = 0;
                            break;
                    }
                } else {
                    sb.AppendLine(shaderLines[k]);
                }
            }

            // Writes modified shader
            File.WriteAllText(shader.path, sb.ToString());
            AssetDatabase.Refresh();
        }

        void ConvertToLocalNonGraph(SCKeyword keyword, SCShader shader) {

            string path = shader.path;
            if (!File.Exists(path)) return;
            string[] lines = File.ReadAllLines(path);
            bool changed = false;
            for (int k = 0; k < lines.Length; k++) {
                // Just convert to local shader_features for now since multi_compile global keywords can be nabled using the Shader global API
                if (lines[k].IndexOf(SCKeywordLine.PRAGMA_FEATURE_GLOBAL, StringComparison.InvariantCultureIgnoreCase) >= 0 && lines[k].IndexOf(keyword.name, StringComparison.InvariantCultureIgnoreCase) >= 0) {
                    lines[k] = lines[k].Replace(SCKeywordLine.PRAGMA_FEATURE_GLOBAL, SCKeywordLine.PRAGMA_FEATURE_LOCAL);
                    lines[k] = lines[k].Replace(SCKeywordLine.PRAGMA_FEATURE_GLOBAL.ToUpper(), SCKeywordLine.PRAGMA_FEATURE_LOCAL);
                    changed = true;
                }
            }
            if (changed) {
                MakeBackup(shader);
                File.WriteAllLines(path, lines, Encoding.UTF8);
            }
        }
    }

}