using System;
using System.Runtime.InteropServices;
using System.Text;

namespace SpeechMarkdown
{
    public class SpeechMarkdownParser : IDisposable
    {
        private static readonly object _lock = new object();

        private IntPtr _libraryHandle;

        private const string DllName = "speechmarkdown_rust";

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr speechmarkdown_to_ssml(IntPtr input, IntPtr platform);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr speechmarkdown_to_text(IntPtr input);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr speechmarkdown_parse(IntPtr input);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        private static extern void speechmarkdown_free(IntPtr s);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr speechmarkdown_get_error();

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.I1)]
        private static extern bool speechmarkdown_is_speech_markdown(IntPtr input);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        [return: MarshalAs(UnmanagedType.I1)]
        private static extern bool speechmarkdown_validate(IntPtr input);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr speechmarkdown_to_smd(IntPtr input);

        public SpeechMarkdownParser()
        {
        }

        public string ToSsml(string input, string platform)
        {
            if (input == null) throw new ArgumentNullException(nameof(input));
            if (platform == null) throw new ArgumentNullException(nameof(platform));

            lock (_lock)
            {
                IntPtr inputPtr = MarshalUtf8(input);
                IntPtr platformPtr = MarshalUtf8(platform);

                try
                {
                    IntPtr resultPtr = speechmarkdown_to_ssml(inputPtr, platformPtr);

                    if (resultPtr == IntPtr.Zero)
                    {
                        string error = GetAndFreeError();
                        throw new SpeechMarkdownException(error ?? "Unknown error");
                    }

                    return PtrToStringAndFree(resultPtr);
                }
                finally
                {
                    Marshal.FreeHGlobal(inputPtr);
                    Marshal.FreeHGlobal(platformPtr);
                }
            }
        }

        public string ToText(string input)
        {
            if (input == null) throw new ArgumentNullException(nameof(input));

            lock (_lock)
            {
                IntPtr inputPtr = MarshalUtf8(input);

                try
                {
                    IntPtr resultPtr = speechmarkdown_to_text(inputPtr);

                    if (resultPtr == IntPtr.Zero)
                    {
                        string error = GetAndFreeError();
                        throw new SpeechMarkdownException(error ?? "Unknown error");
                    }

                    return PtrToStringAndFree(resultPtr);
                }
                finally
                {
                    Marshal.FreeHGlobal(inputPtr);
                }
            }
        }

        public string ParseToJson(string input)
        {
            if (input == null) throw new ArgumentNullException(nameof(input));

            lock (_lock)
            {
                IntPtr inputPtr = MarshalUtf8(input);

                try
                {
                    IntPtr resultPtr = speechmarkdown_parse(inputPtr);

                    if (resultPtr == IntPtr.Zero)
                    {
                        string error = GetAndFreeError();
                        throw new SpeechMarkdownException(error ?? "Unknown error");
                    }

                    return PtrToStringAndFree(resultPtr);
                }
                finally
                {
                    Marshal.FreeHGlobal(inputPtr);
                }
            }
        }

        public bool IsSpeechMarkdown(string input)
        {
            if (input == null) throw new ArgumentNullException(nameof(input));

            lock (_lock)
            {
                IntPtr inputPtr = MarshalUtf8(input);

                try
                {
                    return speechmarkdown_is_speech_markdown(inputPtr);
                }
                finally
                {
                    Marshal.FreeHGlobal(inputPtr);
                }
            }
        }

        public bool Validate(string input)
        {
            if (input == null) throw new ArgumentNullException(nameof(input));

            lock (_lock)
            {
                IntPtr inputPtr = MarshalUtf8(input);

                try
                {
                    bool valid = speechmarkdown_validate(inputPtr);
                    if (!valid)
                    {
                        string error = GetAndFreeError();
                        throw new SpeechMarkdownException(error ?? "Validation failed");
                    }
                    return true;
                }
                finally
                {
                    Marshal.FreeHGlobal(inputPtr);
                }
            }
        }

        public string ToSmd(string ssml)
        {
            if (ssml == null) throw new ArgumentNullException(nameof(ssml));

            lock (_lock)
            {
                IntPtr inputPtr = MarshalUtf8(ssml);

                try
                {
                    IntPtr resultPtr = speechmarkdown_to_smd(inputPtr);

                    if (resultPtr == IntPtr.Zero)
                    {
                        string error = GetAndFreeError();
                        throw new SpeechMarkdownException(error ?? "Unknown error converting SSML to SpeechMarkdown");
                    }

                    return PtrToStringAndFree(resultPtr);
                }
                finally
                {
                    Marshal.FreeHGlobal(inputPtr);
                }
            }
        }

        private static IntPtr MarshalUtf8(string s)
        {
            byte[] bytes = Encoding.UTF8.GetBytes(s + "\0");
            IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);
            Marshal.Copy(bytes, 0, ptr, bytes.Length);
            return ptr;
        }

        private static string PtrToStringAndFree(IntPtr ptr)
        {
            if (ptr == IntPtr.Zero) return null;

            int len = 0;
            while (Marshal.ReadByte(ptr, len) != 0) len++;

            byte[] bytes = new byte[len];
            Marshal.Copy(ptr, bytes, 0, len);
            speechmarkdown_free(ptr);

            return Encoding.UTF8.GetString(bytes);
        }

        private static string GetAndFreeError()
        {
            IntPtr errorPtr = speechmarkdown_get_error();
            return PtrToStringAndFree(errorPtr);
        }

        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }

    public class SpeechMarkdownException : Exception
    {
        public SpeechMarkdownException(string message) : base(message) { }
    }

    public static class Platform
    {
        public const string AmazonAlexa = "amazon-alexa";
        public const string GoogleAssistant = "google-assistant";
        public const string MicrosoftAzure = "microsoft-azure";
        public const string Apple = "apple";
        public const string W3c = "w3c";
        public const string SamsungBixby = "samsung-bixby";
        public const string ElevenLabs = "eleven-labs";
        public const string IbmWatson = "ibm-watson";
    }
}
