// Asm4GCN Assembler by Ryan S White (sunsetquest) http://www.codeproject.com/Articles/872477/Assembler-for-AMD-s-GCN-GPU
// Released under the Code Project Open License (CPOL) http://www.codeproject.com/info/cpol10.aspx 
// Source & Executable can be used in commercial applications and is provided AS-IS without any warranty.
using OpenClWithGcnNS;
using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using FastColoredTextBoxNS;
using AC = AutocompleteMenuNS;          // for autocompleteMenu1 
using System.Runtime.InteropServices;   // for NotepadHelper

namespace Asm4GcnGUI
{
    public partial class frmMain : Form
    {
        OpenClWithGCN gcn = new OpenClWithGCN();
        bool changeSinceLastSave;
        string curFileName;
        string outputFolder = Path.GetTempPath() + "\\OpenCLwithGCNOutput";
        FastColoredTextBox lastSelectedTextControl;
        Regex S_InstNames, V_InstNames, O_InstNames;
        
        public frmMain()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            string gpuInfo = OpenClWithGCN.CheckGPUAndVersion();
            if (gpuInfo.Length > 0)
                txtOutput.AppendText(gpuInfo);

            if (gcn.env == null)
                txtOutput.AppendText("Unable to find an AMD platform.");

            /////////////////// Copy needed exe/dll files to output folder ///////////////////
            if (!Directory.Exists(outputFolder))
                Directory.CreateDirectory(outputFolder);
            //if (!File.Exists(outputFolder + "\\OpenClWithGCN.dll"))
                File.Copy("OpenClWithGCN.dll", outputFolder + "\\OpenClWithGCN.dll", true);
            //if (!File.Exists(outputFolder + "\\Asm4GCN.exe"))
                File.Copy("Asm4GCN.exe", outputFolder + "\\Asm4GCN.exe", true);
            //if (!File.Exists(outputFolder + "\\NOpenCL.dll"))
                File.Copy("NOpenCL.dll", outputFolder + "\\NOpenCL.dll", true);

            /////////////////// Setup AutoCompletes for GCN asm window  //////////////////////
            autocompleteMenu1.MaximumSize = new Size(700, 3000);
            var columnWidth = new int[] { 125, 575 };

            foreach (var i in GcnTools.ISA_DATA.ISA_Insts)
            {
                int img = i.name[0]=='s'?0:i.name[0]=='v'?1:i.name[0]=='d'?2:i.name[0]=='b'?3:4;
                autocompleteMenu1.AddItem(new AC.MulticolumnAutocompleteItem(new[] { i.name, i.iSANotes }, 
                    i.name) { ColumnWidth = columnWidth, ImageIndex = img });
            }
            autocompleteMenu1.AddItem(new AC.MulticolumnAutocompleteItem(new[] { 
                "__asm4GCN", "Begins a Asm4GCN Block" }
                , "__asm4GCN") { ColumnWidth = columnWidth, ImageIndex = 4 }
            );
            autocompleteMenu1.AddItem(new AC.MulticolumnAutocompleteItem(new[] { 
                "__kernel", "Begins an OpenCL Block" }, 
                "__kernel") { ColumnWidth = columnWidth, ImageIndex = 4 }
            );

            changeSinceLastSave = false;
            Text = "Welcome to " + Application.ProductName;
        }

        /// <summary>
        /// Wraps the text in the Asm4GCN window in a namespace, class, and constant string.
        /// </summary>
        private string GetEncapsulatedAsmText()
        {
            string modifiedAsm = txtAsm.Text.Replace(@"""", @"""""");
            return @"namespace GCN_NS { static class Code { public const string DevCode = @""" + modifiedAsm + @""";}}";
        }

        private void CompileAndRun(object sender, EventArgs args)
        {
            // lets first clear the output window so we can see the progress
            txtOutput.Clear();
            Refresh(); //force txtOutput to update
            String exePathAndName = String.Format(@"{0}\{1}.exe", outputFolder, 
                Path.GetFileNameWithoutExtension(curFileName ?? "Output"));

            // Build and if successful then lets tart the program
            if (Build(exePathAndName))
            {
                ProcessStartInfo startInfo = new ProcessStartInfo("cmd", @"/C """ + exePathAndName + @""" & pause" ) ;
                try
                {
                    Process.Start(startInfo);
                }
                catch (Exception e)
                {
                    txtOutput.Text += "ERROR: unable to launch " + exePathAndName + ". Details: " + e.Message;
                }
            }
        }

        private bool Build(string exePathAndName)
        {
            bool enableVSDebug = enableVisualStudioDebugToolStripMenuItem.Checked; // WARNING: Leaves temp files behind.
            if (enableVSDebug)
                txtOutput.AppendText("INFO: compiled with debug info - temp files will not be cleaned up.\r\n");

            bool success = gcn.GcnCompile(txtAsm.Text);

            if (string.IsNullOrWhiteSpace(gcn.env.lastMessage))
                txtOutput.AppendText("INFO: (no Errors/Warnings in GCN kernel)\r\n");
            else
                txtOutput.AppendText(gcn.env.lastMessage);

            if (success)
            {
                var results = (new Microsoft.CSharp.CSharpCodeProvider()).CompileAssemblyFromSource(
                new CompilerParameters()
                {
                    GenerateInMemory = true, // note: this is not really "in memory" 
                    GenerateExecutable = true,
                    CompilerOptions = " /platform:x86",
                    OutputAssembly = exePathAndName,
                    IncludeDebugInformation = enableVSDebug,  // Debug information - not working some reason
                    TempFiles = new TempFileCollection(outputFolder, enableVSDebug/*keep temp files*/), 
                    //TreatWarningsAsErrors = false,
                    WarningLevel = 3,
                    ReferencedAssemblies = { "mscorlib.dll", "System.dll", "System.Core.dll", "System.Data.dll", 
                        "System.Data.Linq.dll", "System.Xml.dll", "System.Xml.Linq.dll", "NOpenCL.dll", 
                        "OpenClWithGCN.dll" }
                }
                , GetEncapsulatedAsmText(), txtHost.Text);

                foreach (CompilerError error in results.Errors)
                    txtOutput.AppendText(String.Format("{0} ({1}) Line:{2}: {3}\r\n", 
                        error.IsWarning ? "WARN: " : "ERROR: ", error.ErrorNumber, error.Line, error.ErrorText));

                if (results.Errors.HasErrors)
                    success = false;
                else
                    txtOutput.AppendText("INFO: (no C# errors found in host code)\r\n");

                results.TempFiles.Delete();
            }

            return success;
        }

        private void tsOpen_Click(object sender, EventArgs e)
        {
            CheckSaveBeforeClosingCurrent();

            openFileDialog1.Title = "What file do you with to open?";
            if (openFileDialog1.ShowDialog() == DialogResult.OK)
                LoadFromFile(openFileDialog1.FileName);
        }

        private void CheckSaveBeforeClosingCurrent()
        {
            if (changeSinceLastSave)
                if (MessageBox.Show("Do you want to save?", "", MessageBoxButtons.YesNo, MessageBoxIcon.Question) 
                    == DialogResult.Yes)
                {
                    if (String.IsNullOrEmpty(curFileName))
                        PromptAndSaveAs();
                    else
                        SaveToFile(curFileName);
                }
        }

        private void SaveToFile(string fileName)
        {
            File.WriteAllText(fileName, GetEncapsulatedAsmText() + 
                "\r\n///////// HOST ///////// (do not modify this line)\r\n" +
                txtHost.Text );
            changeSinceLastSave = false;
            Text = Path.GetFileName(fileName) + " - " + Application.ProductName;
        }

        private void LoadFromFile(string fileName)
        {
            string file;
            try
            {
                file = File.ReadAllText(openFileDialog1.FileName);
            }
            catch (Exception other)
            {
                MessageBox.Show(other.Message);
                return;
            }

            Match m = Regex.Match( file, @"^" +
            @".*?public const string DevCode = @""(?<1>.*?)"";}}" + // Grab the device code
            @"\r\n\s*///////// HOST /////////\s*\(do not modify this line\)\s*\r\n" + // End marker for device code
            @"(?<2>.*?)$", RegexOptions.Singleline); // Grab the host code
            
            if (m.Groups[1].Success && m.Groups[1].Success)
            {
                txtAsm.Text = m.Groups[1].Value;
                txtHost.Text = m.Groups[2].Value;
            }
            else // if we cannot pull apart the file then just use host window.
            {
                txtHost.Text = file;
                txtAsm.Clear();
            }

            changeSinceLastSave = false;
            curFileName = fileName;
            Text = Path.GetFileName(curFileName) + " - " + Application.ProductName;
        }

        private void tsSave_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(curFileName))
                PromptAndSaveAs();
            else
                SaveToFile(curFileName);
        }

        private void PromptAndSaveAs()
        {
            saveFileDialog1.Title = "Please choose a filename to save to...";
            if (saveFileDialog1.ShowDialog() == DialogResult.OK)
            {
                curFileName = saveFileDialog1.FileName;
                SaveToFile(curFileName);
            }
        }

        private void saveAsToolStripButton_Click(object sender, EventArgs e)
        {
            PromptAndSaveAs();
        }

        private void newToolStripButton_Click(object sender, EventArgs e)
        {
            // make sure there is nothing to save first
            CheckSaveBeforeClosingCurrent();

            // load default text
            ComponentResourceManager resources = new ComponentResourceManager(typeof(frmMain));
            txtAsm.Text = resources.GetString("txtAsm.Text");
            txtHost.Text = resources.GetString("txtHost.Text");
            changeSinceLastSave = false;
            curFileName = null;
            Text = Application.ProductName;
        }

        private void tsPrint_Click(object sender, EventArgs e)
        {
            PrintDialogSettings printSettings = new PrintDialogSettings() { ShowPrintPreviewDialog = true};
            if (tabControl1.SelectedIndex == 0)
                txtHost.Print(printSettings);
            else
                txtAsm.Print(printSettings);
        }

        private void setUserControlAsCurrent(object sender, EventArgs e)
        {
            lastSelectedTextControl = (FastColoredTextBox)sender;
        }

        private void cutToolStripButton_Click(object sender, EventArgs e)
        {
            if (lastSelectedTextControl.SelectedText != "")
                lastSelectedTextControl.Cut();
        }

        private void copyToolStripButton_Click(object sender, EventArgs e)
        {
            if (lastSelectedTextControl.SelectionLength > 0)
                lastSelectedTextControl.Copy();
        }

        private void pasteToolStripButton_Click(object sender, EventArgs e)
        {
            // Source: http://msdn.microsoft.com/en-us/library/system.windows.forms.textboxbase.copy(v=vs.110).aspx
            if (Clipboard.GetDataObject().GetDataPresent(DataFormats.Text) == true)
            {
                if (lastSelectedTextControl.SelectionLength > 0)
                        lastSelectedTextControl.SelectionStart = lastSelectedTextControl.SelectionStart 
                            + lastSelectedTextControl.SelectionLength;
                lastSelectedTextControl.Paste();
            }
        }

        Style GreenStyle = new TextStyle(Brushes.Green, null, FontStyle.Italic);
        Style S_InstNamesStyle = new TextStyle(Brushes.Blue, null, FontStyle.Bold);
        Style V_InstNamesStyle = new TextStyle(Brushes.IndianRed, null, FontStyle.Bold);
        Style O_InstNamesStyle = new TextStyle(Brushes.DarkSlateBlue, null, FontStyle.Bold);
        Style S_OppStyle = new TextStyle(Brushes.Blue, null, FontStyle.Italic);
        Style V_OppStyle = new TextStyle(Brushes.IndianRed, null, FontStyle.Italic);
        Style constantStyle = new TextStyle(Brushes.DarkGreen, null, FontStyle.Bold);
        Style TTTStyle = new TextStyle(Brushes.Purple, null, FontStyle.Italic | FontStyle.Bold);
        Style CommandsStyle = new TextStyle(Brushes.DarkGoldenrod, null, FontStyle.Italic);
        Style RegistersStyle = new TextStyle(Brushes.DarkCyan, null, FontStyle.Bold);
        Style LabelStyle = new TextStyle(Brushes.DarkOrange, null, FontStyle.Underline | FontStyle.Bold);
        Style DefineStyle = new TextStyle(Brushes.Black, null, FontStyle.Italic | FontStyle.Bold);
        
        private void txtAsm_TextChanged(object sender, TextChangedEventArgs e)
        {
            Range range = e.ChangedRange; // txtAsm.VisibleRange;

            // Text Template Transformations
            range.ClearStyle(TTTStyle);
            range.SetStyle(TTTStyle, @"\[\[.*?\]\]");

            // Comments
            range.ClearStyle(GreenStyle);
            range.SetStyle(GreenStyle, @"//.*$", RegexOptions.Multiline);
            range.SetStyle(GreenStyle, @"(/\*.*?\*/)|(/\*.*)", RegexOptions.Singleline);
            range.SetStyle(GreenStyle, @"(/\*.*?\*/)|(.*\*/)", RegexOptions.Singleline | RegexOptions.RightToLeft);

            // Commands
            range.ClearStyle(CommandsStyle);
            range.SetStyle(CommandsStyle, @"(?<!=[a-z0-9_])([sv](1|2|4|8|16)[iufb]|free|#define)(?=\s)");

            // Registers
            range.ClearStyle(RegistersStyle);
            range.SetStyle(RegistersStyle, @"(?<=[\s,;])[sv](\d+|\[\d+:\d+\])(?=[\s,;])");

            // Labels
            range.ClearStyle(LabelStyle);
            range.SetStyle(LabelStyle, @"(?<=[\s,;])(@[a-zA-Z_][a-zA-Z0-9_]*|[a-zA-Z_][a-zA-Z0-9_]*:)(?=[\s,;])");
            
            // Defines var names  (i.e.  _myDefine_ )
            range.ClearStyle(DefineStyle);
            range.SetStyle(DefineStyle, @"(?<=[\ ,;])(_[a-zA-Z0-9_]+_)(?!=[a-zA-Z0-9_]+)");

            // initialize S_InstNames, V_InstNames, and O_InstNames
            if (S_InstNames == null)
            {
                StringBuilder s = new StringBuilder();
                StringBuilder v = new StringBuilder();
                StringBuilder o = new StringBuilder();
                s.Append(@"\s(");
                v.Append(@"\s(");
                o.Append(@"\s(");
                foreach (var item in GcnTools.ISA_DATA.ISA_Insts)
                    if (item.name[0] == 's')
                        s.Append(item.name + "|");
                    else if (item.name[0] == 'v')
                        v.Append(item.name + "|");
                    else
                        o.Append(item.name + "|");
                s.Length--;
                v.Length--;
                o.Length--;

                s.Append(@")\s");
                v.Append(@")\s");
                o.Append(@")\s");

                S_InstNames = new Regex(s.ToString());
                V_InstNames = new Regex(v.ToString());
                O_InstNames = new Regex(o.ToString());
            }

            range.ClearStyle(S_InstNamesStyle);
            range.ClearStyle(V_InstNamesStyle);
            range.ClearStyle(O_InstNamesStyle);
            range.SetStyle(S_InstNamesStyle, S_InstNames);
            range.SetStyle(V_InstNamesStyle, V_InstNames);
            range.SetStyle(O_InstNamesStyle, O_InstNames);

            range.ClearStyle(S_OppStyle);
            range.ClearStyle(V_OppStyle);
            range.ClearStyle(constantStyle);
            range.SetStyle(S_OppStyle, @"[\s,][sS](\bin+|\[\bin+:\bin+\])[\s,]");
            range.SetStyle(V_OppStyle, @"[\s,][vV](\bin+|\[\bin+:\bin+\])[\s,]");
            range.SetStyle(constantStyle, @"[\s,](?i:" +
                @"(?:0(?<1>x)(?<2>[0-9a-f]+))|" + // hex values 0x12AB
                @"(?:0(?<1>o)(?<2>[0-9a-f]+))|" + // oct values 0o7564
                @"(?:0(?<1>b)(?<2>[0-9a-f]+))|" + // bin values 0b1101010
                @"(?<2>-?(?<1>\bin)\bin*(?:E[+-]?\bin+)?)" +// exponent int (nnnExx, nnnE+xx, and nnnE-xx)
                @")[\s,]");


            // if we are going from a unsaved state to saved state lets update the title
            if (!changeSinceLastSave && !string.IsNullOrEmpty(curFileName))
                this.Text = Path.GetFileName(curFileName) + "(unsaved) - " + Application.ProductName;
            changeSinceLastSave = true;
        }

        Style InfoStyle = new TextStyle(Brushes.Black, Brushes.LightGreen, FontStyle.Bold);
        Style WarnStyle = new TextStyle(Brushes.Black, Brushes.LightYellow, FontStyle.Bold);
        Style ErrorStyle = new TextStyle(Brushes.Black, Brushes.Salmon, FontStyle.Bold);
        private void txtOutput_TextChanged(object sender, TextChangedEventArgs e)
        {
            Range range = (sender as FastColoredTextBox).VisibleRange;

            //range.ClearStyle(GreenStyle);
            e.ChangedRange.SetStyle(InfoStyle, @"^INFO:", RegexOptions.Multiline);
            e.ChangedRange.SetStyle(WarnStyle, @"^WARN:", RegexOptions.Multiline);
            e.ChangedRange.SetStyle(ErrorStyle, @"^ERROR:", RegexOptions.Multiline);
        }


        MarkerStyle SameWordsStyle = new MarkerStyle(new SolidBrush(Color.FromArgb(40, Color.Gray)));
        /// <summary>This function is used to highlight matching words from where the cursor is at.</summary>
        private void txtAsm_SelectionChangedDelayed(object sender, EventArgs e)
        {
            var fctb = sender as FastColoredTextBox;

            fctb.VisibleRange.ClearStyle(SameWordsStyle);

            if (!fctb.Selection.IsEmpty)
                return; //user selected diapason

            //get fragment around caret
            var fragment = fctb.Selection.GetFragment(@"\w");
            string text = fragment.Text;
            if (text.Length == 0)
                return;
            //highlight same words
            var ranges =
                fctb.VisibleRange.GetRanges("\\b" + text + "\\b");
            //if (ranges.Length > 1)
            foreach (var r in ranges)
                r.SetStyle(SameWordsStyle);
        }


        private void txtHost_TextChanged(object sender, TextChangedEventArgs e)
        {
            // if we are going from a unsaved state to saved state lets update the title
            if (!changeSinceLastSave && !String.IsNullOrEmpty(curFileName))
                this.Text = Path.GetFileName(curFileName) + "(unsaved) - " + Application.ProductName;
        }

        private void codeProjectToolStripButton_Click(object sender, EventArgs e)
        {
            // source: http://support.microsoft.com/kb/305703 (Jan 2015)
            try
            {
                Process.Start("http://www.codeproject.com/Articles/872477/GCN-Assembler-for-AMD-GPUs");
            }
            catch (Win32Exception noBrowser)
            {
                if (noBrowser.ErrorCode == -2147467259)
                    MessageBox.Show(noBrowser.Message);
            }
            catch (Exception other)
            {
                MessageBox.Show(other.Message);
            }
        }

        private void showSpecsInNotepadToolStripMenuItem_Click(object sender, EventArgs e)
        {
            NotepadHelper.ShowMessage(OpenClWithGCN.GetGPUInfo());
        }

        private void saveKernelToFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (gcn.GcnCompile(txtAsm.Text))
            {
                using (FolderBrowserDialog browserDialog = new FolderBrowserDialog())
                {
                    if (browserDialog.ShowDialog() == DialogResult.OK)
                    {
                        foreach (AsmBlock blk in gcn.env.asmBlocks)
                        {
                            string path = Path.Combine(browserDialog.SelectedPath, "KernelBinary-" + blk.funcName + ".bin");
                            txtOutput.Text = "Saving Bin to " + path;
                            File.WriteAllBytes(path, blk.bin);
                        }
                    }
                }
            }
            else
            {
                MessageBox.Show("Unable to export (see output log)");
                txtOutput.Text = gcn.env.lastMessage;
            }
        }

        private void displayKernelsBinaryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (gcn.GcnCompile(txtAsm.Text))
            {
                txtOutput.Text = "INFO: Open kernel in notepad";
                StringBuilder hex = new StringBuilder();
                foreach (AsmBlock blk in gcn.env.asmBlocks)
                {
                    byte[] bin = blk.bin;
                    hex.AppendFormat("Kernel {0}\r\n", blk.funcName);
                    int i; 
                    for (i = 0; i < bin.Length - 4; i += 4)
                        hex.AppendFormat("{0:x2}{1:x2}{2:x2}{3:x2}\r\n", bin[i], bin[i + 1], bin[i + 2], bin[i + 3]);
                    for (; i < bin.Length; i++)
                        hex.AppendFormat("{0:x2}", bin[i]);
                }
                NotepadHelper.ShowMessage(hex.ToString());
            }
            else
            {
                MessageBox.Show("Unable to export (see output log)");
                txtOutput.Text = gcn.env.lastMessage;
            }
        }

        private void saveDummyBinToFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (gcn.GcnCompile(txtAsm.Text))
            {
                using (FolderBrowserDialog browserDialog = new FolderBrowserDialog())
                {
                    if (browserDialog.ShowDialog() == DialogResult.OK)
                    {
                        //foreach (AsmBlock blk in gcn.env.asmBlocks) // for future
                        {
                            string path = Path.Combine(browserDialog.SelectedPath, "DummyBinary" /*+ blk.funcName*/ + ".bin");
                            txtOutput.Text = "INFO: Saving dummy bin to " + path;
                            File.WriteAllBytes(path, gcn.env.dummyBin);
                        }
                    }
                }
            }
            else
            {
                MessageBox.Show("Unable to export (see output log)");
                txtOutput.Text = gcn.env.lastMessage;
            }
        }

        private void showDummyBinaryToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (gcn.GcnCompile(txtAsm.Text))
            {
                txtOutput.Text = "INFO: Open dummy kernel in notepad.";
                StringBuilder hex = new StringBuilder();
                int i;
                byte[] bin = gcn.env.dummyBin;
                for (i = 0; i < bin.Length - 4; i += 4)
                    hex.AppendFormat("{0:x2}{1:x2}{2:x2}{3:x2}\r\n", bin[i], bin[i + 1], bin[i + 2], bin[i + 3]);
                for (; i < bin.Length; i++)
                    hex.AppendFormat("{0:x2}", bin[i]);
                NotepadHelper.ShowMessage(hex.ToString());
            }
            else
            {
                MessageBox.Show("Unable to export (see output log)");
                txtOutput.Text = gcn.env.lastMessage;
            }
        }

        private void openOutputFolderInExplorerToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Process.Start(outputFolder);
        }

        private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            CheckSaveBeforeClosingCurrent();
        }

        private void creditsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show(this,"Thank you to the following...\n\n"
                + "AMD for making their GCN ISA manuals available. I spend many hours combing through the ISA documentation and referencing their data tables. 90% of the instruction information was taken directly AMD’s ISA manual.\n\n"
                + "Sam Harwell for providing NOpenCL, an excellent OpenCL wrapper for .Net. I like this OpenCL wrapper because it is very .net like and is also close the core c based OpenCL. The Tunnel Vision Laboratories project can be found here: https://github.com/tunnelvisionlabs\n\n"
                + "Daniel Bali for building an excellent, easy to follow, open source GCN assembler. This was my first compiler so I was looking for ideas on how to start.  Daniel’s project gave me ideas on how I could tackle this.\n\n"
                + "Derek Gerstmann for his easy to follow and complete OpenCL example. It is very easy to follow and provides a great example for OpenCL. It has been modified some to fit ASM and C# NOpenCL.\n\n"
                + "Pavel Torgashov for the FastColoredTextBox editor control. This control adds some syntax highlighting richness to the GUI interface. His project can be found on CodeProject here: http://www.codeproject.com/Articles/161871/Fast-Colored-TextBox-for-syntax-highlighting \n\n"
                + "Realhet, who built a fully functional and feature rich assembler for windows called HetPas. Much of my GCN assembly skills came from toying with Realhet’s assembler and from reading his posts.  I have been a Realhet fan for a while.  He has made many insightful posts on AMD forums and on his https://realhet.wordpress.com site.\n\n"
                + "\nFor some reason it feels so old school to put credits in an about box... :-)\n\n"
                ,"Credits...", MessageBoxButtons.OK, MessageBoxIcon.Information);
        }

       
        /// <summary>
        /// Opens a notepad and places a string in the body and also gives it an optional title.
        /// Source: kmatyaszek on 1/28/2015  
        /// http://stackoverflow.com/questions/7613576/how-to-open-text-in-notepad-from-net 
        /// </summary>
        public static class NotepadHelper
        {
            static class NativeMethods
            {
                [DllImport("user32.dll", EntryPoint = "SetWindowText", CharSet = CharSet.Unicode)]
                internal static extern int SetWindowText(IntPtr hWnd, string text);

                [DllImport("user32.dll", EntryPoint = "FindWindowEx", CharSet = CharSet.Unicode)]
                internal static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter,
                    string lpszClass, string lpszWindow);

                [DllImport("User32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]
                internal static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, string lParam);
            }

            /// <summary>
            /// Opens a notepad and places a string in the body and also gives it an optional title.
            /// </summary>
            public static void ShowMessage(string message = null, string title = null)
            {
                Process notepad = Process.Start(new ProcessStartInfo("notepad.exe"));
                if (notepad != null)
                {
                    notepad.WaitForInputIdle();

                    if (!string.IsNullOrEmpty(title))
                        NativeMethods.SetWindowText(notepad.MainWindowHandle, title);

                    if (!string.IsNullOrEmpty(message))
                    {
                        IntPtr child = NativeMethods.FindWindowEx(notepad.MainWindowHandle, new IntPtr(0), "Edit", null);
                        NativeMethods.SendMessage(child, 0x000C, 0, message);
                    }
                }
            }
        }

        private void undoCtrlZToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lastSelectedTextControl.UndoEnabled)
                lastSelectedTextControl.Undo();
        }

        private void redoCtrlRToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (lastSelectedTextControl.RedoEnabled)
                lastSelectedTextControl.Redo();
        }

        private void findCtrlFToolStripMenuItem_Click(object sender, EventArgs e)
        {
            lastSelectedTextControl.ShowFindDialog();
        }

        private void replaceCtrlHToolStripMenuItem_Click(object sender, EventArgs e)
        {
            lastSelectedTextControl.ShowReplaceDialog();
        }

        private void SendKeysToControl(object sender, EventArgs e)
        {
            lastSelectedTextControl.Focus();
            SendKeys.Send((string)((ToolStripItem)sender).Tag);
        }
    } // end frmMain class
} //end Asm4GcnGUI namespace