using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Cosmos.Debug.GDB {
    public partial class FormMain : Form {
        protected class AsmLine {
            public readonly UInt32 mAddr;
            public readonly string mLabel;
            public readonly string mOp;
            public readonly string mData = string.Empty;

            public AsmLine(string aInput) {
                //"0x0056d2b9 <_end_data+0>:\tmov    DWORD PTR ds:0x550020,ebx\n"
                var s = GDB.Unescape(aInput);
				var xSplit1 = s.Split(Global.TabSeparator, StringSplitOptions.RemoveEmptyEntries);

				var xSplit2 = xSplit1[0].Split(Global.SpaceSeparator, StringSplitOptions.RemoveEmptyEntries);
                mAddr = Global.FromHexWithLeadingZeroX(xSplit2[0]);
                string xLabel;
                if (xSplit2.Length > 1) {
                    xLabel = xSplit2[1];
                }

				xSplit2 = xSplit1[1].Split(Global.SpaceSeparator, StringSplitOptions.RemoveEmptyEntries);
                mOp = xSplit2[0];
                if (xSplit2.Length > 1) {
                    for (int j = 1; j < xSplit2.Length; j++) {
                        mData += xSplit2[j] + " ";
                    }
                    mData = mData.TrimEnd();
                }
            }

            public override string ToString() {
                // First char reserved for breakpoint (*)
                return "  " + mAddr.ToString("X8") + ":  " + mOp + " " + mData.TrimEnd();
            }
        }

        protected string mFuncName;

        protected void OnGDBResponse(GDB.Response aResponse) {
            try {
                Windows.mLogForm.Log(aResponse);
                var xCmdLine = aResponse.Command.ToLower();
                if (xCmdLine == "info registers") {
                    Windows.mRegistersForm.UpdateRegisters(aResponse);
					Windows.UpdateAfterRegisterUpdate();
                } else if (xCmdLine == "") {
                    // This happens on initial connect
                } else {
					var xCmdParts = xCmdLine.Split(Global.SpaceSeparator);
                    var xCmd = xCmdParts[0];
                    if (xCmd == "disassemble") {
                        OnDisassemble(aResponse);
                    } else if (xCmd == "symbol-file") { // nothing
                    } else if (xCmd == "set") { // nothing
                    } else if (xCmd == "target") { // nothing
                    } else if (xCmd == "delete") {
                        Windows.mBreakpointsForm.OnDelete(aResponse);
                    } else if ((xCmd == "stepi") || (xCmd == "nexti") || (xCmd == "continue")) {
                        lablRunning.Text = "Stopped";
                        Windows.UpdateAllWindows();
                    } else if (xCmd == "where") {
                        Windows.mCallStackForm.OnWhere(aResponse);
                    } else if (xCmd == "break") {
                        Windows.mBreakpointsForm.OnBreak(aResponse);
					} else if (xCmd.StartsWith("x/")) {
						Windows.mWatchesForm.OnWatchUpdate(aResponse);
                    } else {
                        throw new Exception("Unrecognized command response: " + aResponse.Command);
                    }
                }
            } catch (Exception e) {
                MessageBox.Show("Exception: " + e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
            }
        }

        public void Disassemble(string aLabel) {
            textCurrentFunction.Text = string.Empty;
            textCurrentFunction.Visible = true;
            Global.GDB.SendCmd("disassemble " + aLabel.TrimEnd());
        }

        protected void OnDisassemble(GDB.Response xResponse) {
            var xResult = xResponse.Text;
            lboxDisassemble.BeginUpdate();
            try {
                lboxDisassemble.Items.Clear();

                // In some cases GDB might return no results. This is common when no symbols are loaded.
                if (xResult.Count > 0) {
                    // Get function name
					var xSplit = GDB.Unescape(xResult[0]).Split(Global.SpaceSeparator, StringSplitOptions.RemoveEmptyEntries);
                    mFuncName = xSplit[xSplit.Length - 1];
                    textCurrentFunction.Text = mFuncName;

                    // 1 and -2 to eliminate header and footer line
                    for (int i = 1; i <= xResult.Count - 2; i++) {
                        lboxDisassemble.Items.Add(new AsmLine(xResult[i]));
                    }
                }
            } finally {
                lboxDisassemble.EndUpdate();
            }
        }

        public void SetEIP(UInt32 aAddr) {
            foreach (AsmLine x in lboxDisassemble.Items) {
                if (x.mAddr == aAddr) {
                    lboxDisassemble.SelectedItem = x;
                    break;
                }
            }
        }

        public FormMain() {
            InitializeComponent();
        }

        // TODO
        // watches
        // View stack
        // If close without connect, it wipes out the settings file

        private void mitmExit_Click(object sender, EventArgs e) {
            Close();
        }

        private void mitmStepInto_Click(object sender, EventArgs e) {
            lablRunning.Text = "Running";
            Global.GDB.SendCmd("stepi");
        }

        private void mitmStepOver_Click(object sender, EventArgs e) {
            lablRunning.Text = "Running";
            Global.GDB.SendCmd("nexti");
        }

        protected void Connect(int aRetry) {
            if (!mitmConnect.Enabled) {
                return;
            }
            mitmConnect.Enabled = false;
			butnConnect.Enabled = false;

            Windows.CreateForms();
            Global.AsmSource = new AsmFile(Path.Combine(Settings.OutputPath, Settings.AsmFile));
            Global.GDB = new GDB(aRetry, OnGDBResponse);
            if (Global.GDB.Connected) {
                lablConnected.Visible = true;
                lablRunning.Visible = true;
                lablRunning.Text = "Stopped";
                Settings.InitWindows();
                Windows.UpdateAllWindows();
            }
        }

        private void mitmConnect_Click(object sender, EventArgs e) {
            Connect(30);
        }

        private void mitmRefresh_Click(object sender, EventArgs e) {
            Windows.UpdateAllWindows();
        }

        private void continueToolStripMenuItem_Click(object sender, EventArgs e) {
            lablRunning.Text = "Running";
            Global.GDB.SendCmd("continue");
        }

        private void mitmMainViewCallStack_Click(object sender, EventArgs e) {
            Windows.Show(Windows.mCallStackForm);
        }

        private void mitmMainViewWatches_Click(object sender, EventArgs e) {
            Windows.Show(Windows.mWatchesForm);
        }

        protected FormWindowState mLastWindowState = FormWindowState.Normal;
        private void FormMain_Resize(object sender, EventArgs e) {
            if (WindowState == FormWindowState.Minimized) {
                // Window is being minimized
                Windows.Hide();
            } else if ((mLastWindowState == FormWindowState.Minimized) && (WindowState != FormWindowState.Minimized)) {
                // Window is being restored
                Windows.Reshow();
            }
            mLastWindowState = WindowState;
        }

        private void mitmViewLog_Click(object sender, EventArgs e) {
            Windows.Show(Windows.mLogForm);
        }

        private void FormMain_Load(object sender, EventArgs e) {
            Windows.mMainForm = this;
        }

        private void mitmViewBreakpoints_Click(object sender, EventArgs e) {
            Windows.Show(Windows.mBreakpointsForm);
        }

        private void mitmRegisters_Click(object sender, EventArgs e) {
            Windows.Show(Windows.mRegistersForm);
        }

        private void FormMain_Shown(object sender, EventArgs e) {
            // Dont put this in load. Load happens in main call from Main.cs and on exceptions just
            // goes out, no message. 
            // Also we want to show other forms after main form, not before.
            // We also only want to run this once, not on each possible show.
            if (mitmConnect.Enabled) {
                if (Settings.AutoConnect) {
                    Connect(30);
                }
            }
        }

        private void BringWindowsToTop() {
            mIgnoreNextActivate = true;
            foreach (var xWindow in Windows.mForms) {
                if (xWindow == this) {
                    continue;
                }
                if (xWindow.Visible) {
                    xWindow.Activate();
                }
            }
            this.Activate();
        }

        private void mitmWindowsToForeground_Click(object sender, EventArgs e) {
            BringWindowsToTop();
        }

        private bool mIgnoreNextActivate = false;
        private void FormMain_Activated(object sender, EventArgs e) {
            // Necessary else we get looping becuase BringWindowsToTop reactivates this.
            if (mIgnoreNextActivate) {
                mIgnoreNextActivate = false;
            } else {
                BringWindowsToTop();
            }
        }

        private void mitmSave_Click(object sender, EventArgs e) {
            Windows.SavePositions();
            Settings.Save();
        }

        private void mitemDisassemblyAddBreakpoint_Click(object sender, EventArgs e) {
            var x = (AsmLine)lboxDisassemble.SelectedItem;
            if (x != null) {
                Windows.mBreakpointsForm.AddBreakpoint("*0x" + x.mAddr.ToString("X8"));
            }
        }

        private void mitmCopyToClipboard_Click(object sender, EventArgs e) {
            var x = (AsmLine)lboxDisassemble.SelectedItem;
            Clipboard.SetText(x.ToString());
        }

        private void FormMain_FormClosing(object sender, FormClosingEventArgs e) {
        }

        private void butnBreakpoints_Click(object sender, EventArgs e) {
            mitmViewBreakpoints.PerformClick();
        }

		private void textCurrentFunction_TextChanged(object sender, EventArgs e) {
			base.OnTextChanged(e);

			using (Graphics g = textCurrentFunction.CreateGraphics()) {
				SizeF size = g.MeasureString(textCurrentFunction.Text, textCurrentFunction.Font);
				textCurrentFunction.Width = (int)size.Width + textCurrentFunction.Padding.Horizontal;
			}
		}
    }
}