using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using HARKlib;

namespace HARKlibSample
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged([CallerMemberName] string propertyName = null)
            => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private static int SAMPLING_RATE = 16000;
        private static int BITS_PER_SAMPLE = 16;
        private static int CHANNEL_COUNT = 1;
        private static float BUFFER_LENGTH = 0.1f;

        private String _Status;
        private String _OutputText;

        private WaveIn _WaveIn;

        private Network _network = Network.create_network<Main>("MAIN");
        private PublishData _publisher = null;
        private SubscribeData _subscriber = null, _source_subscriber = null;

        public MainWindow()
        {
            InitializeComponent();

            this.DataContext = this;
            this.StopRecording();

            this._publisher = this._network.query_nodedef("Publisher") as PublishData;
            this._subscriber = this._network.query_nodedef("Subscriber") as SubscribeData;
            this._source_subscriber = this._network.query_nodedef("Subscriber_Source") as SubscribeData;

            this._subscriber.Received += OnReceived;
            this._source_subscriber.Received += OnReceived;
        }

        public String Status
        {
            get => _Status;
            set
            {
                if (value == _Status)
                    return;
                _Status = value;
                RaisePropertyChanged();
            }
        }

        public String OutputText
        {
            get => _OutputText;
            set
            {
                if (value == _OutputText)
                    return;
                _OutputText = value;
                RaisePropertyChanged();
            }
        }

        private void StartButton_Click(object sender, RoutedEventArgs e)
        {
            Task.Run(() =>
            {
                this._network.execute();
            });
            this.StartRecording();
        }

        private void StopButton_Click(object sender, RoutedEventArgs e)
        {
            this._network.stop();
            this.StopRecording();
        }


        private void StartRecording()
        {
            var waveIn = new WaveIn();
            waveIn.WaveFormat = new WaveFormat(SAMPLING_RATE, BITS_PER_SAMPLE, CHANNEL_COUNT); // 1: mono
            waveIn.DeviceNumber = 0;
            waveIn.BufferMilliseconds = (int)(1000 * BUFFER_LENGTH);
            waveIn.DataAvailable += new EventHandler<WaveInEventArgs>(DataAvailable);
            try
            {
                waveIn.StartRecording();
                this._WaveIn = waveIn;
            }
            catch (NAudio.MmException exc)
            {
                // when no device exists or no microphone is plugged in, an exception will be thrown here
                MessageBox.Show("Error initializing audio device. Make sure that a default recording device is available!" + Environment.NewLine + "Error details:" + exc.Message);
            }

            this.Status = "WORKING";
        }

        private void StopRecording()
        {
            if (this._WaveIn != null)
            {
                this._WaveIn.StopRecording();
                this._WaveIn = null;
            }

            this.Status = "IDLE";
        }

        private void DataAvailable(object sender, WaveInEventArgs args)
        {
            Debug.WriteLine($"received: {args.BytesRecorded}");

            byte[] bdata = args.Buffer;
            short[] sdata = new short[args.BytesRecorded / 2];
            for (int s = 0, d = 0; s < args.BytesRecorded; s += 2, ++d)
            {
                sdata[d] = BitConverter.ToInt16(bdata, s);
            }

            this._publisher.push(sdata);
        }

        private void OnReceived(object sender, SubscribeDataEventArgs args)
        {
            //Debug.WriteLine($"OnReceived: {args.ReceivedData}");
            if (args.ReceivedData is Dictionary<int, object>)
            {
                var src_dict = args.ReceivedData as Dictionary<int, object>;

                var new_dict = new HashSet<int>(src_dict.Keys);
                var old_dict = new HashSet<int>(this._last_sources);

                old_dict.ExceptWith(new_dict);
                new_dict.ExceptWith(this._last_sources);

                foreach (var ap in new_dict)
                {
                    OutputText += $"Found: {ap}\n";
                }
                foreach (var da in old_dict)
                {
                    OutputText += $"Lost: {da}\n";
                }

                this._last_sources = new HashSet<int>(src_dict.Keys);

            }else if(args.ReceivedData is List<object>)
            {
                foreach (var src in args.ReceivedData as List<object>)
                {
                    Debug.WriteLine($"Source: {src}");
                }

            }
        }

        private HashSet<int> _last_sources = new HashSet<int>();
    }
}
