#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace FMODUnity
{
    public static class CodeGeneration
    {
        public static void GenerateStaticPluginRegistration(string filePath, Platform platform,
            Action<string> reportError)
        {
            List<string> validatedPlugins = ValidateStaticPlugins(platform.StaticPlugins, reportError);

            using (StreamWriter file = new StreamWriter(filePath))
            {
                WriteStaticPluginRegistration(file, platform.IsFMODStaticallyLinked, validatedPlugins);
            }
        }

        private static void WriteStaticPluginRegistration(StreamWriter file, bool isFMODStaticallyLinked,
            IEnumerable<string> pluginFunctions)
        {
            file.WriteLine("#if ENABLE_IL2CPP");
            file.WriteLine();
            file.WriteLine("// This file was generated by FMOD for Unity from the Static Plugins list in the FMOD settings.");
            file.WriteLine();

            file.WriteLine("using System;");
            file.WriteLine("using System.Runtime.InteropServices;");
            file.WriteLine();

            file.WriteLine("namespace FMODUnity");
            file.WriteLine("{");

            file.WriteLine("    class {0}", Platform.RegisterStaticPluginsClassName);
            file.WriteLine("    {");

            // Import the plugin functions
            foreach (string pluginFunction in pluginFunctions)
            {
                file.WriteLine("        [DllImport(\"__Internal\")]");
                file.WriteLine("        private static extern IntPtr {0}();", pluginFunction);
                file.WriteLine();
            }

            // Import the RegisterDSP function
            file.WriteLine("        [DllImport(FMOD.VERSION.dll)]");
            file.WriteLine("        private static extern FMOD.RESULT FMOD5_System_RegisterDSP(IntPtr system, IntPtr description, IntPtr handle);");
            file.WriteLine();

            file.WriteLine("        public static void {0}(FMOD.System coreSystem, Action<FMOD.RESULT, string> reportResult)",
                Platform.RegisterStaticPluginsFunctionName);
            file.WriteLine("        {");

            if (pluginFunctions.Any())
            {
                file.WriteLine("            FMOD.RESULT result;");

                foreach (string pluginFunction in pluginFunctions)
                {
                    file.WriteLine();
                    file.WriteLine("            result = FMOD5_System_RegisterDSP(coreSystem.handle, {0}(), IntPtr.Zero);", pluginFunction);
                    file.WriteLine("            reportResult(result, \"Registering static plugin '{0}'\");", pluginFunction);
                }
            }

            file.WriteLine("        }");
            file.WriteLine("    }");
            file.WriteLine("}");
            file.WriteLine("");
            file.WriteLine("#endif // ENABLE_IL2CPP");
        }

        private static List<string> ValidateStaticPlugins(List<string> staticPlugins, Action<string> reportError)
        {
            List<string> result = new List<string>();

            for (int i = 0; i < staticPlugins.Count; ++i)
            {
                string functionName = staticPlugins[i];

                string trimmedName = (functionName != null) ? functionName.Trim() : null;

                if (string.IsNullOrEmpty(trimmedName))
                {
                    reportError(string.Format("Static plugin {0} has no name and will be ignored.", i + 1));
                }
                else if (IsValidFunctionName(trimmedName, reportError))
                {
                    result.Add(trimmedName);
                }
            }

            return result;
        }

        private static bool IsValidFunctionName(string name, Action<string> reportError)
        {
            if (!(char.IsLetter(name[0]) || name[0] == '_'))
            {
                reportError(string.Format(
                    "Plugin name '{0}' is not valid. Names must start with a letter or an underscore ('_').", name));
                return false;
            }

            for (int i = 1; i < name.Length; ++i)
            {
                if (!(char.IsLetterOrDigit(name[i]) || name[i] == '_'))
                {
                    reportError(string.Format(
                        "Plugin name '{0}' is not valid. " +
                        "Character '{1}' at position {2} is invalid - it must be a letter, a number, or an underscore ('_').",
                        name, name[i], i));
                    return false;
                }
            }

            return true;
        }
    }
}
#endif
