using CodeWalker.GameFiles;
using CodeWalker.Utils;
using FastColoredTextBoxNS;
using System;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;
using System.Drawing;
using System.Linq;
using Range = FastColoredTextBoxNS.Range;

namespace CodeWalker.Forms
{
    public partial class AwcForm : Form
    {
        public AwcFile Awc { get; set; }

        private string fileName;
        public string FileName
        {
            get { return fileName; }
            set
            {
                fileName = value;
                UpdateFormTitle();
            }
        }
        public string FilePath { get; set; }

        private bool LoadingXml = false;
        private bool DelayHighlight = false;

        private AudioPlayer Player = new AudioPlayer();

        private bool PositionScrolled = false;

        public AwcForm()
        {
            InitializeComponent();
        }

        private void UpdateFormTitle()
        {
            Text = fileName + " - AWC Player - CodeWalker by dexyfex";
        }

        private void UpdateXmlTextBox(string xml)
        {
            LoadingXml = true;
            XmlTextBox.Text = "";
            XmlTextBox.Language = Language.XML;
            DelayHighlight = false;

            if (string.IsNullOrEmpty(xml))
            {
                LoadingXml = false;
                return;
            }
            //if (xml.Length > (1048576 * 5))
            //{
            //    XmlTextBox.Language = Language.Custom;
            //    XmlTextBox.Text = "[XML size > 10MB - Not shown due to performance limitations - Please use an external viewer for this file.]";
            //    return;
            //}
            //else 
            if (xml.Length > (1024 * 512))
            {
                XmlTextBox.Language = Language.Custom;
                DelayHighlight = true;
            }
            //else
            //{
            //    XmlTextBox.Language = Language.XML;
            //}


            Cursor = Cursors.WaitCursor;



            XmlTextBox.Text = xml;
            //XmlTextBox.IsChanged = false;
            XmlTextBox.ClearUndo();

            Cursor = Cursors.Default;
            LoadingXml = false;
        }

        public void LoadAwc(AwcFile awc)
        {
            Awc = awc;
            DetailsPropertyGrid.SelectedObject = awc;

            fileName = awc?.Name;
            if (string.IsNullOrEmpty(fileName))
            {
                fileName = awc?.FileEntry?.Name;
            }

            PlayListView.Items.Clear();

            float totalLength = 0;
            if (awc.Streams != null)
            {
                var strlist = awc.Streams.ToList();
                strlist.Sort((a, b) => a.Name.CompareTo(b.Name));
                foreach (var audio in strlist)
                {
                    var stereo = (audio.ChannelStreams?.Length == 2);
                    if ((audio.StreamBlocks != null) && (!stereo)) continue;//don't display multichannel source audios
                    var name = audio.Name;
                    if (stereo) name = "(Stereo Playback)";
                    var item = PlayListView.Items.Add(name);
                    item.SubItems.Add(audio.Type);
                    item.SubItems.Add(audio.LengthStr);
                    item.SubItems.Add(TextUtil.GetBytesReadable(audio.ByteLength));
                    item.Tag = audio;
                    totalLength += audio.Length;
                }
            }

            LabelInfo.Text = awc.Streams.Length.ToString() + " track(s), Length: " + TimeSpan.FromSeconds((float)totalLength).ToString("h\\:mm\\:ss");
            UpdateFormTitle();
        }

        public void LoadXml()
        {
            if (Awc != null)
            {
                var xml = AwcXml.GetXml(Awc);
                UpdateXmlTextBox(xml);
            }
        }

        private void HTMLSyntaxHighlight(Range range)
        {
            try
            {
                Style BlueStyle = new TextStyle(Brushes.Blue, null, FontStyle.Regular);
                Style RedStyle = new TextStyle(Brushes.Red, null, FontStyle.Regular);
                Style MaroonStyle = new TextStyle(Brushes.Maroon, null, FontStyle.Regular);

                //clear style of changed range
                range.ClearStyle(BlueStyle, MaroonStyle, RedStyle);
                //tag brackets highlighting
                range.SetStyle(BlueStyle, @"<|/>|</|>");
                //tag name
                range.SetStyle(MaroonStyle, @"<(?<range>[!\w]+)");
                //end of tag
                range.SetStyle(MaroonStyle, @"</(?<range>\w+)>");
                //attributes
                range.SetStyle(RedStyle, @"(?<range>\S+?)='[^']*'|(?<range>\S+)=""[^""]*""|(?<range>\S+)=\S+");
                //attribute values
                range.SetStyle(BlueStyle, @"\S+?=(?<range>'[^']*')|\S+=(?<range>""[^""]*"")|\S+=(?<range>\S+)");
            }
            catch
            { }
        }


        private void Stop()
        {
            Player.Stop();
            UpdateUI();
            UpdatePlayerButtons();
        }

        private void Play()
        {
            Stop();

            if (PlayListView.SelectedItems.Count == 1)
            {
                var item = PlayListView.SelectedItems[0];
                var audio = item.Tag as AwcStream;
                var stereo = (audio?.ChannelStreams?.Length == 2);
                if (stereo)
                {
                    var name0 = audio.ChannelStreams[0].Name;
                    var left0 = name0.EndsWith("left") || name0.EndsWith(".l");
                    var f0 = left0 ? 1.0f : 0.0f;
                    var f1 = left0 ? 0.0f : 1.0f;
                    Player.LoadAudio(audio.ChannelStreams);
                    Player.SetVolume(VolumeTrackBar.Value / 100.0f);
                    Player.SetOutputMatrix(0, f0, f1);
                    Player.SetOutputMatrix(1, f1, f0);
                    Player.Play();
                }
                else if ((audio?.FormatChunk != null) || (audio?.StreamFormat != null))
                {
                    Player.LoadAudio(audio);
                    Player.SetVolume(VolumeTrackBar.Value / 100.0f);
                    Player.Play();
                }
                else if (audio.MidiChunk != null)
                {
                    //todo: play MIDI?
                }
            }

            UpdateUI();
            UpdatePlayerButtons();
        }

        private void PlayPrevious()
        {
            Stop();
            if (PlayListView.SelectedIndices.Count > 0)
            {
                var nextIndex = PlayListView.SelectedIndices[0] - 1;
                if (nextIndex >= 0)
                {
                    PlayListView.Items[nextIndex].Selected = true;
                    PlayListView.Items[nextIndex].Focused = true;
                    Play();
                }
            }
        }

        private void PlayNext()
        {
            Stop();
            if (PlayListView.SelectedIndices.Count > 0)
            {
                var nextIndex = PlayListView.SelectedIndices[0] + 1;
                if (nextIndex < PlayListView.Items.Count)
                {
                    PlayListView.Items[nextIndex].Selected = true;
                    PlayListView.Items[nextIndex].Focused = true;
                    Play();
                }
            }
        }

        private void Pause()
        {
            Player.Pause();
            UpdatePlayerButtons();
        }

        private void Resume()
        {
            Player.Resume();
            UpdatePlayerButtons();
        }

        private void UpdateUI()
        {
            if ((Player.State != AudioPlayer.PlayerState.Stopped) && Player.trackFinished)
            {
                if (chbAutoJump.Checked)
                    PlayNext();
                else
                    Stop();
            }

            if (Player.State != AudioPlayer.PlayerState.Stopped)
            {
                int playedMs = Player.PlayTimeMS;
                int totalMs = Player.TotalTimeMS;
                PositionTrackBar.Maximum = totalMs;
                PositionTrackBar.Value = playedMs < totalMs ? playedMs : totalMs;

                LabelTime.Text = TimeSpan.FromSeconds(playedMs / 1000).ToString("m\\:ss")
                    + " / " + TimeSpan.FromSeconds(totalMs / 1000).ToString("m\\:ss");
            }
            else
            {
                PositionTrackBar.Value = 0;
            }
        }

        private void UpdatePlayerButtons()
        {
            switch (Player.State)
            {
                case AudioPlayer.PlayerState.Playing:
                    PlayButton.Text = "\u275A\u275A";
                    StopButton.Enabled = true;
                    LabelTime.Visible = true;
                    break;
                case AudioPlayer.PlayerState.Paused:
                    PlayButton.Text = "\u25B6";
                    StopButton.Enabled = true;
                    LabelTime.Visible = true;
                    break;
                case AudioPlayer.PlayerState.Stopped:
                    PlayButton.Text = "\u25B6";
                    StopButton.Enabled = true;
                    LabelTime.Visible = false;
                    break;
            }
        }


        private void PlayButton_Click(object sender, EventArgs e)
        {
            switch (Player.State)
            {
                case AudioPlayer.PlayerState.Stopped:
                    Play();
                    break;
                case AudioPlayer.PlayerState.Playing:
                    Pause();
                    break;
                case AudioPlayer.PlayerState.Paused:
                    Resume();
                    break;
            }
        }

        private void StopButton_Click(object sender, EventArgs e)
        {
            Stop();
        }

        private void PrevButton_Click(object sender, EventArgs e)
        {
            PlayPrevious();
        }

        private void NextButton_Click(object sender, EventArgs e)
        {
            PlayNext();
        }

        private void PositionTrackBar_Scroll(object sender, EventArgs e)
        {
            PositionScrolled = true;

            var t = PositionTrackBar.Value / 1000.0f;

            Player.Seek(t);
        }

        private void PositionTrackBar_MouseUp(object sender, MouseEventArgs e)
        {
            if (PositionScrolled)
            {
                PositionScrolled = false;
                return;
            }
            PositionScrolled = false;

            var f = Math.Min(Math.Max((e.X-13.0f) / (PositionTrackBar.Width-26.0f), 0.0f), 1.0f);
            var v = f * (PositionTrackBar.Maximum / 1000.0f);

            Player.Seek(v);
        }

        private void VolumeTrackBar_Scroll(object sender, EventArgs e)
        {
            Player.SetVolume((float)VolumeTrackBar.Value / 100);
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            UpdateUI();
        }

        private void PlayListView_DoubleClick(object sender, EventArgs e)
        {
            Play();
        }

        private void AwcForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            Stop();
            Player.DisposeAudio();
        }

        private void ExportAsWav_Click(object sender, EventArgs e)
        {
            if (PlayListView.SelectedItems.Count == 1)
            {
                var item = PlayListView.SelectedItems[0];
                var audio = item.Tag as AwcStream;
                var stereo = (audio?.ChannelStreams?.Length == 2);

                if (stereo)
                {
                    MessageBox.Show("Sorry, export for stereo output is not currently available.");
                    return;
                }

                var ext = ".wav";
                if (audio?.MidiChunk != null)
                {
                    ext = ".midi";
                }

                saveFileDialog.FileName = audio.Name + ext;
                if (saveFileDialog.ShowDialog() == DialogResult.OK)
                {
                    if (audio?.MidiChunk != null)
                    {
                        File.WriteAllBytes(saveFileDialog.FileName, audio.MidiChunk.Data);
                    }
                    else if ((audio?.FormatChunk != null) || (audio?.StreamFormat != null))
                    {
                        Stream wavStream = audio.GetWavStream();
                        FileStream stream = File.Create(saveFileDialog.FileName);
                        wavStream.CopyTo(stream);
                        stream.Close();
                        wavStream.Close();
                    }

                }
            }
        }

        private void PlayListView_SelectedIndexChanged(object sender, EventArgs e)
        {
            ExportAsWav.Text = "Export as .wav";
            if (PlayListView.SelectedItems.Count == 1)
            {
                var item = PlayListView.SelectedItems[0];
                var audio = item.Tag as AwcStream;
                if (audio?.MidiChunk != null)
                {
                    ExportAsWav.Text = "Export as .midi";
                }
            }
        }

        private void MainTabControl_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (MainTabControl.SelectedTab == XmlTabPage)
            {
                if (string.IsNullOrEmpty(XmlTextBox.Text))
                {
                    LoadXml();
                }
            }
        }

        private void XmlTextBox_VisibleRangeChangedDelayed(object sender, EventArgs e)
        {
            //this approach is much faster to load, but no outlining is available

            //highlight only visible area of text
            if (DelayHighlight)
            {
                HTMLSyntaxHighlight(XmlTextBox.VisibleRange);
            }
        }

        private void XmlTextBox_TextChanged(object sender, TextChangedEventArgs e)
        {
            if (!LoadingXml)
            {

            }
        }
    }
}
