using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using ThoughtWorks.CruiseControl.Remote;

using SharpSevenZip;
using System.Text;
//using DocumentFormat.OpenXml.Wordprocessing;

namespace CCNetLogReader
{
    public partial class MainWindow : Form
    {
        Configuration configuration = new Configuration();
        public static string PROJECT_LOG_DIR { get; } = "Projects\\";
        private CCNetWrapper.ProjectLog _projectLog = new CCNetWrapper.ProjectLog();
        private static object PROJECT_LOG_LOCK = new object();

        private List<string> _buildNames;
        public string _projName { get; private set; }
        public string _versionNumber { get; private set; }
        private const string BUILDNAME_PRE_VERSION_STR = "build";

        private string _ipAddressOrHostNameOfCCServer;
        private string _managerAddress;
        private CruiseServerClientFactory _cruiseServerClientFactory;

        private static long _progTotal, _progCurrent;

        private FogbugzWrapper.FogbugzClient _fogbugzClient;

        public List<ServerBuild> _serverBuilds = new List<ServerBuild>();

        public MainWindow()
        {
            InitializeComponent();
        }

        private void MainWindow_Load(object sender, EventArgs e)
        {
            if (!Directory.Exists($"{PROJECT_LOG_DIR}"))
            {
                Directory.CreateDirectory($"{PROJECT_LOG_DIR}");
            }

            var configfilename = Path.Combine(Application.StartupPath, "Configuration.xml");
            configuration = Configuration.Deserialize(configfilename);

            txt_Server.Text = Properties.Settings.Default.CCNetServerAddress;
            ConnectToServer();
            if (!string.IsNullOrEmpty(Properties.Settings.Default.ManuscriptToken))
            {
                _fogbugzClient = new FogbugzWrapper.FogbugzClient($@"http://{Properties.Settings.Default.ManuscriptServerAddress}/api.asp?", Properties.Settings.Default.ManuscriptToken);
                if (_fogbugzClient.IsLoggedIn())
                {
                    ckbx_UpdateFogbugz.Enabled = true;
                }
            }

            //Configuration.Serialize(configfilename, configuration);
            //dgv_Projects.Rows
            //    .Cast<DataGridViewRow>()
            //    .Where(r => (r.Cells["PublishedDate"].Value ?? "").ToString() != "")
            //    .First().Selected = true;
        }

        private void btn_Connect_Click(object sender, EventArgs e)
        {
            ConnectToServer();
        }

        private void ConnectToServer()
        {
            _projectLog = new CCNetWrapper.ProjectLog();
            UpdateFogBugz();

            _projName = string.Empty;
            _buildNames = new List<string>();
            _serverBuilds = new List<ServerBuild>();
            if (string.IsNullOrEmpty(txt_Server.Text)) { return; }
            _ipAddressOrHostNameOfCCServer = txt_Server.Text; // Complete this value
            //var urlAddress = $"http://{_ipAddressOrHostNameOfCCServer}/ccnet/";
            _managerAddress = $"tcp://{_ipAddressOrHostNameOfCCServer}:21234/CruiseManager.rem";

            _cruiseServerClientFactory = new CruiseServerClientFactory();
            DGVDoubleBuffer(dgv_Projects);
            DGVDoubleBuffer(dgv_CommitComments);
            using (var client = _cruiseServerClientFactory.GenerateRemotingClient(_managerAddress, _ipAddressOrHostNameOfCCServer))
            {
                //using (var sw = new StreamWriter("datapro.csv", true))
                //{
                var projectStatuses = client.GetProjectStatus().ToList();
                projectStatuses.Sort((x, y) => x.Name.CompareTo(y.Name));
                var uniqueBuilds = new List<string>();
                Parallel.ForEach(projectStatuses, projectStatus =>
                {
                    DateTime? Published = null;

                    if (configuration.SoftwarePackages.Any(s => s.ProjectName == projectStatus.Name))
                    {
                        var sp = configuration.SoftwarePackages.First(s => s.ProjectName == projectStatus.Name);
                        Published = sp.PublishedDate;
                    }
                    
                    //dataGridView1.Rows.Add(new object[] { projectStatus.Name, projectStatus.BuildStatus, projectStatus.LastSuccessfulBuildLabel, projectStatus.LastBuildDate });
                    _serverBuilds.Add(new ServerBuild(projectStatus.Name,
                        projectStatus.BuildStatus,
                        projectStatus.LastSuccessfulBuildLabel,
                        projectStatus.LastBuildDate,
                        Published
                        ));

                    _projName = projectStatus.Name;
                    if (_projName.Contains("DATAPRO"))
                    {
                        try
                        {
                            _buildNames = client.GetBuildNames(_projName).ToList()
                                .Where(x => x.Contains(BUILDNAME_PRE_VERSION_STR)).ToList();
                        }
                        catch { return; }
                    }

                    //Application.DoEvents();
                });
                UpdateBuildLogUI();
            }
        }

        private int GetMinorVersionFromBuildName(string buildName)
        {
            var buildNameArray = buildName.Split('.');
            if (buildNameArray.Count() < 3) { return 0; }
            var rv = int.Parse(buildNameArray[3]);
            return rv;
        }

        private void btn_Filter_Click(object sender, EventArgs e)
        {
            UpdateProjectLogUI(false);
        }

        private void UpdateBuildLogUI()
        {
            dgv_Projects.DataSource = null;
            _serverBuilds.Sort((x, y) => string.CompareOrdinal(x?.ProjectName, y?.ProjectName));
            dgv_Projects.DataSource = _serverBuilds.Where(x => x != null ? x.ProjectName.ToLower().Contains(txt_SearchBuildNames.Text.ToLower()) : false).ToList();
            //dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dgv_Projects.Columns["ProjectName"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
        }

        private void UpdateProjectLogUI(bool update)
        {
            if (update)
            {
                UpdateChangeLog();
                UpdateFogBugz();
            }
            dgv_CommitComments.DataSource = null;
            //if(int.Parse(build.Split('.')[2]) >recentMinor || int.Parse(build.Split('.')[2]) < oldMinor){ continue; }
            dgv_CommitComments.DataSource = _projectLog.BuildLogs.Where(x => SearchAndFilter(x)).ToList();
            //dataGridView2.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;
            dgv_CommitComments.Columns["Comment"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            dgv_CommitComments.Columns["ReleaseNote"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
            dgv_CommitComments.Columns["ProjectName"].Visible = false;
                    }

        private bool SearchAndFilter(CCNetWrapper.BuildLog buildLog)
        {
            return (buildLog.Comment.ToLower().Contains(txt_CommentSearch.Text.ToLower()) ||
                buildLog.ReleaseNote.ToLower().Contains(txt_CommentSearch.Text.ToLower()) ||
                buildLog.Fogbugz.ToLower().Contains(txt_CommentSearch.Text.ToLower()) ||
                buildLog.FBStatus.ToString().ToLower().Contains(txt_CommentSearch.Text.ToLower()) ||
                buildLog.User.ToLower().Contains(txt_CommentSearch.Text.ToLower())
                )
                && buildLog.GetMinorBuildNumber() <= int.Parse(txt_Recent.Text)
                && buildLog.GetMinorBuildNumber() >= int.Parse(txt_Old.Text);
        }

        public void UpdateFogBugz(bool forceUpdate = false)
        {
            if (ckbx_UpdateFogbugz.Checked || forceUpdate)
            {
                this.Cursor = Cursors.WaitCursor;
                ManualResetEvent mre = new ManualResetEvent(false);
                _progCurrent = 0;
                UpdateProgress();

                BeginInvoke(new MethodInvoker(delegate ()
                {
                    _projectLog.SetFBClient(_fogbugzClient, ref _progTotal, ref _progCurrent);
                    mre.Set();
                }));
                while (!mre.WaitOne(100, false))
                {
                    Application.DoEvents();
                    toolStripProgressBar1.Value = (int)(Progress * 100.0);
                    //UpdateProgress();
                }
                SerializeObject(_projectLog, $"{PROJECT_LOG_DIR}{_projName}.xml");
                this.Cursor = Cursors.Default;
            }
        }

        public void UpdateChangeLog()
        {
            this.Cursor = Cursors.WaitCursor;
            dgv_CommitComments.DataSource = null;
            int recentMinor = 0;
            int oldMinor = 0;

            if (File.Exists($"{PROJECT_LOG_DIR}{_projName}.xml"))
            {
                _projectLog = DeSerializeObject<CCNetWrapper.ProjectLog>($"{PROJECT_LOG_DIR}{_projName}.xml");
            }
            else
            {
                _projectLog = new CCNetWrapper.ProjectLog();
            }
            if (string.IsNullOrEmpty(_projName)
                || !int.TryParse(txt_Recent.Text, out recentMinor)
                || !int.TryParse(txt_Old.Text, out oldMinor)
                || recentMinor <= oldMinor
                ) { return; }
            var existingBuildNumbers = _projectLog.BuildLogs.Select(x => x.BuildNumber).ToList();
            //var recentBuildNumbers = _buildNames.ToDictionary(x => $"{x.Split('.')[1]}.{x.Split('.')[2]}.{x.Split('.')[3]}"+ x.Split('.')[4].ToLower()=="xml" ? "": $".{x.Split('.')[4]}", x=>x);
            var recentBuildNumbers = new Dictionary<string, string>();

            foreach (var bn in _buildNames)
            {
                var buildNumber = bn.Substring(bn.IndexOf('.') + 1, bn.LastIndexOf('.') - 1 - bn.IndexOf('.'));
                recentBuildNumbers[buildNumber] = bn;
            }

            var buildNamesLimited = recentBuildNumbers.Where(x => !existingBuildNumbers.Contains(x.Key)).ToDictionary(x => x.Key, x => x.Value);
            if (buildNamesLimited.Any())
            {
                using (var client = _cruiseServerClientFactory.GenerateRemotingClient(_managerAddress, _ipAddressOrHostNameOfCCServer))
                {
                    _progTotal = buildNamesLimited.Count;
                    _progCurrent = 0;
                    Parallel.ForEach(buildNamesLimited.Values, bn =>
                    {
                        var log = string.Empty;
                        try
                        {
                            log = client.GetLog(_projName, bn);
                        }
                        catch { return; }
                        XmlDocument doc = new XmlDocument();
                        doc.LoadXml(log);
                        var topNode = doc.SelectSingleNode("cruisecontrol");
                        if (topNode != null)
                        {
                            string build = string.Empty;
                            string comment = string.Empty;
                            string user = string.Empty;
                            string date = string.Empty;
                            string fogbugz = string.Empty;
                            List<string> modifiedFiles = new List<string>();
                            if (null != topNode.SelectSingleNode("modifications") && topNode.SelectSingleNode("modifications").HasChildNodes)
                            {
                                comment = topNode.SelectSingleNode("modifications").FirstChild.SelectSingleNode("comment").InnerText.Replace(Environment.NewLine, ";");
                                foreach (Match m in Regex.Matches(comment, @"\d+"))
                                {
                                    if (comment.StartsWith(m.Value)
                                    || (comment.Contains("fb") && (comment.IndexOf("fb") + 2 == comment.IndexOf(m.Value) || comment.IndexOf("fb") + 3 == comment.IndexOf(m.Value)))
                                    || (comment.Contains("FB") && (comment.IndexOf("FB") + 2 == comment.IndexOf(m.Value) || comment.IndexOf("FB") + 3 == comment.IndexOf(m.Value)))
                                    || (comment.Contains("asp?") && (comment.IndexOf("asp?") + 4 == comment.IndexOf(m.Value)))
                                    || (comment.Contains("http://manuscript") && comment.IndexOf("cases/") + 6 == comment.IndexOf(m.Value))
                                    )
                                    {
                                        fogbugz = m.Value; continue;
                                    }
                                }

                                //if (string.IsNullOrEmpty(comment)) { return; }
                                user = topNode.SelectSingleNode("modifications").FirstChild.SelectSingleNode("user").InnerText;
                                date = topNode.SelectSingleNode("modifications").FirstChild.SelectSingleNode("date").InnerText;
                                foreach (XmlNode node in topNode.SelectSingleNode("modifications").SelectNodes("modification"))
                                {
                                    modifiedFiles.Add($"{node.SelectSingleNode("project").InnerText}/{node.SelectSingleNode("filename").InnerText}");
                                }
                            }
                            else { return; }
                            if (null != topNode.SelectSingleNode("integrationProperties"))
                            {
                                build = topNode.SelectSingleNode("integrationProperties").SelectSingleNode("CCNetLabel").InnerText;
                            }
                            var bl = new CCNetWrapper.BuildLog(_projName, build, comment, user, date, fogbugz, modifiedFiles);
                            Interlocked.Increment(ref _progCurrent);
                            UpdateProgress();
                            lock (PROJECT_LOG_LOCK)
                            {
                                _projectLog.BuildLogs.Add(bl);
                                Application.DoEvents();
                            }
                        }
                    });
                    _projectLog.SortByMinorBuild();
                    SerializeObject(_projectLog, $"{PROJECT_LOG_DIR}{_projName}.xml");
                }
            }
            this.Cursor = Cursors.Default;
        }

        public void UpdateProgress()
        {
            BeginInvoke(new MethodInvoker(delegate
            {
                toolStripProgressBar1.Value = (int)(Progress * 100.0);
            }));
        }

        public double Progress
        {
            get
            {
                if (_progTotal == 0L)
                {
                    return 0.0D;
                }
                return (double)_progCurrent / _progTotal;
            }
        }

        private void DGVDoubleBuffer(DataGridView dgv)
        {
            Type dgvType = dgv.GetType();
            System.Reflection.PropertyInfo pi = dgvType.GetProperty("DoubleBuffered",
                  System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
            pi.SetValue(dgv, true, null);
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            UpdateProjectLogUI(false);
        }

        private void txt_SearchBuildNames_TextChanged(object sender, EventArgs e)
        {
            UpdateBuildLogUI();
        }

        public void SerializeObjectProtobuf<T>(T serializableObject, string fileName)
        {

        }
        /// <summary>
        /// Serializes an object.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="serializableObject"></param>
        /// <param name="fileName"></param>
        public void SerializeObject<T>(T serializableObject, string fileName)
        {
            if (serializableObject == null) { return; }
            ManualResetEvent mre = new ManualResetEvent(false);
            _progCurrent = 0;
            _progTotal = 45 * 100;
            BeginInvoke(new MethodInvoker(delegate ()
            {
                try
                {
                    XmlDocument xmlDocument = new XmlDocument();
                    XmlSerializer serializer = new XmlSerializer(serializableObject.GetType());
                    using (MemoryStream stream = new MemoryStream())
                    {
                        serializer.Serialize(stream, serializableObject);
                        stream.Position = 0;
                        xmlDocument.Load(stream);
                        xmlDocument.Save(fileName);
                    }
                }
                catch (Exception ex)
                {
                    //Log exception here
                }
                mre.Set();
            }));
            while (!mre.WaitOne(100, false))
            {
                _progCurrent++;
                UpdateProgress();
                Application.DoEvents();
            }
        }

        /// <summary>
        /// Deserializes an xml file into an object list
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public T DeSerializeObject<T>(string fileName)
        {
            if (string.IsNullOrEmpty(fileName)) { return default(T); }
            T objectOut = default(T);
            ManualResetEvent mre = new ManualResetEvent(false);
            _progCurrent = 0;
            _progTotal = 45 * 100;
            BeginInvoke(new MethodInvoker(delegate ()
            {
                try
                {
                    XmlDocument xmlDocument = new XmlDocument();
                    xmlDocument.Load(fileName);
                    string xmlString = xmlDocument.OuterXml;

                    using (StringReader read = new StringReader(xmlString))
                    {
                        Type outType = typeof(T);

                        XmlSerializer serializer = new XmlSerializer(outType);
                        using (XmlReader reader = new XmlTextReader(read))
                        {
                            objectOut = (T)serializer.Deserialize(reader);
                        }
                    }
                }
                catch (Exception)
                {
                    //Log exception here
                }
                mre.Set();
            }));
            while (!mre.WaitOne(100, false))
            {
                _progCurrent++;
                UpdateProgress();
                Application.DoEvents();
            }
            return objectOut;
        }



        private void MainWindow_FormClosing(object sender, FormClosingEventArgs e)
        {
            Properties.Settings.Default.Save();
        }

        private void txt_Server_TextChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.CCNetServerAddress = txt_Server.Text;
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void projectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (File.Exists($"{PROJECT_LOG_DIR}{_projName}.xml"))
            {
                File.Delete($"{PROJECT_LOG_DIR}{_projName}.xml");
            }
            UpdateProjectLogUI(true);
        }

        private void allToolStripMenuItem_Click(object sender, EventArgs e)
        {
            foreach (string file in Directory.EnumerateFiles($"{PROJECT_LOG_DIR}", "*.xml").ToList())
            {
                File.Delete(file);
            }
        }

        private void dataGridView1_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (e.RowIndex == -1) { return; }
            (sender as DataGridView).Enabled = false;
            _projName = dgv_Projects.Rows[e.RowIndex].Cells["ProjectName"].Value.ToString();
            using (var client = _cruiseServerClientFactory.GenerateRemotingClient(_managerAddress, _ipAddressOrHostNameOfCCServer))
            {
                try
                {
                    _buildNames = client.GetBuildNames(_projName).ToList().Where(x => x.Contains(BUILDNAME_PRE_VERSION_STR)).ToList();
                }
                catch { return; }
                if (_buildNames.Any())
                {
                    _buildNames.Sort();
                    _buildNames.Reverse();
                    txt_Old.Text = GetMinorVersionFromBuildName(_buildNames.Last()).ToString();
                    txt_Recent.Text = GetMinorVersionFromBuildName(_buildNames.First()).ToString();
                    UpdateProjectLogUI(true);
                }
            }

            btnPublish.Enabled = false;

            (sender as DataGridView).Enabled = true;
            dgv_CommitComments.Rows[0].Selected = true;
        }

        private void dataGridView1_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
        {
        }

        private void dgv_CellStyles(object sender, DataGridViewCellFormattingEventArgs e)
        {
            var dgv = sender as DataGridView;
            var bIsFail = false;
            if (e.Value is IntegrationStatus buildStatus)
            {
                switch (buildStatus)
                {
                    case IntegrationStatus.Failure:
                    case IntegrationStatus.Exception:
                    case IntegrationStatus.Cancelled:
                        dgv.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Salmon;
                        break;
                    default:
                        dgv.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.White;
                        break;
                }
            }
            else if (e.Value is FogbugzWrapper.FBEvent.FBStatuses fbStatus)
            {
                switch (fbStatus)
                {
                    case FogbugzWrapper.FBEvent.FBStatuses.Opened:
                    case FogbugzWrapper.FBEvent.FBStatuses.Reactivated:
                        dgv.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.Salmon;
                        break;
                    case FogbugzWrapper.FBEvent.FBStatuses.Resolved:
                        dgv.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.PaleGoldenrod;
                        break;
                    default:
                        dgv.Rows[e.RowIndex].DefaultCellStyle.BackColor = Color.White;
                        break;
                }
            }
            else if (e.Value is DateTime dt)
            {
                e.Value = dt.ToString("yyyy-MM-dd HH:mm", CultureInfo.InvariantCulture);
            }
        }

        private void releaseNotesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var releaseNoteForm = new ReleaseNotes(_serverBuilds);
            releaseNoteForm.MainWindowParent = this;
            releaseNoteForm.Show();
        }

        private void logonToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var manuscriptLogin = new CCNetControls.ManuscriptLogin();
            manuscriptLogin.LoginCompleted += ManuscriptLogin_LoginCompleted;
            manuscriptLogin.Show();
        }

        private void ManuscriptLogin_LoginCompleted(object sender, EventArgs e)
        {
            var output = (string[])sender;
            _fogbugzClient = new FogbugzWrapper.FogbugzClient($@"http://{Properties.Settings.Default.ManuscriptServerAddress}/api.asp?", output[0], output[1]);
            if(_fogbugzClient.IsLoggedIn())
            {
                Properties.Settings.Default.ManuscriptToken = _fogbugzClient.Token;
                ckbx_UpdateFogbugz.Enabled = true;
            }
        }

        private void publishSelectedBuildToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

        private void dgv_CommitComments_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            // Enable Publish Button if a config exists
            btnPublish.Enabled = 
                (dgv_Projects.SelectedRows[0].Cells["PublishedDate"].Value != null);

            _versionNumber = dgv_CommitComments.Rows[e.RowIndex].Cells["BuildNumber"].Value.ToString();
        }

        private void btnPublish_Click(object sender, EventArgs e)
        {
            UpdateFogBugz();
            var publishForm = new Publish(this, configuration);
            publishForm.Show();
        }

        private void dgv_Projects_CellContentClick(object sender, DataGridViewCellEventArgs e)
        {

        }

        private void dataGridView2_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.RowIndex == -1) { return; }
            var fogbugz = dgv_CommitComments.Rows[e.RowIndex].Cells["Fogbugz"].Value.ToString();
            System.Diagnostics.Process.Start($"http://{Properties.Settings.Default.ManuscriptServerAddress}/f/cases/{fogbugz}");
        }
    }
}