using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using ClosedXML.Excel;

namespace SiemensTagOku
{
    public partial class MainForm : Form
    {
        private TiaPortalHost tiaHost;
        private string projectPath;
        private List<BlockGroupInfo> blockGroups;
        private bool isUpdatingCheckState;

        public MainForm()
        {
            InitializeComponent();
            CreateAppIcon();
        }

        private void CreateAppIcon()
        {
            using (var bmp = new Bitmap(32, 32))
            {
                using (var g = Graphics.FromImage(bmp))
                {
                    g.Clear(Color.FromArgb(0, 110, 127));
                    using (var brush = new SolidBrush(Color.White))
                    using (var font = new Font("Segoe UI", 18, FontStyle.Bold))
                    {
                        g.DrawString("T", font, brush, 6, 3);
                    }
                }
                this.Icon = Icon.FromHandle(bmp.GetHicon());
            }
        }

        protected override void OnFormClosing(FormClosingEventArgs e)
        {
            tiaHost?.Dispose();
            base.OnFormClosing(e);
        }

        private async Task<TiaPortalHost> GetHostAsync()
        {
            if (tiaHost == null)
            {
                tiaHost = new TiaPortalHost();
                try
                {
                    await tiaHost.StartAsync();
                }
                catch
                {
                    tiaHost.Dispose();
                    tiaHost = null;
                    throw;
                }
            }
            return tiaHost;
        }

        private void BtnSelectProject_Click(object sender, EventArgs e)
        {
            using (var dialog = new OpenFileDialog
            {
                Title = "TIA Portal Projesi Se\u00E7in",
                Filter = "TIA Portal Projesi (*.ap20;*.ap19;*.ap18;*.ap17)|*.ap20;*.ap19;*.ap18;*.ap17|T\u00FCm Dosyalar (*.*)|*.*",
                CheckFileExists = true,
            })
            {
                if (dialog.ShowDialog() != DialogResult.OK)
                    return;

                projectPath = dialog.FileName;
                lblProjectPath.Text = $"Proje: {Path.GetFileName(projectPath)}";
                lblSummary.Text = "";
                lblBlockSummary.Text = "";
                tvTags.Nodes.Clear();
                blockGroups = null;
                btnReadTags.Enabled = true;
                UpdateSaveButtonState();
            }
        }

        private async void BtnReadTags_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(projectPath))
            {
                MessageBox.Show("\u00D6nce bir proje se\u00E7in.", "Uyar\u0131",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            SetBusy(true);
            lblSummary.Text = "Proje a\u00E7\u0131l\u0131yor ve taglar okunuyor...";
            tvTags.Nodes.Clear();
            blockGroups = null;
            UpdateSaveButtonState();

            try
            {
                var host = await GetHostAsync();
                await host.Enqueue(svc => svc.OpenProject(projectPath));
                var sources = await host.Enqueue(svc => svc.ReadAllTagSources());

                foreach (var src in sources)
                {
                    string categoryTag = src.Category == "DB" ? "DB" : "Tablo";
                    string parentText = $"{src.SourceName,-35}  [{categoryTag}]  {src.Tags.Count,4} etiket";
                    var parentNode = new TreeNode(parentText);
                    parentNode.Tag = src;

                    foreach (var tag in src.Tags.OrderBy(t => AddressSortKey(t.LogicalAddress)))
                    {
                        string childText = $"  {tag.Name,-42} {tag.DataType,-15} {tag.LogicalAddress}";
                        var childNode = new TreeNode(childText);
                        childNode.Tag = tag;
                        parentNode.Nodes.Add(childNode);
                    }

                    tvTags.Nodes.Add(parentNode);
                    parentNode.Expand();
                }

                int totalTags = sources.Sum(s => s.TagCount);
                lblSummary.Text = $"{sources.Count} kaynak bulundu, toplam {totalTags} etiket";
                UpdateSaveButtonState();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Taglar okunurken hata:\n\n{ex.Message}",
                    "Hata", MessageBoxButtons.OK, MessageBoxIcon.Error);
                lblSummary.Text = "Hata: " + ex.Message;
                lblProjectPath.Text = "Proje: hen\u00FCz se\u00E7ilmedi";
            }
            finally
            {
                SetBusy(false);
            }
        }

        private void BtnSelectAll_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < tvTags.Nodes.Count; i++)
                SetNodeChecked(tvTags.Nodes[i], true);
        }

        private void BtnClearAll_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < tvTags.Nodes.Count; i++)
                SetNodeChecked(tvTags.Nodes[i], false);
        }

        private void BtnInvert_Click(object sender, EventArgs e)
        {
            isUpdatingCheckState = true;
            foreach (TreeNode parent in tvTags.Nodes)
                foreach (TreeNode child in parent.Nodes)
                    child.Checked = !child.Checked;
            isUpdatingCheckState = false;
            foreach (TreeNode parent in tvTags.Nodes)
                UpdateParentCheckState(parent);
            UpdateSaveButtonState();
        }

        private void BtnCollapseAll_Click(object sender, EventArgs e)
        {
            tvTags.CollapseAll();
        }

        private void BtnExpandAll_Click(object sender, EventArgs e)
        {
            tvTags.ExpandAll();
        }

        private async void BtnReadBlocks_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(projectPath))
            {
                MessageBox.Show("\u00D6nce bir proje se\u00E7in.", "Uyar\u0131",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            SetBusy(true);
            lblBlockSummary.Text = "Bloklar okunuyor...";
            blockGroups = null;

            try
            {
                var host = await GetHostAsync();
                await host.Enqueue(svc => svc.OpenProject(projectPath));
                var groups = await host.Enqueue(svc => svc.ReadAllCodeBlocks());
                blockGroups = groups;

                var oldBlockNodes = tvTags.Nodes.Cast<TreeNode>()
                    .Where(n => n.Tag is BlockGroupInfo).ToList();
                foreach (var n in oldBlockNodes)
                    tvTags.Nodes.Remove(n);

                foreach (var grp in groups)
                {
                    string parentText = $"{grp.GroupName,-35}  [{grp.BlockType}]  {grp.BlockCount,4} blok";
                    var parentNode = new TreeNode(parentText);
                    parentNode.Tag = grp;

                    foreach (var blk in grp.Blocks)
                    {
                        string childText = $"  {blk.Name,-42} {blk.Number,-6} {blk.ProgrammingLanguage,-8} {blk.Title}";
                        var childNode = new TreeNode(childText);
                        childNode.Tag = blk;
                        parentNode.Nodes.Add(childNode);
                    }

                    tvTags.Nodes.Add(parentNode);
                    parentNode.Expand();
                }

                int totalBlocks = groups.Sum(g => g.BlockCount);
                lblBlockSummary.Text = $"{groups.Count} grup, toplam {totalBlocks} blok bulundu";
                UpdateSaveButtonState();
            }
            catch (Exception ex)
            {
                MessageBox.Show($"Bloklar okunurken hata:\n\n{ex.Message}", "Hata",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                lblBlockSummary.Text = "Hata: " + ex.Message;
            }
            finally
            {
                SetBusy(false);
            }
        }

        private async void BtnExportBlockXml_Click(object sender, EventArgs e)
        {
            var checkedBlocks = GetCheckedBlocks();
            if (checkedBlocks.Count == 0)
            {
                MessageBox.Show("L\u00FCtfen en az bir blok se\u00E7in.", "Uyar\u0131",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            using (var dialog = new FolderBrowserDialog
            {
                Description = "Blok XML'lerinin kaydedilece\u011Fi klas\u00F6r\u00FC se\u00E7in",
            })
            {
                if (dialog.ShowDialog() != DialogResult.OK)
                    return;

                string folder = dialog.SelectedPath;
                SetBusy(true);
                SetStatus("Blok XML'leri kaydediliyor...");

                try
                {
                    var blocksCopy = checkedBlocks.ToList();
                    var host = await GetHostAsync();
                    int count = await host.Enqueue(svc =>
                    {
                        svc.ExportBlocksToXml(blocksCopy, folder);
                        return blocksCopy.Count;
                    });

                    SetStatus($"{count} blok XML -> {Path.GetFileName(folder)} kaydedildi.");

                    var result = MessageBox.Show(
                        $"{count} blok XML'i ba\u015Far\u0131yla kaydedildi.\n\nKlas\u00F6r\u00FC a\u00E7mak ister misiniz?",
                        "Ba\u015Far\u0131l\u0131", MessageBoxButtons.YesNo, MessageBoxIcon.Information);

                    if (result == DialogResult.Yes)
                        Process.Start(folder);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"XML kaydedilirken hata:\n\n{ex.Message}",
                        "Hata", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    SetBusy(false);
                }
            }
        }

        private async void BtnExportBlockData_Click(object sender, EventArgs e)
        {
            var checkedBlocks = GetCheckedBlocks();
            if (checkedBlocks.Count == 0)
            {
                MessageBox.Show("L\u00FCtfen en az bir blok se\u00E7in.", "Uyar\u0131",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            bool isExcel = rbXlsx.Checked;
            string filter = isExcel ? "Excel Dosyas\u0131 (*.xlsx)|*.xlsx" : "CSV Dosyas\u0131 (*.csv)|*.csv";
            string defaultName = isExcel ? "PLC_Blocks.xlsx" : "PLC_Blocks.csv";

            using (var dialog = new SaveFileDialog
            {
                Title = "Blok Bilgilerini Kaydet",
                Filter = filter,
                FileName = defaultName,
                DefaultExt = isExcel ? "xlsx" : "csv",
            })
            {
                if (dialog.ShowDialog() != DialogResult.OK)
                    return;

                string filePath = dialog.FileName;
                SetBusy(true);
                SetStatus("Blok bilgileri kaydediliyor...");

                try
                {
                    var sorted = checkedBlocks
                        .OrderBy(b => b.DeviceName)
                        .ThenBy(b => b.BlockType)
                        .ThenBy(b => b.Number)
                        .ToList();

                    if (isExcel)
                        await Task.Run(() => WriteBlockDataExcel(filePath, sorted));
                    else
                        await Task.Run(() => WriteBlockDataCsv(filePath, sorted));

                    SetStatus($"{sorted.Count} blok -> {Path.GetFileName(filePath)} kaydedildi.");

                    var result = MessageBox.Show(
                        $"{sorted.Count} blok bilgisi ba\u015Far\u0131yla kaydedildi.\n\nDosyay\u0131 a\u00E7mak ister misiniz?",
                        "Ba\u015Far\u0131l\u0131", MessageBoxButtons.YesNo, MessageBoxIcon.Information);

                    if (result == DialogResult.Yes)
                        Process.Start(filePath);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Kaydedilirken hata:\n\n{ex.Message}",
                        "Hata", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    SetBusy(false);
                }
            }
        }

        private void TvTags_AfterCheck(object sender, TreeViewEventArgs e)
        {
            if (isUpdatingCheckState) return;
            isUpdatingCheckState = true;

            if (e.Node.Nodes.Count > 0)
            {
                foreach (TreeNode child in e.Node.Nodes)
                    child.Checked = e.Node.Checked;
            }
            else if (e.Node.Parent != null)
            {
                UpdateParentCheckState(e.Node.Parent);
            }

            isUpdatingCheckState = false;
            UpdateSaveButtonState();
        }

        private void UpdateParentCheckState(TreeNode parentNode)
        {
            if (parentNode.Nodes.Count == 0) return;
            parentNode.Checked = parentNode.Nodes.Cast<TreeNode>().All(c => c.Checked);
        }

        private void SetNodeChecked(TreeNode node, bool check)
        {
            isUpdatingCheckState = true;
            node.Checked = check;
            foreach (TreeNode child in node.Nodes)
                child.Checked = check;
            isUpdatingCheckState = false;
            UpdateSaveButtonState();
        }

        private async void BtnSaveCsv_Click(object sender, EventArgs e)
        {
            var checkedTags = GetCheckedTags();
            if (checkedTags.Count == 0)
            {
                MessageBox.Show("L\u00FCtfen en az bir tag se\u00E7in.", "Uyar\u0131",
                    MessageBoxButtons.OK, MessageBoxIcon.Warning);
                return;
            }

            bool isExcel = rbXlsx.Checked;
            string filter = isExcel ? "Excel Dosyas\u0131 (*.xlsx)|*.xlsx" : "CSV Dosyas\u0131 (*.csv)|*.csv";
            string defaultName = isExcel ? "PLC_Tags.xlsx" : "PLC_Tags.csv";

            using (var dialog = new SaveFileDialog
            {
                Title = "Dosyay\u0131 Kaydet",
                Filter = filter,
                FileName = defaultName,
                DefaultExt = isExcel ? "xlsx" : "csv",
            })
            {
                if (dialog.ShowDialog() != DialogResult.OK)
                    return;

                string filePath = dialog.FileName;
                SetBusy(true);
                SetStatus("Kaydediliyor...");

                try
                {
                    if (isExcel)
                        await Task.Run(() => WriteExcel(filePath, checkedTags));
                    else
                        await Task.Run(() => WriteCsv(filePath, checkedTags));

                    SetStatus($"{checkedTags.Count} etiket -> {Path.GetFileName(filePath)} kaydedildi.");

                    var result = MessageBox.Show(
                        $"{checkedTags.Count} etiket ba\u015Far\u0131yla kaydedildi.\n\nDosyay\u0131 a\u00E7mak ister misiniz?",
                        "Ba\u015Far\u0131l\u0131", MessageBoxButtons.YesNo, MessageBoxIcon.Information);

                    if (result == DialogResult.Yes)
                        Process.Start(filePath);
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"Kaydedilirken hata:\n\n{ex.Message}",
                        "Hata", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    SetBusy(false);
                }
            }
        }

        private List<TagInfo> GetCheckedTags()
        {
            var result = new List<TagInfo>();
            foreach (TreeNode parent in tvTags.Nodes)
                foreach (TreeNode child in parent.Nodes)
                    if (child.Checked && child.Tag is TagInfo tag)
                        result.Add(tag);
            return result;
        }

        private List<BlockInfo> GetCheckedBlocks()
        {
            var result = new List<BlockInfo>();
            foreach (TreeNode parent in tvTags.Nodes)
                foreach (TreeNode child in parent.Nodes)
                    if (child.Checked && child.Tag is BlockInfo blk)
                        result.Add(blk);
            return result;
        }

        private void UpdateSaveButtonState()
        {
            int tagCount = GetCheckedTags().Count;
            btnSaveCsv.Enabled = tagCount > 0;
            btnSaveCsv.Text = tagCount > 0 ? $"Kaydet ({tagCount})" : "Kaydet";

            int blockCount = GetCheckedBlocks().Count;
            bool hasBlocks = blockGroups != null && blockGroups.Count > 0;
            btnExportBlockXml.Enabled = hasBlocks && blockCount > 0;
            btnExportBlockData.Enabled = hasBlocks && blockCount > 0;
        }

        private static string AddressSortKey(string address)
        {
            if (string.IsNullOrEmpty(address)) return "9";
            string prefix;
            if (address.Contains("%I") || address.StartsWith("I"))
                prefix = "0";
            else if (address.Contains("%Q") || address.StartsWith("Q"))
                prefix = "1";
            else if (address.Contains("%M") || address.StartsWith("M"))
                prefix = "2";
            else
                prefix = "3";
            return prefix + Regex.Replace(address, @"\d+", m => m.Value.PadLeft(10, '0'));
        }

        private void SetBusy(bool busy)
        {
            btnSelectProject.Enabled = !busy;
            btnReadTags.Enabled = !busy && !string.IsNullOrEmpty(projectPath);
            tvTags.Enabled = !busy;
            btnSelectAll.Enabled = !busy;
            btnClearAll.Enabled = !busy;
            btnInvert.Enabled = !busy;
            btnCollapseAll.Enabled = !busy;
            btnExpandAll.Enabled = !busy;
            btnReadBlocks.Enabled = !busy && !string.IsNullOrEmpty(projectPath);
            btnExportBlockXml.Enabled = !busy && blockGroups != null && GetCheckedBlocks().Count > 0;
            btnExportBlockData.Enabled = !busy && blockGroups != null && GetCheckedBlocks().Count > 0;
            rbCsv.Enabled = !busy;
            rbXlsx.Enabled = !busy;
            progressBar.Visible = busy;
            this.Cursor = busy ? Cursors.WaitCursor : Cursors.Default;
            if (!busy)
                UpdateSaveButtonState();
        }

        private void SetStatus(string text)
        {
            if (this.InvokeRequired)
            {
                this.BeginInvoke((MethodInvoker)(() => SetStatus(text)));
                return;
            }
            lblStatus.Text = text;
        }

        private static void WriteCsv(string path, List<TagInfo> tags)
        {
            var sb = new StringBuilder();
            sb.AppendLine("DeviceName;TableName;Category;TagName;DataType;LogicalAddress;Comment");

            foreach (var tag in tags.OrderBy(t => t.DeviceName).ThenBy(t => t.TableName).ThenBy(t => AddressSortKey(t.LogicalAddress)))
            {
                sb.AppendLine($"{EscapeCsv(tag.DeviceName)};{EscapeCsv(tag.TableName)};{EscapeCsv(tag.Category)};{EscapeCsv(tag.Name)};{EscapeCsv(tag.DataType)};{EscapeCsv(tag.LogicalAddress)};{EscapeCsv(tag.Comment)}");
            }

            File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
        }

        private static void WriteExcel(string path, List<TagInfo> tags)
        {
            var sorted = tags.OrderBy(t => t.DeviceName).ThenBy(t => t.TableName).ThenBy(t => AddressSortKey(t.LogicalAddress)).ToList();

            using (var workbook = new XLWorkbook())
            {
                var ws = workbook.Worksheets.Add("PLC Tags");

                string[] headers = { "DeviceName", "TableName", "Category", "TagName", "DataType", "LogicalAddress", "Comment" };
                for (int c = 0; c < headers.Length; c++)
                    ws.Cell(1, c + 1).Value = headers[c];

                var headerRange = ws.Range(1, 1, 1, headers.Length);
                headerRange.Style.Font.Bold = true;
                headerRange.Style.Fill.BackgroundColor = XLColor.FromArgb(0, 110, 127);
                headerRange.Style.Font.FontColor = XLColor.White;

                for (int i = 0; i < sorted.Count; i++)
                {
                    var tag = sorted[i];
                    int row = i + 2;
                    ws.Cell(row, 1).Value = tag.DeviceName;
                    ws.Cell(row, 2).Value = tag.TableName;
                    ws.Cell(row, 3).Value = tag.Category;
                    ws.Cell(row, 4).Value = tag.Name;
                    ws.Cell(row, 5).Value = tag.DataType;
                    ws.Cell(row, 6).Value = tag.LogicalAddress;
                    ws.Cell(row, 7).Value = tag.Comment;
                }

                ws.Columns().AdjustToContents(10, 60);
                workbook.SaveAs(path);
            }
        }

        private static string EscapeCsv(string value)
        {
            if (string.IsNullOrEmpty(value)) return "";
            if (value.Contains(';') || value.Contains('"') || value.Contains('\n') || value.Contains('\r'))
                return $"\"{value.Replace("\"", "\"\"")}\"";
            return value;
        }

        private static void WriteBlockDataCsv(string path, List<BlockInfo> blocks)
        {
            var sb = new StringBuilder();
            sb.AppendLine("DeviceName;BlockType;Number;Name;ProgrammingLanguage;Title;Comment");

            foreach (var b in blocks)
            {
                sb.AppendLine($"{EscapeCsv(b.DeviceName)};{EscapeCsv(b.BlockType)};{b.Number};{EscapeCsv(b.Name)};{EscapeCsv(b.ProgrammingLanguage)};{EscapeCsv(b.Title)};{EscapeCsv(b.Comment)}");
            }

            File.WriteAllText(path, sb.ToString(), Encoding.UTF8);
        }

        private static void WriteBlockDataExcel(string path, List<BlockInfo> blocks)
        {
            using (var workbook = new XLWorkbook())
            {
                var ws = workbook.Worksheets.Add("PLC Blocks");

                string[] headers = { "DeviceName", "BlockType", "Number", "Name", "ProgrammingLanguage", "Title", "Comment" };
                for (int c = 0; c < headers.Length; c++)
                    ws.Cell(1, c + 1).Value = headers[c];

                var headerRange = ws.Range(1, 1, 1, headers.Length);
                headerRange.Style.Font.Bold = true;
                headerRange.Style.Fill.BackgroundColor = XLColor.FromArgb(0, 110, 127);
                headerRange.Style.Font.FontColor = XLColor.White;

                for (int i = 0; i < blocks.Count; i++)
                {
                    var b = blocks[i];
                    int row = i + 2;
                    ws.Cell(row, 1).Value = b.DeviceName;
                    ws.Cell(row, 2).Value = b.BlockType;
                    ws.Cell(row, 3).Value = b.Number;
                    ws.Cell(row, 4).Value = b.Name;
                    ws.Cell(row, 5).Value = b.ProgrammingLanguage;
                    ws.Cell(row, 6).Value = b.Title;
                    ws.Cell(row, 7).Value = b.Comment;
                }

                ws.Columns().AdjustToContents(10, 60);
                workbook.SaveAs(path);
            }
        }
    }
}
