// Copyright (c)  2023  Xiaomi Corporation (authors: Fangjun Kuang)

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

namespace SherpaNcnn
{

    [StructLayout(LayoutKind.Sequential)]
    public struct TransducerModelConfig
    {
        [MarshalAs(UnmanagedType.LPStr)]
        public string EncoderParam;

        [MarshalAs(UnmanagedType.LPStr)]
        public string EncoderBin;

        [MarshalAs(UnmanagedType.LPStr)]
        public string DecoderParam;

        [MarshalAs(UnmanagedType.LPStr)]
        public string DecoderBin;

        [MarshalAs(UnmanagedType.LPStr)]
        public string JoinerParam;

        [MarshalAs(UnmanagedType.LPStr)]
        public string JoinerBin;

        [MarshalAs(UnmanagedType.LPStr)]
        public string Tokens;

        public int UseVulkanCompute;

        public int NumThreads;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct TransducerDecoderConfig
    {
        [MarshalAs(UnmanagedType.LPStr)]
        public string DecodingMethod;

        public int NumActivePaths;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct FeatureConfig
    {
        public float SampleRate;
        public int FeatureDim;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct OnlineRecognizerConfig
    {
        public FeatureConfig FeatConfig;
        public TransducerModelConfig ModelConfig;
        public TransducerDecoderConfig DecoderConfig;

        public int EnableEndpoint;
        public float Rule1MinTrailingSilence;
        public float Rule2MinTrailingSilence;
        public float Rule3MinUtteranceLength;

        [MarshalAs(UnmanagedType.LPStr)]
        public string HotwordsFile;

        public float HotwordsScore;
    }

    // please see
    // https://www.mono-project.com/docs/advanced/pinvoke/#gc-safe-pinvoke-code
    // https://www.mono-project.com/docs/advanced/pinvoke/#properly-disposing-of-resources
    public class OnlineRecognizer : IDisposable
    {
        public OnlineRecognizer(OnlineRecognizerConfig config)
        {
            IntPtr h = CreateOnlineRecognizer(ref config);
            _handle = new HandleRef(this, h);
        }

        public OnlineStream CreateStream()
        {
            IntPtr p = CreateOnlineStream(_handle.Handle);
            return new OnlineStream(p);
        }

        public bool IsReady(OnlineStream stream)
        {
            return IsReady(_handle.Handle, stream.Handle) != 0;
        }

        public bool IsEndpoint(OnlineStream stream)
        {
            return IsEndpoint(_handle.Handle, stream.Handle) != 0;
        }

        public void Reset(OnlineStream stream)
        {
            Reset(_handle.Handle, stream.Handle);
        }

        public void Decode(OnlineStream stream)
        {
            Decode(_handle.Handle, stream.Handle);
        }

        public OnlineRecognizerResult GetResult(OnlineStream stream)
        {
            IntPtr h = GetResult(_handle.Handle, stream.Handle);
            OnlineRecognizerResult result = new OnlineRecognizerResult(h);
            DestroyResult(h);
            return result;
        }

        public void Dispose()
        {
            Cleanup();
            // Prevent the object from being placed on the
            // finalization queue
            System.GC.SuppressFinalize(this);
        }

        ~OnlineRecognizer()
        {
            Cleanup();
        }

        private void Cleanup()
        {
            DestroyOnlineRecognizer(_handle.Handle);

            // Don't permit the handle to be used again.
            _handle = new HandleRef(this, IntPtr.Zero);
        }

        private HandleRef _handle;

        private const string dllName = "sherpa-ncnn-c-api";

        [DllImport(dllName, EntryPoint = "CreateRecognizer")]
        private static extern IntPtr CreateOnlineRecognizer(ref OnlineRecognizerConfig config);

        [DllImport(dllName, EntryPoint = "DestroyRecognizer")]
        private static extern void DestroyOnlineRecognizer(IntPtr handle);

        [DllImport(dllName, EntryPoint = "CreateStream")]
        private static extern IntPtr CreateOnlineStream(IntPtr handle);

        [DllImport(dllName)]
        private static extern int IsReady(IntPtr handle, IntPtr stream);

        [DllImport(dllName)]
        private static extern int IsEndpoint(IntPtr handle, IntPtr stream);

        [DllImport(dllName)]
        private static extern void Reset(IntPtr handle, IntPtr stream);

        [DllImport(dllName, EntryPoint = "Decode")]
        private static extern void Decode(IntPtr handle, IntPtr stream);

        [DllImport(dllName)]
        private static extern IntPtr GetResult(IntPtr handle, IntPtr stream);

        [DllImport(dllName)]
        private static extern void DestroyResult(IntPtr result);
    }


    public class OnlineStream : IDisposable
    {
        public OnlineStream(IntPtr p)
        {
            _handle = new HandleRef(this, p);
        }

        public void AcceptWaveform(float sampleRate, float[] samples)
        {
            AcceptWaveform(Handle, sampleRate, samples, samples.Length);
        }
        public void InputFinished()
        {
            InputFinished(Handle);
        }

        ~OnlineStream()
        {
            Cleanup();
        }

        public void Dispose()
        {
            Cleanup();
            // Prevent the object from being placed on the
            // finalization queue
            System.GC.SuppressFinalize(this);
        }

        private void Cleanup()
        {
            DestroyOnlineStream(Handle);

            // Don't permit the handle to be used again.
            _handle = new HandleRef(this, IntPtr.Zero);
        }

        private HandleRef _handle;
        public IntPtr Handle => _handle.Handle;

        private const string dllName = "sherpa-ncnn-c-api";

        [DllImport(dllName, EntryPoint = "DestroyStream")]
        private static extern void DestroyOnlineStream(IntPtr handle);

        [DllImport(dllName)]
        private static extern void AcceptWaveform(IntPtr handle, float sampleRate, float[] samples, int n);

        [DllImport(dllName)]
        private static extern void InputFinished(IntPtr handle);
    }

    public class OnlineRecognizerResult
    {
        public OnlineRecognizerResult(IntPtr handle)
        {
            Impl impl = (Impl)Marshal.PtrToStructure(handle, typeof(Impl));

            // PtrToStringUTF8() requires .net standard 2.1
            // _text = Marshal.PtrToStringUTF8(impl.Text);

            int length = 0;

            unsafe
            {
                byte* buffer = (byte*)impl.Text;
                while (*buffer != 0)
                {
                    ++buffer;
                }
                length = (int)(buffer - (byte*)impl.Text);
            }

            byte[] stringBuffer = new byte[length];
            Marshal.Copy(impl.Text, stringBuffer, 0, length);
            _text = Encoding.UTF8.GetString(stringBuffer);
        }

        [StructLayout(LayoutKind.Sequential)]
        struct Impl
        {
            public IntPtr Text;
            public IntPtr Tokens;
            public IntPtr Timestamps;
            int Count;
        }

        private String _text;
        public String Text => _text;

    }

}
