using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using static System.Double;

namespace AdvertsingProfitControl
{
    public partial class FrmModifyRecord : Form
    {
        private int _lastRowHeaderIndex = -1;
        //Keep track of the AdSpecial row's index, both table's AdSpecial row will have the same row index value.
        private int _adSpecialIndex = -1;
        //Determines what should be add or subtracted from the _adSpecialIndex after a drag and drop event.
        private int _adSpecialIndexOffset = 0;
        //This array keeps track of the number of times an Ad Item is used, if used once then it may only be used AFTER an AdSpecialRow and only once after that.
        //Once an item has been used twice, it will not appear again in the AutoCompleteSuggestions.
        //Structure: "ADITEMSTRING":"SECTIONUSED" Section used means what side of the AdSpecialRow, one (1) being before and two (2) being after.
        private readonly List<string> _usedAdItems = new List<string>();
        //This string keeps track of the last ad item used. This item can then be used to safely remove an ad item from the list of used items.
        //Used in the OnCellValidating event to store the last ad item used in the event that the user changes a row that already exists.
        private string _lastAdItemEntered;
        //Keep track of the last comment entered.
        private string _lastComment;
        private List<string> _adItemCollection;
        private AutoCompleteStringCollection _tempAdItemCollection = new AutoCompleteStringCollection();
        private AutoCompleteStringCollection _supplierCollection;
        private AutoCompleteStringCollection _adSpecialList;
        private readonly List<string> _dateStringCollection = new List<string>();
        private readonly FrmLogConsole _console = FrmLogConsole.GetStaticInstance;
        //Variables to be used with the drag and drop functionality
        private Rectangle _dragBoxFromMouseDown;
        private int _rowIndexFromMouseDown;
        private int _rowIndexOfItemUnderMouseToDrop;

        public FrmModifyRecord()
        {
            InitializeComponent();
            //Set the maximum and minimum sizes for the form.
            MaximumSize = new Size(1200, 650);
            MinimumSize = new Size(1000, 550);
        }

        public sealed override Size MinimumSize
        {
            get { return base.MinimumSize; }
            set { base.MinimumSize = value; }
        }

        public sealed override Size MaximumSize
        {
            get { return base.MaximumSize; }
            set { base.MaximumSize = value; }
        }

        private void FrmModifyRecord_Load(object sender, EventArgs e)
        {
            var databaseTracker = new DatabaseTracker();
            var databaseReader = new DatabaseReader();

            //Enable Drag and Drop
            projectionsDataGridView.MouseMove += OnMouseMove;
            projectionsDataGridView.MouseDown += OnMouseDown;
            projectionsDataGridView.DragOver += OnRowDragOver;
            projectionsDataGridView.DragDrop += ProjectionsDragAndDrop;

            inventoryDataGridView.MouseMove += OnMouseMove;
            inventoryDataGridView.MouseDown += OnMouseDown;
            inventoryDataGridView.DragOver += OnRowDragOver;
            inventoryDataGridView.DragDrop += InventoryDragAndDrop;

            actualSalesDataGridView.MouseMove += OnMouseMove;
            actualSalesDataGridView.MouseDown += OnMouseDown;
            actualSalesDataGridView.DragOver += OnRowDragOver;
            actualSalesDataGridView.DragDrop += ActualSalesDragAndDrop;

            //Setup events for row numbers
            projectionsDataGridView.RowsAdded += DisplayRowNumbers;
            inventoryDataGridView.RowsAdded += DisplayRowNumbers;
            actualSalesDataGridView.RowsAdded += DisplayRowNumbers;

            //Data validation, formatting and auto-complete lists for the Suppliers DataGridView.
            invoicesDataGridView.CellValidating += SupplierOnCellValidating;
            invoicesDataGridView.EditingControlShowing += DisplaySupplierNameAutoComplete;

            //Formatting and data validation for the Weekly Sales data.
            weeklySalesDataGridView.CellValidating += FormatWeeklySalesOnCellValidating;

            commentsTextBox.Leave += UpdateCommentsOnLeave;
            //Fill combo boxes and construct the DataGridViews
            FillDateSuggestionComboBoxes();
            FillDataGridViews();
            //Fill the auto complete suggestion lists for the ad items and suppliers.
            _adItemCollection = databaseReader.GetAdItemsSuggestionList(databaseTracker.DatabaseConnectionString);
            _supplierCollection = databaseReader.GetSupplierSuggestionList(databaseTracker.DatabaseConnectionString);
            _adSpecialList = databaseReader.RetrieveAdSpecialList(databaseTracker.DatabaseConnectionString);
        }

        private void UpdateApcTables(string dateId)
        {
            var databaseTracker = new DatabaseTracker();
            var databaseReader = new DatabaseReader();
            var databaseWriter = new DatabaseWriter(databaseTracker.DatabaseConnectionString);
            var parser = new RowParsing();
            //The layout of the table to be sent shall reflect the physical layout of the database's table
            //Columns 0-5 are projections, 6-9 are inventory, 10-15 are the actual sales and 16-21 are the row attributes, position, ad item (FK), group (FK) and then the date id (FK). 
            var parameters = new object[21];
            var rowIndexNumber = 0;
            var groupId = "";
            //Negative one (-1) shows that the variable is not set.
            var lastHeaderRowNumber = -1;
            var rowPosition = 1;
            var table = new DataTable();
            //Build the table's columns.
            for (var i = 0; i <= 20; i++)
            {
                var column = new DataColumn { ColumnName = "Column" + i };
                table.Columns.Add(column);
            }
            //Spin through the rows in the main tables...
            foreach (var row in actualSalesDataGridView.Rows.Cast<DataGridViewRow>().TakeWhile(row => !row.IsNewRow))
            {
                //Start by getting the current row's attribute.
                var rowAttribute = parser.GetRowAttribute(row);
                //If the row is incomplete then ignore it (continue).
                if (rowAttribute == RowAttribute.IncompleteRow)
                {
                    //We don't want this row in the database, so skip over it.
                    continue;
                }
                //IF the current row is the ad special row, then fill the group ID, increment the row index and move on.
                if (rowIndexNumber == _adSpecialIndex)
                {
                    //Get the group ID for the Ad Special Row.
                    groupId =
                        databaseReader.RetrieveGroupIdByString(
                            parser.CheckForGroupKeyWord(row.Cells[0].EditedFormattedValue.ToString()),
                            databaseTracker.DatabaseConnectionString);
                    continue;
                }
                //Add the ad item into it's respective column.
                parameters[18] = row.Cells[0].EditedFormattedValue.ToString();
                //BEGIN row attribute check.
                //Do an in-depth check to determine if the row should be marked with a "Header Row" group tag.
                //Only rows that have members will be explicitly marked as such.
                if (rowAttribute == RowAttribute.HeaderRow)
                {
                    if ((rowIndexNumber + 1) <= actualSalesDataGridView.Rows.Count)
                    {
                        //Check to see if the next row is a Member Row.
                        if ((parser.GetRowAttribute(actualSalesDataGridView.Rows[rowIndexNumber + 1])) == RowAttribute.MemberRow)
                        {
                            //Set the current row's attribute to HeaderRow in the database.
                            parameters[16] = "1";
                            lastHeaderRowNumber = rowIndexNumber;
                        }
                        //IF the next row is not a member row, then do not apply any attribute to this row.
                        else
                        {
                            //Clear the row number to prevent any mix ups down the line and the row attribute.
                            lastHeaderRowNumber = -1;
                            parameters[16] = "0";
                        }
                    }
                }
                else if (rowAttribute == RowAttribute.MemberRow && lastHeaderRowNumber != -1)
                {
                    //ELSE IF this row is a member, then mark it as such.
                    parameters[16] = "2";
                }
                //END row attribute check.
                //BEGIN checking to see if the current row is an ad special row.
                if (rowIndexNumber > _adSpecialIndex && _adSpecialIndex != -1)
                {
                    //IF the ad item is already in the used ad item list, then skip it as it has already been added to the data table.
                    if (_usedAdItems.Contains(parameters[18] + ":1"))
                    {
                        //IF this item has already been used, then search for its row that's in the data table and update it.
                        var indexOfUsedItem = table.Rows.IndexOf(table.Select("column18 = '" + row.Cells[0].EditedFormattedValue + "'").First());
                        _console.WriteToLog(FrmLogConsole.Level.Debug,
                            "Matched index at " + indexOfUsedItem + " with the item " +
                            row.Cells[0].EditedFormattedValue + ".");
                        if (indexOfUsedItem != -1)
                        {
                            table.Rows[indexOfUsedItem][19] = databaseReader.RetrieveGroupIdByString(groupId,
                                databaseTracker.DatabaseConnectionString);
                            continue;
                        }
                    }
                    else
                    {
                        //ELSE if the ad item isn't used, then simply add the group id to the current row.
                        parameters[19] = groupId;
                    }
                }
                else
                {
                    //Add a zero to ensure that null is not sent to the database.
                    parameters[19] = "0";
                }
                //Add the date ID to its respective column.
                parameters[20] = dateId;
                var cellNumberTotal = 0;
                var currentCellIndex = 0;
                //Pull all the contents from the Projections table and add it to the row to be added.
                foreach (DataGridViewCell cell in projectionsDataGridView.Rows[rowIndexNumber].Cells)
                {
                    if (currentCellIndex != 0)
                    {
                        var formattedValue = projectionsDataGridView.Rows[rowIndexNumber].Cells[currentCellIndex].EditedFormattedValue;
                        if (formattedValue != null && formattedValue.ToString() == "")
                        {
                            parameters[cellNumberTotal] = "0";
                        }
                        else
                        {
                            var value = projectionsDataGridView.Rows[rowIndexNumber].Cells[currentCellIndex].EditedFormattedValue;
                            if (value != null)
                                parameters[cellNumberTotal] = value.ToString();
                        }
                        cellNumberTotal++;
                    }
                    currentCellIndex++;
                }
                currentCellIndex = 0;
                foreach (DataGridViewCell cell in inventoryDataGridView.Rows[rowIndexNumber].Cells)
                {
                    if (currentCellIndex != 0)
                    {
                        var formattedValue = inventoryDataGridView.Rows[rowIndexNumber].Cells[currentCellIndex].EditedFormattedValue;
                        if (formattedValue != null && formattedValue.ToString() == "")
                        {
                            parameters[cellNumberTotal] = "0";
                        }
                        else
                        {
                            var value = inventoryDataGridView.Rows[rowIndexNumber].Cells[currentCellIndex].EditedFormattedValue;
                            if (value != null)
                                parameters[cellNumberTotal] = value.ToString();
                        }
                        cellNumberTotal++;
                    }
                    currentCellIndex++;
                }
                currentCellIndex = 0;
                //Next pull all the information from the Actual Sales table and add it to the row.
                foreach (DataGridViewCell cell in actualSalesDataGridView.Rows[rowIndexNumber].Cells)
                {
                    if (currentCellIndex != 0)
                    {
                        var formattedValue = actualSalesDataGridView.Rows[rowIndexNumber].Cells[currentCellIndex].EditedFormattedValue;
                        if (formattedValue != null && formattedValue.ToString() == "")
                        {
                            parameters[cellNumberTotal] = "0";
                        }
                        else
                        {
                            var value = actualSalesDataGridView.Rows[rowIndexNumber].Cells[currentCellIndex].EditedFormattedValue;
                            if (value !=
                                null)
                                parameters[cellNumberTotal] = value.ToString();
                        }
                        cellNumberTotal++;
                    }
                    currentCellIndex++;
                }

                //Finally add the row's position to the parameters.
                parameters[17] = rowPosition;
                table.Rows.Add(parameters);
                rowPosition++;
                rowIndexNumber++;
            }
            //Now update the database with the new table.
            var rowsAdded = databaseWriter.RedundantlessInsertIntoApc(table);
            notificationLabel.Text = "Added " + rowsAdded.Count + " rows to the database.";
        }

        private void UpdateInvoicesTable(string dateId)
        {
            var databaseTracker = new DatabaseTracker();
            var databaseWriter = new DatabaseWriter(databaseTracker.DatabaseConnectionString);
            var dataTable = new DataTable();

            for (var i = 0; i <= 7; i++)
            {
                dataTable.Columns.Add("Column" + i);
            }

            var parameters = new object[7];

            foreach (DataGridViewRow row in invoicesDataGridView.Rows.Cast<DataGridViewRow>().TakeWhile(row => !row.IsNewRow))
            {
                //InvoiceDate
                parameters[0] = row.Cells[2].EditedFormattedValue;
                //Supplier Name
                parameters[1] = row.Cells[0].EditedFormattedValue;
                //Invoice Number
                parameters[2] = row.Cells[1].EditedFormattedValue;
                //NetAmount At Cost
                parameters[3] = row.Cells[3].EditedFormattedValue;
                //Net Amount
                parameters[4] = row.Cells[4].EditedFormattedValue;
                //Notes
                parameters[5] = row.Cells[5].EditedFormattedValue;
                //Date ID
                parameters[6] = dateId;
                dataTable.Rows.Add(parameters);
            }
            databaseWriter.RedundantlessInsertIntoInvoice(dataTable);
        }

        private void UpdateWeeklySales(string dateId)
        {
            var databaseTracker = new DatabaseTracker();
            var databaseWriter = new DatabaseWriter(databaseTracker.DatabaseConnectionString);
            var parameteres = new object[9];

            var dataTable = new DataTable();

            for (var i = 0; i <= 8; i++)
            {
                dataTable.Columns.Add("Column" + i);
            }
            DataRow rows = dataTable.NewRow();
            foreach (DataGridViewRow row in weeklySalesDataGridView.Rows.Cast<DataGridViewRow>().TakeWhile(row => !row.IsNewRow))
            {
                rows[0] = row.Cells[0].EditedFormattedValue;
                rows[1] = row.Cells[1].EditedFormattedValue;
                rows[2] = row.Cells[2].EditedFormattedValue;
                rows[3] = row.Cells[3].EditedFormattedValue;
                rows[4] = row.Cells[4].EditedFormattedValue;
                rows[5] = row.Cells[5].EditedFormattedValue;
                rows[6] = row.Cells[6].EditedFormattedValue;
                rows[7] = row.Cells[7].EditedFormattedValue;
                rows[8] = dateId;
            }

            databaseWriter.RedundantlessInsertIntoWeeklySales(rows);
        }

        //Re factor later....
        #region DataGridView Filling Methods
        private void FillDataGridViews(string dateId = "")
        {
            var databaseTracker = new DatabaseTracker();
            var databaseReader = new DatabaseReader();

            BuildDataGridViewEvents(false);

            if (dateId == "")
            {
                dateId = databaseReader.RetrieveMostRecentDateId(databaseTracker.DatabaseConnectionString);
            }
            //Clear the DataGridViews and the last used ad item.
            projectionsDataGridView.DataSource = null;
            projectionsDataGridView.Columns.Clear();
            inventoryDataGridView.DataSource = null;
            inventoryDataGridView.Columns.Clear();
            actualSalesDataGridView.DataSource = null;
            actualSalesDataGridView.Columns.Clear();
            invoicesDataGridView.DataSource = null;
            invoicesDataGridView.Columns.Clear();
            weeklySalesDataGridView.DataSource = null;
            weeklySalesDataGridView.Columns.Clear();
            _tempAdItemCollection.Clear();
            _adSpecialIndex = -1;
            _lastAdItemEntered = "";
            BuildDataGridViewSalesTable(databaseReader.ReturnProjectionsTable(dateId,
        databaseTracker.DatabaseConnectionString), projectionsDataGridView);
            BuildDataGridViewSalesTable(databaseReader.ReturnActualSales(dateId,
        databaseTracker.DatabaseConnectionString), actualSalesDataGridView);
            BuildDataGridViewInventoryTable(databaseReader.ReturnInventoryTable(dateId, databaseTracker.DatabaseConnectionString), inventoryDataGridView);

            //Clear the used ad item list
            _usedAdItems.Clear();
            //And fill it with the items from the database
            foreach (var adItem in databaseReader.RetrieveUsedAdItemListByDateId(dateId,
                databaseTracker.DatabaseConnectionString))
            {
                _usedAdItems.Add(adItem);
            }

            var invoiceDataTable = databaseReader.ReturnInvoiceTable(dateId, databaseTracker.DatabaseConnectionString);

            foreach (var dataGridColumn in from DataColumn column in invoiceDataTable.Columns select new DataGridViewColumn
            {
                Name = column.ColumnName,
                HeaderText = AddSpacesToSentence(column.ColumnName, false),
                CellTemplate = new DataGridViewTextBoxCell()
            })
            {
                invoicesDataGridView.Columns.Add(dataGridColumn);
            }

            foreach (DataRow row in invoiceDataTable.Rows)
            {
                invoicesDataGridView.Rows.Add(row.ItemArray);
            }
            inventoryDataGridView.Columns.Remove("RowAttribute");
            inventoryDataGridView.Columns.Remove("FK_GroupID");

            var weeklySalesTable = databaseReader.ReturnWeeklySalesFromDateId(dateId,
                databaseTracker.DatabaseConnectionString);
            foreach (var dataGridColumn in from DataColumn column in weeklySalesTable.Columns select new DataGridViewColumn()
            {
                Name = column.ColumnName,
                HeaderText = AddSpacesToSentence(column.ColumnName, false),
                CellTemplate = new DataGridViewTextBoxCell()
            })
            {
                weeklySalesDataGridView.Columns.Add(dataGridColumn);
            }

            foreach (DataRow row in weeklySalesTable.Rows)
            {
                weeklySalesDataGridView.Rows.Add(row.ItemArray);
            }

            commentsTextBox.Text = databaseReader.RetrieveComments(dateId, databaseTracker.DatabaseConnectionString);

            BuildDataGridViewEvents();
        }

        private void BuildDataGridViewEvents(bool enableEvents = true)
        {
            if (enableEvents)
            {
                //Subscribe event handlers to the DataGridViews
                projectionsDataGridView.CellValidating += OnValidatingSales;
                projectionsDataGridView.RowLeave += OnRowLeave;
                projectionsDataGridView.UserDeletingRow += OnRowRemoving;
                projectionsDataGridView.RowsRemoved += OnProjectionsRowsRemoved;
                projectionsDataGridView.EditingControlShowing += DisplayAutoCompleteOnEditingControlShowing;
                projectionsDataGridView.RowValidating += UpdateInventoryActualSalesDataGridView;

                actualSalesDataGridView.CellValidating += OnValidatingSales;
                actualSalesDataGridView.RowLeave += OnRowLeave;
                actualSalesDataGridView.RowsRemoved += OnActualSalesRowsRemoved;
                actualSalesDataGridView.UserDeletingRow += OnRowRemoving;
                actualSalesDataGridView.EditingControlShowing += DisplayAutoCompleteOnEditingControlShowing;
                actualSalesDataGridView.RowValidating += UpdateProjectionsAndInventory;

                inventoryDataGridView.CellValidating += OnInventoryValidating;
                inventoryDataGridView.RowLeave += OnRowLeave;
                inventoryDataGridView.RowsRemoved += OnInventoryRowsRemoved;
                inventoryDataGridView.UserDeletingRow += OnRowRemoving;
                inventoryDataGridView.EditingControlShowing += DisplayAutoCompleteOnEditingControlShowing;
                inventoryDataGridView.RowValidating += UpdateProjectionsAndActualSalesTables;
            }
            else
            {
                //Un-subscribe event handlers to the DataGridViews
                projectionsDataGridView.CellValidating -= OnValidatingSales;
                projectionsDataGridView.RowLeave -= OnRowLeave;
                projectionsDataGridView.UserDeletingRow -= OnRowRemoving;
                projectionsDataGridView.RowsRemoved -= OnProjectionsRowsRemoved;
                projectionsDataGridView.EditingControlShowing -= DisplayAutoCompleteOnEditingControlShowing;
                projectionsDataGridView.RowValidating -= UpdateInventoryActualSalesDataGridView;

                actualSalesDataGridView.CellValidating -= OnValidatingSales;
                actualSalesDataGridView.RowLeave -= OnRowLeave;
                actualSalesDataGridView.RowsRemoved -= OnActualSalesRowsRemoved;
                actualSalesDataGridView.UserDeletingRow -= OnRowRemoving;
                actualSalesDataGridView.EditingControlShowing -= DisplayAutoCompleteOnEditingControlShowing;
                actualSalesDataGridView.RowValidating -= UpdateProjectionsAndInventory;

                inventoryDataGridView.CellValidating -= OnInventoryValidating;
                inventoryDataGridView.RowLeave -= OnRowLeave;
                inventoryDataGridView.RowsRemoved -= OnInventoryRowsRemoved;
                inventoryDataGridView.UserDeletingRow -= OnRowRemoving;
                inventoryDataGridView.EditingControlShowing -= DisplayAutoCompleteOnEditingControlShowing;
                inventoryDataGridView.RowValidating -= UpdateProjectionsAndActualSalesTables;
            }
        }

        private void BuildDataGridViewSalesTable(DataTable dataTable, DataGridView dataGridView)
        {
            var lastAdSpecialIndex = -1;
            //Add all the columns
            for(var columnIndex = 0; columnIndex < dataTable.Columns.Count; columnIndex++)
            {
                var dataGridViewColumn = new DataGridViewColumn
                {
                    Name = dataTable.Columns[columnIndex].ColumnName,
                    CellTemplate = new DataGridViewTextBoxCell()
                };
                var columnHeaderText = dataTable.Columns[columnIndex].ColumnName;

                if (dataTable.Columns[columnIndex].ColumnName.Contains("Projection"))
                {
                    columnHeaderText = columnHeaderText.Replace("Projection", "");
                    columnHeaderText = AddSpacesToSentence(columnHeaderText, false);
                    dataGridViewColumn.HeaderText = columnHeaderText;
                }
                else
                {
                    columnHeaderText = columnHeaderText.Replace("Actual", "");
                    columnHeaderText = AddSpacesToSentence(columnHeaderText, false);
                    dataGridViewColumn.HeaderText = columnHeaderText;
                }
                //Make the row attribute column invisible.
                if (dataTable.Columns[columnIndex].ColumnName == "RowAttribute")
                {
                    dataGridViewColumn.Visible = false;
                }
                //Make any columns with a foreign key invisible.
                if (dataTable.Columns[columnIndex].ColumnName.Contains("FK"))
                {
                    dataGridViewColumn.Visible = false;
                }
                dataGridView.Columns.Add(dataGridViewColumn);
            }

            for (var rowIndex = 0; rowIndex < dataTable.Rows.Count; rowIndex++)
            {
                var row = new DataGridViewRow();

                if (dataTable.Rows[rowIndex][8].ToString() != "" &&
                    int.Parse(dataTable.Rows[rowIndex][8].ToString()) != 0 && lastAdSpecialIndex == -1)
                {
                    _adSpecialIndex = rowIndex;
                    lastAdSpecialIndex = rowIndex;
                    var adSpecialRow = new DataGridViewRow();
                    var databaseTracker = new DatabaseTracker();
                    var databaseReader = new DatabaseReader();
                    var groupName = databaseReader.ReturnGroupNameFromGroupId(dataTable.Rows[rowIndex][8].ToString(), databaseTracker.DatabaseConnectionString);

                    adSpecialRow.DefaultCellStyle.BackColor = Color.DarkGray;

                    dataGridView.Rows.Add(adSpecialRow);
                    dataGridView.Rows[rowIndex].Cells[0].Value = groupName;
                }

                if (dataTable.Rows[rowIndex][7].ToString() != "")
                {
                    //The seventh column contains the row's attribute if any.
                    var rowAttribute = int.Parse(dataTable.Rows[rowIndex][7].ToString());
                    if (rowAttribute == 1)
                    {
                        row.DefaultCellStyle.BackColor = Color.LightGray;
                    }
                    else if (rowAttribute == 2)
                    {
                        row.DefaultCellStyle.BackColor = Color.LightBlue;
                    }

                    foreach (var dataCell in dataTable.Rows[rowIndex].ItemArray.Select(cell => new DataGridViewTextBoxCell {Value = cell}))
                    {
                        row.Cells.Add(dataCell);
                    }
                    dataGridView.Rows.Add(row);
                }
                else
                {
                    dataGridView.Rows.Add(dataTable.Rows[rowIndex].ItemArray);                    
                }
            }
            dataGridView.Columns.Remove("RowAttribute");
            dataGridView.Columns.Remove("Fk_GroupID");
        }

        private void BuildDataGridViewInventoryTable(DataTable dataTable, DataGridView dataGridView)
        {
            var lastAdSpecialIndex = -1;
            for (var columnIndex = 0; columnIndex < dataTable.Columns.Count; columnIndex++)
            {
                var dataGridViewColumn = new DataGridViewColumn
                {
                    Name = dataTable.Columns[columnIndex].ColumnName,
                    CellTemplate = new DataGridViewTextBoxCell()
                };
                var columnHeaderText = dataTable.Columns[columnIndex].ColumnName;
                columnHeaderText = AddSpacesToSentence(columnHeaderText, false);
                //Make the row attribute column invisible.
                if (dataTable.Columns[columnIndex].ColumnName == "RowAttribute")
                {
                    dataGridViewColumn.Visible = false;
                }
                //Make any columns with a foreign key invisible.
                if (dataTable.Columns[columnIndex].ColumnName.Contains("FK"))
                {
                    dataGridViewColumn.Visible = false;
                }
                dataGridViewColumn.HeaderText = columnHeaderText;
                dataGridView.Columns.Add(dataGridViewColumn);
            }

            for (var rowIndex = 0; rowIndex < dataTable.Rows.Count; rowIndex++)
            {
                var row = new DataGridViewRow();

                if (dataTable.Rows[rowIndex][6].ToString() != "" && int.Parse(dataTable.Rows[rowIndex][6].ToString()) != 0 && lastAdSpecialIndex == -1)
                {
                    lastAdSpecialIndex = rowIndex;
                    var adSpecialRow = new DataGridViewRow();
                    var databaseTracker = new DatabaseTracker();
                    var databaseReader = new DatabaseReader();
                    var groupName = databaseReader.ReturnGroupNameFromGroupId(dataTable.Rows[rowIndex][6].ToString(), databaseTracker.DatabaseConnectionString);

                    adSpecialRow.DefaultCellStyle.BackColor = Color.DarkGray;

                    dataGridView.Rows.Add(adSpecialRow);
                    dataGridView.Rows[rowIndex].Cells[0].Value = groupName;
                }

                if (dataTable.Rows[rowIndex][5].ToString() != "")
                {
                    //The fifth column contains the row's attribute if any.
                    var rowAttribute = int.Parse(dataTable.Rows[rowIndex][5].ToString());
                    if (rowAttribute == 1)
                    {
                        row.DefaultCellStyle.BackColor = Color.LightGray;
                    }
                    else if (rowAttribute == 2)
                    {
                        row.DefaultCellStyle.BackColor = Color.LightBlue;
                    }

                    foreach (var dataCell in dataTable.Rows[rowIndex].ItemArray.Select(cell => new DataGridViewTextBoxCell {Value = cell}))
                    {
                        row.Cells.Add(dataCell);
                    }
                    dataGridView.Rows.Add(row);
                }
                else
                {
                    dataGridView.Rows.Add(dataTable.Rows[rowIndex].ItemArray);
                }
            }
        }

        private void UpdateDataGridViewInformation(object sender, EventArgs e)
        {
            var databaseTracker = new DatabaseTracker();
            var databaseReader = new DatabaseReader();
            FillDataGridViews(databaseReader.RetrieveDateIdByDateString(monthComboBox.SelectedItem + "/" + dayComboBox.SelectedItem + "/" + yearComboBox.Text, databaseTracker.DatabaseConnectionString));
        }

        private void FillDateSuggestionComboBoxes()
        {
            //Create a connection to the database retrieval class.
            var databaseTracker = new DatabaseTracker();
            var databaseReader = new DatabaseReader();
            //Grab the most recent date in the database.
            var mostRecentDateTime = databaseReader.RetrieveMostRecentDateString(databaseTracker.DatabaseConnectionString);
            var mostRecentDateString = mostRecentDateTime.ToString("MM/dd/yyyy");
            //Check to see if the return value is null and IF so log the error and return.
            if (mostRecentDateString == "") { _console.WriteToLog(FrmLogConsole.Level.Error, "No dates could be found in the database."); return; }
            //Otherwise, if there was a return date, split it into a array.
            var mostRecentDateParts = mostRecentDateString.Split('/');
            //Grab only the most recent years in the database (only say 2015) and fill a table with the dates. Indexes are as follows: [0] is Month, [1] is Day and [2] is Year.
            var datesList = databaseReader.RetrieveDateListByYear(mostRecentDateParts[2], databaseTracker.DatabaseConnectionString);
            //Suspend the control's drawing so the user doesn't see any ugly enumeration and index changing.
            DrawingControl.SuspendDrawing(dateSelectorGroupBox);
            //Considering there was a return value for the most recent date, its safe to assume there is at least one date in the database, so clear the class's date collection.
            _dateStringCollection.Clear();
            monthComboBox.Items.Clear();
            dayComboBox.Items.Clear();
            yearComboBox.Items.Clear();
            monthComboBox.SelectedIndexChanged -= UpdateDaysOfMonth;
            dayComboBox.SelectedIndexChanged -= UpdateDataGridViewInformation;
            yearComboBox.SelectedIndexChanged -= UpdateDaysOfMonthByYear;
            //Now spin through the oneYearDatesTable and fill the class wide object with all the dates for the most recent year.
            for (var i = 0; i < datesList.Count; i++)
            {
                var fullDateString = datesList[i].ToString("MM/dd/yyyy");
                //Check for nulls just to be paranoid.
                if (fullDateString == "")
                {
                    return;
                }
                //IF the date string collection already contains the date, then continue to the next iteration.
                if (_dateStringCollection.Contains(fullDateString))
                {
                    continue;
                }
                _dateStringCollection.Add(fullDateString);
            }

            var dayBasedOnMonthAndYeaRegex = new Regex("^0?" + mostRecentDateParts[0] + @"/\d{2}/" + mostRecentDateParts[2]);
            var monthBasedOnYearRegex = new Regex(@"^\d{2}/\d{2}/" + mostRecentDateParts[2]);
            for (var i = 0; i < _dateStringCollection.Count; i++)
            {
                var dateArray = _dateStringCollection[i].Split('/');
                var month = dateArray[0];
                var day = dateArray[1];

                if (dayBasedOnMonthAndYeaRegex.IsMatch(_dateStringCollection[i]))
                {
                    dayComboBox.Items.Add(day);
                }
                if (!monthBasedOnYearRegex.IsMatch(_dateStringCollection[i])) continue;
                if (!monthComboBox.Items.Contains(month))
                {
                    monthComboBox.Items.Add(month);
                }
            }

            var yearsInDatabase = databaseReader.RetrieveUniqueYearsList(databaseTracker.DatabaseConnectionString);

            foreach (var year in yearsInDatabase)
            {
                yearComboBox.Items.Add(year);
            }

            if (dayComboBox.Items.Count >= 1 && yearComboBox.Items.Count >= 1 && monthComboBox.Items.Count >= 1)
            {
                monthComboBox.SelectedIndex = monthComboBox.Items.Count - 1;
                dayComboBox.SelectedIndex = dayComboBox.Items.Count - 1;
                yearComboBox.SelectedIndex = yearComboBox.Items.Count - 1;
                monthComboBox.Enabled = true;
                dayComboBox.Enabled = true;
                yearComboBox.Enabled = true;
            }
            else
            {
                monthComboBox.Enabled = false;
                dayComboBox.Enabled = false;
                yearComboBox.Enabled = false;
            }
            monthComboBox.SelectedIndexChanged += UpdateDaysOfMonth;
            dayComboBox.SelectedIndexChanged += UpdateDataGridViewInformation;
            yearComboBox.SelectedIndexChanged += UpdateDaysOfMonthByYear;
            DrawingControl.ResumeDrawing(dateSelectorGroupBox);
        }

        private void UpdateDaysOfMonth(object sender, EventArgs e)
        {
            if (yearComboBox.SelectedIndex == -1) return;
            var year = yearComboBox.SelectedItem.ToString();
            var month = monthComboBox.SelectedItem.ToString();
            var dateParserPattern = new Regex("^" + month + @"\/\d{2}\/" + year);

            dayComboBox.Items.Clear();

            for (var i = 0; i < _dateStringCollection.Count; i++)
            {
                if (!dateParserPattern.IsMatch(_dateStringCollection[i])) continue;
                var dateArray = _dateStringCollection[i].Split('/');
                var day = dateArray[1];
                dayComboBox.Items.Add(day);
            }

            if (dayComboBox.Items.Count > 0)
            {
                dayComboBox.SelectedIndex = dayComboBox.Items.Count - 1;
            }
        }

        /// <summary>
        /// Fires when the Year combo box's index changes.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateDaysOfMonthByYear(object sender, EventArgs e)
        {
            //Obligatory database retrieval call...
            var databaseTracker = new DatabaseTracker();
            var databaseReader = new DatabaseReader();
            //First, grab the year and the month from their respective combo boxes.
            var year = yearComboBox.SelectedItem.ToString();
            //Since we can more or less be certain that nothing is null, clear the current date collection.
             _dateStringCollection.Clear();
            var months = databaseReader.RetrieveUniqueMonthsList(year, databaseTracker.DatabaseConnectionString);
            var mostRecentMonth = months.Max();
            if (mostRecentMonth.Length == 1)
            {
                mostRecentMonth = "0" + mostRecentMonth;
            }
            var datesList = databaseReader.RetrieveDateListByYear(year, databaseTracker.DatabaseConnectionString);

            for (var i = 0; i < datesList.Count; i++)
            {
                 _dateStringCollection.Add(datesList[i].ToString("MM/dd/yyyy"));
            }

            //Clear the month and day combo boxes and unregister their event handlers.
            dayComboBox.Items.Clear();
            monthComboBox.Items.Clear();
            monthComboBox.SelectedIndexChanged -= UpdateDaysOfMonth;
            dayComboBox.SelectedIndexChanged -= UpdateDataGridViewInformation;
            for (var i = 0; i <  _dateStringCollection.Count; i++)
            {
                var dateArray =  _dateStringCollection[i].Split('/');
                var month = dateArray[0];
                //Declare the patterns to look for when enumerating the combo boxes.
                var dayBasedOnMonthAndYeaRegex = new Regex(@"^(" + mostRecentMonth + @"\/\d{2}\/" + year + ")"); //Only allows days that are actually part of the month and year.
                var day = dateArray[1];

                if (dayBasedOnMonthAndYeaRegex.IsMatch( _dateStringCollection[i]))
                {
                    dayComboBox.Items.Add(day);
                }

                if (!monthComboBox.Items.Contains(month))
                {
                    monthComboBox.Items.Add(month);
                }
            }

            if (dayComboBox.Items.Count > 0)
            {
                dayComboBox.SelectedIndex = dayComboBox.Items.Count - 1;
            }
            if (monthComboBox.Items.Count > 0)
            {
                monthComboBox.SelectedIndex = monthComboBox.Items.Count - 1;
            }

            //Now re-register the event handlers
            monthComboBox.SelectedIndexChanged += UpdateDaysOfMonth;
            dayComboBox.SelectedIndexChanged += UpdateDataGridViewInformation;
            FillDataGridViews(databaseReader.RetrieveDateIdByDateString(monthComboBox.SelectedItem + "/" + dayComboBox.SelectedItem + "/" + year, databaseTracker.DatabaseConnectionString));
        }
        #endregion

        #region Third Party Code
        //http://stackoverflow.com/questions/272633/add-spaces-before-capital-letters
        /// <summary>
        /// Add spaces between all words that have capital letters
        /// allowing for pretty looking column header text.
        /// </summary>
        /// <param name="text">The column header text to be made presentable.</param>
        /// <param name="preserveAcronyms">Whether or not to do anything with text that's in all caps.</param>
        /// <returns></returns>
        private string AddSpacesToSentence(string text, bool preserveAcronyms)
        {
            if (string.IsNullOrWhiteSpace(text))
                return string.Empty;
            var newText = new StringBuilder(text.Length * 2);
            newText.Append(text[0]);
            for (var i = 1; i < text.Length; i++)
            {
                if (char.IsUpper(text[i]))
                    if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
                        (preserveAcronyms && char.IsUpper(text[i - 1]) &&
                         i < text.Length - 1 && !char.IsUpper(text[i + 1])))
                        newText.Append(' ');
                newText.Append(text[i]);
            }
            return newText.ToString();
        }
        #endregion

        #region Global DataGridView Event Handlers

        /// <summary>
        /// Event Used: RowsAdded
        /// Adds the row's number to the Row Header Cell whenever a new row is added.
        /// </summary>
        /// <param name="sender">The DataGridView that is getting new rows added to it.</param>
        /// <param name="e">Row arguments, like row index</param>
        private static void DisplayRowNumbers(object sender, DataGridViewRowsAddedEventArgs e)
        {
            var table = ((DataGridView)sender);
            table.Rows[e.RowIndex].HeaderCell.Value = (e.RowIndex + 1).ToString();
        }

        /// <summary>
        /// Event Used: RowLeave
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnRowLeave(object sender, DataGridViewCellEventArgs e)
        {
            var dataGridView = ((DataGridView)sender);
            //
            if(dataGridView.Name != "inventoryDataGridView")
            {
                PaintRowGroups(e.RowIndex, dataGridView);
            }
            //Add in used Ad Items to the list.
            if (dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString() == "") return;
            var adItem = dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
            //Section one (1) detected.
            if (_adSpecialIndex == -1 || e.RowIndex < _adSpecialIndex)
            {
                if (_usedAdItems.Contains(dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString()))
                    return;
                if (!_usedAdItems.Contains(dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue + ":1"))
                {
                    _usedAdItems.Add(dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue + ":1");
                }
            }
            //Section two (2) detected.
            else if (e.RowIndex > _adSpecialIndex)
            {
                //Add the ad item from the row that is being left and add it into the _usedAdItems list, if its not already in there.
                if (_usedAdItems.Contains(dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString()))
                    return;
                if (!_usedAdItems.Contains(dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue + ":2"))
                {
                    _usedAdItems.Add(dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue + ":2");
                }
            }
        }//End OnRowLeave

        /// <summary>
        /// Event Used: OnEditingControlShowing
        /// Configures the auto complete collection and how it will be shown to the user. This method detects the section,
        /// either one (1) or two (2), based on the gAdSpecialIndex and removes items from the auto complete accordingly.
        /// Just a measure to help reduce redundancy in the tables.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DisplayAutoCompleteOnEditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
        {
            var dataGridView = ((DataGridView)sender);
            var autoText = e.Control as TextBox;
            if (e.Control is DataGridViewTextBoxEditingControl && dataGridView.CurrentCell.ColumnIndex == 0)
            {
                //Log the ad item in this control if its not null.
                if(autoText.Text != "")
                {
                    _lastAdItemEntered = autoText.Text;
                }
                //Create a copy of the main ad item list that can be freely manipulated.
                var customAutoComplete = new AutoCompleteStringCollection();
                var customList = new List<string>();
                foreach (string item in _adItemCollection)
                {
                    customList.Add(item);
                }

                //IF the current row is less than the AdSpecialRow, remove all used items with the section 1 attribute.
                if (_adSpecialIndex == -1 || dataGridView.CurrentCell.RowIndex < _adSpecialIndex)
                {
                    if (_usedAdItems != null)
                    {
                        foreach (var adItem in _usedAdItems)
                        {
                            string[] adItemString = adItem.Split(':');
                            string section = adItemString[1];
                            string adItemToRemove = adItemString[0];
                            if (section == "1")
                            {
                                customList.RemoveAll(
                                    w => w.Equals(adItemToRemove, StringComparison.OrdinalIgnoreCase));
                            }
                        }
                    }
                }
                //Occurs AFTER the AdSpecial row.
                else if (dataGridView.CurrentCell.RowIndex > _adSpecialIndex)
                {
                    if (_usedAdItems != null)
                    {
                        foreach (string adItem in _usedAdItems)
                        {
                            string[] adItemString = adItem.Split(':');
                            string section = adItemString[1];
                            string adItemToRemove = adItemString[0];
                            if (section == "2")
                            {
                                customList.RemoveAll(
                                    w => w.Equals(adItemToRemove, StringComparison.OrdinalIgnoreCase));
                            }
                        }
                    }
                }
                foreach (var item in customList)
                {
                    customAutoComplete.Add(item);
                }
                //Create a temporary copy of the trimmed ad item auto complete list for use with the key press event.
                _tempAdItemCollection = customAutoComplete;
                autoText.KeyDown += UpdateAutoCompleteListOnKeyPress;
                autoText.AutoCompleteMode = AutoCompleteMode.Suggest;
                autoText.AutoCompleteSource = AutoCompleteSource.CustomSource;
                autoText.AutoCompleteCustomSource = customAutoComplete;
            }
            else if (e.Control is DataGridViewTextBoxEditingControl && dataGridView.CurrentCell.ColumnIndex != 0)
            {
                autoText.AutoCompleteMode = AutoCompleteMode.None;
            }
        }//End DisplayAutoCompleteOnEditingControlShowing

        private void UpdateAutoCompleteListOnKeyPress(object sender, KeyEventArgs e)
        {
            var textBox = (TextBox) sender;
            notificationLabel.Text = "";

            if (e.Modifiers == Keys.Shift && e.KeyCode == Keys.Up)
            {
                if (_adSpecialIndex == -1)
                {
                    textBox.AutoCompleteCustomSource = _adSpecialList;
                    notificationLabel.Text = "Auto complete mode changed to Ad Special.";
                }
                else
                {
                    notificationLabel.Text = "An Ad Special row already exists, auto complete mode\n can not be changed.";
                }
            }
            else if (e.Modifiers == Keys.Shift && e.KeyCode == Keys.Down)
            {
                textBox.AutoCompleteCustomSource = _tempAdItemCollection;
                notificationLabel.Text = "Auto complete mode changed to Ad Items.";
            }
        }
        #endregion

        #region Row Parsing

        /// <summary>
        /// Orginal Name: OnRowLeave
        /// Paints rows according to their RowAttribute and detects whether or not they are part of a group.
        /// This function offers a faster method of determining all of this by starting at the row index that 
        /// fired the row leave even. As opposed to the PaintRowGroups function which spins through all the 
        /// DataGridView's rows.
        /// </summary>
        /// <param name="startingIndex">The starting index (the row that fired the RowLeave event)</param>
        /// <param name="dataGridView">A reference to the DataGridView that fired the event.</param>
        private void PaintRowGroups(int startingIndex, DataGridView dataGridView)
        {
            var parser = new RowParsing();
            //If the current row is an Ad Special Row, the color code it and return as nothing further needs to be done.
            if (parser.CheckForGroupKeyWord(dataGridView.Rows[startingIndex].Cells[0].EditedFormattedValue.ToString()) != "NoGroupFound")
            {
                _adSpecialIndex = startingIndex;
                dataGridView.Rows[startingIndex].DefaultCellStyle.BackColor = Color.Silver;
                return;
            }
            //Spin through the dataGridView rows starting at the row that fired the event.
            for (var i = startingIndex; i < (dataGridView.RowCount - 1); i++)
            {
                //Get the status of the current row.
                var rowStatus = parser.GetRowAttribute(dataGridView.Rows[i]);
                //IF the current row is a header row...
                if (rowStatus == RowAttribute.HeaderRow)
                {
                    _lastRowHeaderIndex = i;
                    //Then check to see if the next row is a row header.
                    rowStatus = parser.GetRowAttribute(dataGridView.Rows[i + 1]);
                    //IF so, color code this row as White, since it is not a true group header.
                    if (rowStatus != RowAttribute.HeaderRow)
                    {
                        dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.White;
                    }
                    //IF the current row is not the first row in the data grid (index zero)...
                    if (i > 0)
                    {
                        //The start by checking the previous row's status.
                        rowStatus = parser.GetRowAttribute(dataGridView.Rows[i - 1]);
                        //IF the previous row is a header row OR if the previous row is index zero AND a member row, clear its coloring.
                        if (rowStatus == RowAttribute.HeaderRow || rowStatus == RowAttribute.MemberRow && (i - 1) == 0)
                        {
                            dataGridView.Rows[i - 1].DefaultCellStyle.BackColor = Color.White;
                        }
                        //IF the previous row is a header row AND has color coding saying it is a group header, then clear its color and apply it to this row (i).
                        else if (rowStatus == RowAttribute.HeaderRow && dataGridView.Rows[i - 1].DefaultCellStyle.BackColor == Color.LightGray)
                        {
                            dataGridView.Rows[i - 1].DefaultCellStyle.BackColor = Color.White;
                            dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.LightGray;
                        }
                        //IF previous row is Incomplete, then call the stable PaintRowGroups function.
                        else if (rowStatus == RowAttribute.IncompleteRow)
                        {
                            PaintRowGroups(dataGridView);
                        }
                    }
                }
                else if (rowStatus == RowAttribute.MemberRow)
                {
                    if (i > 0)
                    {
                        //Check the previous row.
                        rowStatus = parser.GetRowAttribute(dataGridView.Rows[i - 1]);
                        //IF the previous row is a member row AND is the first row in the data grid, then remove the coloring for it and the current row (i).
                        if (rowStatus == RowAttribute.MemberRow && (i - 1) == 0)
                        {
                            dataGridView.Rows[i - 1].DefaultCellStyle.BackColor = Color.White;
                            dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.White;
                        }
                        //IF the previous row is a member row AND it also has color coding suggesting that it is a member of a group, then add the current row (i) as well.
                        else if (rowStatus == RowAttribute.MemberRow && dataGridView.Rows[i - 1].DefaultCellStyle.BackColor == Color.LightBlue)
                        {
                            dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.LightBlue;
                        }
                        //IF the previous row is simply a member row with no coloring at all, then remove the coloring from the current row (i).
                        else if (rowStatus == RowAttribute.MemberRow || rowStatus == RowAttribute.AdSpecialRow)
                        {
                            dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.White;
                        }
                        //IF the previous row is a header row, then color it as a group header and color the current row as a member of said group.
                        else if (rowStatus == RowAttribute.HeaderRow)
                        {
                            dataGridView.Rows[i - 1].DefaultCellStyle.BackColor = Color.LightGray;
                            dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.LightBlue;
                        }
                        //IF the previous row is incomplete, then call the stable PaintRowGroups function.
                        else if (rowStatus == RowAttribute.IncompleteRow)
                        {
                            PaintRowGroups(dataGridView);
                        }
                    }
                    //ELSE the current row is the first row in the data grid, therefore it can't be a member row so remove all coloring.
                    else
                    {
                        dataGridView.Rows[0].DefaultCellStyle.BackColor = Color.White;
                    }
                }
            }
        }

        private void PaintRowGroups(DataGridView dataGridView)
        {
            var parser = new RowParsing();
            var statusReport = "";
            var incompleteRowsFound = 0;
            var lastHeaderIndex = -1;
            var i = 0;
            var groupCount = 0;

            foreach (DataGridViewRow row in dataGridView.Rows)
            {
                //Get the current row's status.
                var rowType = parser.GetRowAttribute(row);
                //If the current row is not a group header, the first row in the grid, or a new row and in fact a member row...
                if (i != 0 && rowType == RowAttribute.MemberRow && !row.IsNewRow && rowType != RowAttribute.IncompleteRow)
                {
                    //Remove all coloring on the current row.
                    dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.White;

                    //Check to make sure the numbers are within bounds, errors occurs when an incomplete row is found at index zero (0), and the current row is one (1).
                    if ((i - (1 + incompleteRowsFound)) > 0)
                    {
                        //Check to see if the previous row is a header row by subtracting the number of rows that are (incomplete + 1) from the total number of rows (i).
                        //Adding one (1) to the incomplete count allows for checking the row right before the first incomplete row found so far to see if it's a header row.
                        if (parser.GetRowAttribute(dataGridView.Rows[i - (1 + incompleteRowsFound)]) == RowAttribute.HeaderRow)
                        {
                            row.DefaultCellStyle.BackColor = Color.LightBlue;   //Group member
                            dataGridView.Rows[lastHeaderIndex].DefaultCellStyle.BackColor = Color.LightGray;   //Group header
                            groupCount++;

                            statusReport +=
                                $"\nGroup {groupCount} created with the header row {(i - incompleteRowsFound)} and the following row members: {(i + 1)} ";
                        }
                        //Else if a header row was not found from the previous operation but a header row is set, then its safe to assume that this row can be a member.
                        else if (lastHeaderIndex >= 0)
                        {
                            //Check to make sure that the last header index is underneath the Ad Special Row OR that the current row (i) is before the Ad Special Index.
                            if (lastHeaderIndex > _adSpecialIndex || i < _adSpecialIndex)
                            {
                                //Assume that the current row is a member of that group header and color it as such.
                                row.DefaultCellStyle.BackColor = Color.LightBlue;
                                statusReport += $" {(i + 1)} ";
                            }
                        }
                    }
                }
                //If the current row is a potential group header AND the next row is as well remove the coloring from the current row, no need for checks.
                else if (rowType == RowAttribute.HeaderRow && parser.GetRowAttribute(dataGridView.Rows[i + 1]) == RowAttribute.HeaderRow)
                {
                    dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.White;
                }
                //Check to see if this row is the first row, a header index row, AND the next row is a MemberRow.
                else if (rowType == RowAttribute.HeaderRow && parser.GetRowAttribute(dataGridView.Rows[i + 1]) == RowAttribute.MemberRow && i == 0)
                {
                    //IF so, set the last header index, and color code of this row along with the next row which is a MemberRow.
                    lastHeaderIndex = 0;
                    dataGridView.Rows[0].DefaultCellStyle.BackColor = Color.LightGray;
                    dataGridView.Rows[i + 1].DefaultCellStyle.BackColor = Color.LightBlue;
                    statusReport +=
                        $"Group {groupCount} created with the header row 1 and the following row member(s): {(i + 2).ToString()}";
                }
                //Check to see if this row is a group header.
                else if (rowType == RowAttribute.HeaderRow)
                {
                    //If so, reset the incomplete rows found count, set the lastHeaderIndex to this row's index (i) and change it's color to white.
                    incompleteRowsFound = 0;
                    lastHeaderIndex = i;
                    dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.White;
                }
                //If the current row is the first row in the grid AND is not a group header then remove all color from it, since its not allowed to be a header or a member.
                else if (i == 0 && rowType == RowAttribute.MemberRow)
                {
                    dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.White;
                }
                //Mark an incomplete row to determine whether or not we found one and remove any coloring it may have, incomplete rows are not allowed to be members or headers.
                if (rowType == RowAttribute.IncompleteRow)
                {
                    incompleteRowsFound++;
                    dataGridView.Rows[i].DefaultCellStyle.BackColor = Color.White;
                }
                i++;
            }//end for-each

            dataGridView.Refresh();
            _console.WriteToLog(FrmLogConsole.Level.Debug, statusReport);
        }//End PaintRowGroups

        #endregion

        #region Sales Tables Event Handlers
        /// <summary>
        /// Event Used: CellValidating
        /// This method will validate the contents of the sales tables' row and apply correct formatting where needed.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnValidatingSales(object sender, DataGridViewCellValidatingEventArgs e)
        {
            var dataGridView = ((DataGridView)sender);
            var textInfo = new CultureInfo("en-US", false).TextInfo;
            //Check for isNewRow if it is, return no need to check it for anything.
            if (dataGridView.Rows[e.RowIndex].IsNewRow)
            {
                return;
            }

            //Check to see if the current column is the ad item column.
            if (e.ColumnIndex == 0)
            {
                var adItemText = dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                //If there is text after all the whitespace has been cleared, clear the error text property regardless of whether or not it is set.
                if (adItemText.Replace(" ", "") != "")
                {
                    //Clear the error text since there is in fact an item entered.
                    dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "";
                    //Check for the reserved character, and remove it if it is found.
                    if (adItemText.Contains(":"))
                    {
                        _console.WriteToLog(FrmLogConsole.Level.Warning, "Colon (:) is a reserved character and therefore not allowed to be used in an ad item name.");
                        adItemText = adItemText.Replace(":", "");
                    }
                    //Pretty up the ad item's name by uppercasing the name.
                    adItemText = textInfo.ToTitleCase(adItemText);
                    //Eliminate the upper cased pound abbreviation ("Lb") with a standard "lb".
                    var rgx = new Regex("Lb");
                    adItemText = rgx.Replace(adItemText, "lb");
                    dataGridView.Rows[e.RowIndex].Cells[0].Value = adItemText;
                    dataGridView.RefreshEdit();
                    return;
                }
                if (e.ColumnIndex == 0 && adItemText == "") //Otherwise, set the error text to inform the user.
                {
                    dataGridView.Rows[e.RowIndex].Cells[0].ErrorText = "Ad Item needed";
                    return;
                }
            }
            //Check to make sure the column isn't the ad item and to make sure its not empty
            if (e.ColumnIndex != 0 && string.IsNullOrEmpty(dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString()))
                return;
            //IF not, the check to see if the column is the "Sold" column.
            if (e.ColumnIndex == 1)
            {
                var invenotryStringCheck = new Regex(@"^[0-9]{1,2} \bbin(s){0,1}\b", RegexOptions.IgnoreCase);

                if (
                    invenotryStringCheck.IsMatch(
                        dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString()))
                {
                    //IF a match has been found, then make sure the word 'bin(s)' is capitalized to keep things looking pretty.
                    var input = textInfo.ToTitleCase(dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString());
                    dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = input;
                    dataGridView.RefreshEdit();
                    return;
                }
            }
            //IF not, try to parse the contents to a Double and apply number formatting to the contents.
            //Check for the cost column to see if there are any strings formatted like such:
            var regExpression = new Regex(@"^\d+( *)?/( *)?\${0,1}?\d+(\.\d+)?", RegexOptions.IgnoreCase); // [0-9]/($)?[0-9]
            //IF the current cell is in the sale price column, check for the string format above, else move to the default method.
            if ((e.ColumnIndex == 2) && regExpression.IsMatch(dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString()))
            {
                //Grab the input and split it at the forward slash (/) for formatting.
                var input = dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString();
                input = input.Replace(" ", "");
                //Remove any dollar signs as these cause errors.
                input = input.Replace("$", "");
                var stringArray = input.Split('/');
                //Format the last number as Currency, and round it up if necessary.
                dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value =
                    $@"{stringArray[0]}/{Math.Round(decimal.Parse(stringArray[1]), 2):C}";
                //Always refresh edit so the new value shows up to the user.
                dataGridView.RefreshEdit();
                return; //And return, there is no need to go further.
            }
            //Check to see if the entered value can be parsed to a double, if not then throw an error to the user.
            double parsedUserInput;
            if (
                !TryParse(
                    dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString(),
                    out parsedUserInput))
            {
                _console.WriteToLog(FrmLogConsole.Level.Error, "The cell in column " + (e.ColumnIndex + 1) + " row " + (e.RowIndex + 1) + " only allows for numeric input.");
                MessageBox.Show(@"Non numeric characters in column " + (e.ColumnIndex + 1) + @" are not allowed.", @"Invalid Characters Detected");
                e.Cancel = true;
                dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = "";
                dataGridView.RefreshEdit();
                return;
            }
            //IF the index is greater then two (2), then that means we are not in a column that requires special formatting out side of currency.
            if (e.ColumnIndex <= 2) return;
            //IF not, then apply currency formatting to the cell.
            var value = Parse(dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString(), NumberStyles.Currency);
            //If all goes well, format the string in question by adding commas and decimal points if applicable.
            dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = Math.Round(value, 2).ToString("N", new CultureInfo("en-US"));
            //Always refresh edit so the new value shows up to the user.
            dataGridView.RefreshEdit();
        }//End OnCellValidating

        /// <summary>
        /// Event Used: UserDeleteingRow
        /// This function is responsible for removing ad Items from the gUsedAdItem array; this must be done during the row removing
        /// event handler so the data in the row can be grabbed and used.
        /// </summary>
        /// <param name="sender">The DataGridView that fired the event.</param>
        /// <param name="e">Parameters, mainly allowing for canceling the event.</param>
        private void OnRowRemoving(object sender, CancelEventArgs e)
        {
            //Create an object that represents the DataGridView that fired the event.
            var dataGridView = ((DataGridView)sender);
            if (dataGridView.CurrentRow == null) return;
            if (dataGridView.CurrentRow.IsNewRow == true) return;
            var currentRowIndex = dataGridView.CurrentRow.Index;
            var databaseTracker = new DatabaseTracker();
            var databaseWriter = new DatabaseWriter(databaseTracker.DatabaseConnectionString);
            var databaseReader = new DatabaseReader();
            var adItemsRemoved = new List<string>();
            var dateId = databaseReader.RetrieveDateIdByDateString(monthComboBox.Text + "/" + dayComboBox.Text + "/" + yearComboBox.Text, databaseTracker.DatabaseConnectionString);
            var adItemName = dataGridView.Rows[currentRowIndex].Cells[0].EditedFormattedValue.ToString();
            var adItemId = databaseReader.RetrieveAdItemId(adItemName, databaseTracker.DatabaseConnectionString);
            //Remove the ad item from the gUsedAdItem collection, if it exists.
            if (_adSpecialIndex == -1 || currentRowIndex < _adSpecialIndex)
            {
                //Clear the database of the item.
                var count = databaseWriter.RemoveRecord(adItemId, dateId);

                if (count == 1)
                {
                    notificationLabel.Text = "Successfully removed " + adItemName + " from the database.";
                }
                else
                {
                    notificationLabel.Text = "Failed to remove " + adItemName + " from the database.";
                    e.Cancel = true;
                }
                //If the ad item entered in the first cell is in the gUsedAdItems collection, then remove it from there.
                if (_usedAdItems.Contains(dataGridView.Rows[currentRowIndex].Cells[0].EditedFormattedValue + ":1"))
                {
                    //This is section one (1) since it occurs before the adSpecial row.
                    _usedAdItems.RemoveAll(I => I == dataGridView.Rows[currentRowIndex].Cells[0].EditedFormattedValue.ToString() + ":1");
                }

            }
            else if (currentRowIndex > _adSpecialIndex)
            {
                //Clear the database of the item.
                var count = databaseWriter.RemoveRecord(adItemId, dateId);

                if (count == 1)
                {
                    notificationLabel.Text = "Successfully removed " + adItemName + " from the database.";
                }
                else
                {
                    notificationLabel.Text = "Failed to remove " + adItemName + " from the database.";
                    e.Cancel = true;
                }
                //If the ad item entered in the first cell is in the gUsedAdItems collection, then remove it from there.
                if (_usedAdItems.Contains(dataGridView.Rows[currentRowIndex].Cells[0].EditedFormattedValue + ":2"))
                {
                    //This is section two (2) since it after the adSpecial row.
                    _usedAdItems.RemoveAll(I => I == dataGridView.Rows[currentRowIndex].Cells[0].EditedFormattedValue.ToString() + ":2");
                }
            }
            else if (currentRowIndex == _adSpecialIndex)
            {
                //Handle removing the AdSpecial row.
                var result = MessageBox.Show(@"Deleting the Ad Special row will remove all rows beneath it. Do you wish to continue?", @"Clear " + dataGridView.Rows[currentRowIndex].Cells[0].EditedFormattedValue, MessageBoxButtons.YesNo);
                if (result == DialogResult.Yes)
                {
                    //Clear all events that handle row removal from both DataGridViews.
                    //Projections table
                    projectionsDataGridView.RowsRemoved -= OnProjectionsRowsRemoved;
                    projectionsDataGridView.UserDeletingRow -= OnRowRemoving;
                    //Actual Sales table
                    actualSalesDataGridView.RowsRemoved -= OnActualSalesRowsRemoved;
                    actualSalesDataGridView.UserDeletingRow -= OnRowRemoving;
                    //inventory
                    inventoryDataGridView.RowsRemoved -= OnInventoryRowsRemoved;
                    inventoryDataGridView.UserDeletingRow -= OnRowRemoving;
                    //Loop through all items in the ad special and delete them from the database.
                    for (var itemIndex = currentRowIndex; itemIndex < dataGridView.RowCount; itemIndex++)
                    {
                        if (itemIndex == _adSpecialIndex) continue;
                        adItemName = dataGridView.Rows[itemIndex].Cells[0].EditedFormattedValue.ToString();
                        if (adItemName == "") break;
                        adItemId = databaseReader.RetrieveAdItemId(adItemName, databaseTracker.DatabaseConnectionString);
                        //Clear the database of the item.
                        var count = databaseWriter.RemoveRecord(adItemId, dateId);

                        if (count == 1)
                        {
                            adItemsRemoved.Add(adItemName);
                        }
                    }
                    //Now for-each through each row that is underneath the Ad Special row.
                    for (var rowIndex = currentRowIndex; rowIndex < dataGridView.RowCount; rowIndex++)
                    {         
                        //If the ad item entered in the first cell is in the gUsedAdItems collection, then remove it from there.
                        if (_usedAdItems.Contains(dataGridView.Rows[currentRowIndex].Cells[0].EditedFormattedValue + ":2"))
                        {
                            //This is section two (2) since its after the adSpecial row.
                            _usedAdItems.RemoveAll(I => I == dataGridView.Rows[currentRowIndex].Cells[0].EditedFormattedValue.ToString() + ":2");
                        }
                        if (projectionsDataGridView.Rows[currentRowIndex].IsNewRow != true)
                        {
                            projectionsDataGridView.Rows.RemoveAt(currentRowIndex);
                        }
                        if (inventoryDataGridView.Rows[currentRowIndex].IsNewRow != true)
                        {
                            inventoryDataGridView.Rows.RemoveAt(currentRowIndex);
                        }
                        if (actualSalesDataGridView.Rows[currentRowIndex].IsNewRow != true)
                        {
                            actualSalesDataGridView.Rows.RemoveAt(currentRowIndex);
                        }
                    }
                    notificationLabel.Text = "Successfully removed the following items:";
                    foreach (var item in adItemsRemoved)
                    {
                        notificationLabel.Text += "\n" + item;
                    }

                    //Re-enable all row removal events on both tables.
                    //Projections table
                    projectionsDataGridView.RowsRemoved += OnProjectionsRowsRemoved;
                    projectionsDataGridView.UserDeletingRow += OnRowRemoving;
                    //Actual Sales table
                    actualSalesDataGridView.RowsRemoved += OnActualSalesRowsRemoved;
                    actualSalesDataGridView.UserDeletingRow += OnRowRemoving;
                    //inventory
                    inventoryDataGridView.RowsRemoved += OnInventoryRowsRemoved;
                    inventoryDataGridView.UserDeletingRow += OnRowRemoving;
                    //Reset the gAdSpecialIndex to -1.
                    _adSpecialIndex = -1;
                    e.Cancel = true;
                }
                else
                {
                    e.Cancel = true;
                }
            }
        }
        #endregion

        #region Projections DataGridView Event Handlers

        /// <summary>
        /// Event Used: RowValidating
        /// This method simply copies over the Sale Price, Cost and the Profit Return columns
        /// from the Projections table to the Actual Sales table; this action for merely for the convenience 
        /// of the user. 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateInventoryActualSalesDataGridView(object sender, DataGridViewCellCancelEventArgs e)
        {
            //IF the current row is a new then simply return, we don't want to copy a new row over.
            if (projectionsDataGridView.Rows[e.RowIndex].IsNewRow)
            {
                return;
            }
            //IF the current row's Ad Item cell (column 0) is not empty then...
            if (projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString() == "")
            {
                MessageBox.Show(@"An Ad Item is required.", @"Invalid Ad Item");
                projectionsDataGridView.Rows[e.RowIndex].Cells[0].Selected = true;
                e.Cancel = true;
                return;
            }
            var parser = new RowParsing();
            var actualSalesRowContents = new object[7];
            var inventoryRow = new object[5];
            //Check to make sure the user didn't simply leave a row that already exists.
            if (string.Equals(projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                return;
            }
            //Next check to see if the user changed the ad item is the corresponding row.
            if (!string.Equals(projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                if (projectionsDataGridView.RowCount == actualSalesDataGridView.RowCount)
                {
                    var adItemText = projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                    inventoryDataGridView.Rows[e.RowIndex].Cells[0].Value = adItemText;
                    actualSalesDataGridView.Rows[e.RowIndex].Cells[0].Value = adItemText;
                    inventoryDataGridView.RefreshEdit();
                    actualSalesDataGridView.RefreshEdit();
                    //Remove the last ad item from the UsedAdItem array, the new one will be added in the OnRowLeave event handler.
                    if (_adSpecialIndex == -1 || e.RowIndex < _adSpecialIndex)
                    {
                        _usedAdItems.RemoveAll(I => I == _lastAdItemEntered + ":1");
                    }
                    else
                    {
                        _usedAdItems.RemoveAll(I => I == _lastAdItemEntered + ":2");
                    }
                    return;
                }
            }
            //IF the current row is either a MemberRow OR an AdSpecialRow then only add the item in the first cell, place holder values are not needed.
            if (parser.GetRowAttribute(projectionsDataGridView.Rows[e.RowIndex]) == RowAttribute.MemberRow || parser.GetRowAttribute(projectionsDataGridView.Rows[e.RowIndex]) == RowAttribute.AdSpecialRow)
            {
                actualSalesRowContents[0] = projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                actualSalesDataGridView.Rows.Add(actualSalesRowContents);
                inventoryRow[0] = projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                inventoryDataGridView.Rows.Add(inventoryRow); //Add a single row to the Inventory table to keep the numbers consistent.
                //Use the more optimized overload to paint the rows.
                PaintRowGroups(e.RowIndex, actualSalesDataGridView);
                return;
            }
            //Spin through the DataGridViewCells in the row and add their contents to an array.
            for (var i = 0; i < 7; i++)
            {
                //Grab the Ad Item in the first cell and add it into the array.
                switch (i)
                {
                    case 0:
                        actualSalesRowContents[0] = projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                        break;
                    case 2:
                        //IF the Sale Price cell is empty then place 0.00 into the array as a place holder value (assuming this row is a HeaderRow).
                        if (projectionsDataGridView.Rows[e.RowIndex].Cells[2].EditedFormattedValue.ToString() == "")
                        {
                            actualSalesRowContents[2] = "0.00";
                        }
                        //ELSE place the value from the Projections table into the array, since Sale Price can be determined before actual data is used.
                        else
                        {
                            actualSalesRowContents[2] = projectionsDataGridView.Rows[e.RowIndex].Cells[2].EditedFormattedValue.ToString();
                        }
                        break;
                    case 4:
                        //IF the Cost cell is empty then place 0.00 into the array as a place holder value (assuming this row is a HeaderRow).
                        if (projectionsDataGridView.Rows[e.RowIndex].Cells[4].EditedFormattedValue.ToString() == "")
                        {
                            actualSalesRowContents[4] = "0.00";
                        }
                        //ELSE place the value from the Projections table into the array, since Cost can be determined before actual data is used.
                        else
                        {
                            actualSalesRowContents[4] = projectionsDataGridView.Rows[e.RowIndex].Cells[4].EditedFormattedValue.ToString();
                        }
                        break;
                    case 5:
                        //IF there is no value for the Profit Return column then insert a zero.
                        if (projectionsDataGridView.Rows[e.RowIndex].Cells[4].EditedFormattedValue.ToString() == "")
                        {
                            actualSalesRowContents[5] = "0.00";
                        }
                        //ELSE place the value entered for the Profit Return into the array.
                        else
                        {
                            actualSalesRowContents[5] = projectionsDataGridView.Rows[e.RowIndex].Cells[5].EditedFormattedValue.ToString();
                        }
                        break;
                    default:
                        actualSalesRowContents[i] = "0.00";
                        break;
                }
            }
            actualSalesDataGridView.Rows.Add(actualSalesRowContents);
            inventoryRow[0] = projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
            inventoryDataGridView.Rows.Add(inventoryRow); //Add a single row to the Inventory table to keep the numbers consistent.
        }

        private void OnProjectionsRowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
        {
            var dataGridView = (DataGridView)sender;
            //Provide overflow protection
            if((e.RowIndex + 1) > dataGridView.Rows.Count)
            {
                return;
            }
            //Un-subscribe the event handlers of the other DataGridview tables. 
            inventoryDataGridView.RowsRemoved -= OnInventoryRowsRemoved;
            inventoryDataGridView.UserDeletingRow -= OnRowRemoving;
            actualSalesDataGridView.RowsRemoved -= OnActualSalesRowsRemoved;
            actualSalesDataGridView.UserDeletingRow -= OnRowRemoving;

            if(projectionsDataGridView.RowCount <= inventoryDataGridView.RowCount)
            {
                //IF the row at the same index as the one that was deleted is not a new row, then delete it.
                if (!inventoryDataGridView.Rows[e.RowIndex].IsNewRow)
                {
                    inventoryDataGridView.Rows.RemoveAt(e.RowIndex);
                }
            }

            if (projectionsDataGridView.RowCount <= actualSalesDataGridView.RowCount)
            {
                //IF the row at the same index as the one that was deleted is not a new row, then delete it.
                if (!actualSalesDataGridView.Rows[e.RowIndex].IsNewRow)
                {
                    actualSalesDataGridView.Rows.RemoveAt(e.RowIndex);
                }
            }
            //Decrement the ad special index IF the row that was deleted is above the ad special group.
            if(e.RowIndex < _adSpecialIndex && _adSpecialIndex != -1)
            {
                _adSpecialIndex--;
            }
            inventoryDataGridView.RowsRemoved += OnInventoryRowsRemoved;
            inventoryDataGridView.UserDeletingRow += OnRowRemoving;
            actualSalesDataGridView.RowsRemoved += OnActualSalesRowsRemoved;
            actualSalesDataGridView.UserDeletingRow += OnRowRemoving;
        }

        private void ProjectionsDragAndDrop(object sender, DragEventArgs e)
        {
            // The mouse locations are relative to the screen, so they must be 
            // converted to client coordinates.
            var clientPoint = projectionsDataGridView.PointToClient(new Point(e.X, e.Y));
            var parser = new RowParsing();
            if (e.Effect != DragDropEffects.Move) return;
            // Get the row index of the item the mouse is below. 
            _rowIndexOfItemUnderMouseToDrop = projectionsDataGridView.HitTest(clientPoint.X, clientPoint.Y).RowIndex;
            //Check to see if the row that was previously selected from the mouse down and the row the mouse is currently over is the same.
            if (_rowIndexFromMouseDown == _rowIndexOfItemUnderMouseToDrop)
            {
                //IF so, no action is required as the user is trying to drag and drop onto the row that they selected to begin with.
                return;
            }
            //Before proceeding any further, make sure the object being dropped is in fact a DataGridViewRow.
            if (!e.Data.GetDataPresent(typeof(DataGridViewRow))) return;
            //Disable all events for the DataGridViews.
            BuildDataGridViewEvents(false);
            var rowToMove = e.Data.GetData(typeof(DataGridViewRow)) as DataGridViewRow;
            //Just make sure nothing breaks.
            if (rowToMove == null || _rowIndexOfItemUnderMouseToDrop == -1) return;
            if (rowToMove.IsNewRow)
            {
                notificationLabel.Text = "The last row cannot be moved.";
                return;
            }
            //If the row being moved is a header row...
            if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) == RowAttribute.HeaderRow)
            {
                //Move the row to where the user dragged it, then check to see if the row has any members.    
                if (!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown))
                {
                    _adSpecialIndexOffset = 0;
                    BuildDataGridViewEvents();
                    return;
                }
                //Now update all the grid views
                projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, rowToMove);


                var tempRow = actualSalesDataGridView.Rows[_rowIndexFromMouseDown];
                actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);

                tempRow = inventoryDataGridView.Rows[_rowIndexFromMouseDown];
                inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);

                //... then check to see if it has any member rows.
                var offest = 1;
                if (_rowIndexFromMouseDown > _rowIndexOfItemUnderMouseToDrop)
                {
                    foreach (var row in actualSalesDataGridView.Rows)
                    {
                        if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown + offest]) == RowAttribute.MemberRow)
                        {
                            rowToMove = inventoryDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            var copyOfRowToMove = new DataGridViewRow();
                            for (var index = 0; index < rowToMove.Cells.Count; index++)
                            {
                                var cell = new DataGridViewTextBoxCell { Value = rowToMove.Cells[index].EditedFormattedValue };
                                copyOfRowToMove.Cells.Add(cell);
                            }
                            if (!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown + offest)) continue;
                            //Move the rows to the data grid views
                            projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest, copyOfRowToMove);

                            tempRow = actualSalesDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest,
                                tempRow);

                            tempRow = inventoryDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest, tempRow);

                            offest++;
                        }
                        else
                        {
                            //IF the next row(s) are anything but member rows, then break out of the loop.
                            break;
                        }
                    }
                }
                else
                {
                    foreach (var row in actualSalesDataGridView.Rows)
                    {
                        if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) == RowAttribute.MemberRow)
                        {
                            rowToMove = inventoryDataGridView.Rows[_rowIndexFromMouseDown];
                            var copyOfRowToMove = new DataGridViewRow();
                            for (var index = 0; index < rowToMove.Cells.Count; index++)
                            {
                                var cell = new DataGridViewTextBoxCell { Value = rowToMove.Cells[index].EditedFormattedValue };
                                copyOfRowToMove.Cells.Add(cell);
                            }
                            if(!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown)) continue;
                            //Update the grid views
                            projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                            projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, copyOfRowToMove);

                            tempRow = actualSalesDataGridView.Rows[_rowIndexFromMouseDown];
                            actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                            actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop,
                                tempRow);

                            tempRow = inventoryDataGridView.Rows[_rowIndexFromMouseDown];
                            inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                            inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, rowToMove);

                        }
                        else
                        {
                            //IF the next row(s) are anything but member rows, then break out of the loop.
                            break;
                        }
                    }
                }

            }
            else if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) ==
         RowAttribute.MemberRow)
            {
                notificationLabel.Text = "Rows that are members (shown in light blue) cannot be dragged freely.\nThey can only be moved by dragging their group header row (light gray). ";
            }
            //Clear the ad special offset to prevent corruption.
            _adSpecialIndexOffset = 0;
            UpdateRowHeaderText(projectionsDataGridView);
            UpdateRowHeaderText(inventoryDataGridView);
            UpdateRowHeaderText(actualSalesDataGridView);
            BuildDataGridViewEvents();
        }

        #endregion

        #region Inventory DataGridView Events

        /// <summary>
        /// Event Used: CellValidating
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnInventoryValidating(object sender, DataGridViewCellValidatingEventArgs e)
        {
            var dataGridView = (DataGridView)sender;
            var textInfo = new CultureInfo("en-US", false).TextInfo;
            //Check for isNewRow if it is, return no need to check it for anything.
            if (dataGridView.Rows[e.RowIndex].IsNewRow)
            {
                return;
            }
            //IF the cell being evaluated is in the ad item column then perform some parsing tests on it to ensure it is valid.
            if (e.ColumnIndex == 0)
            {
                var adItemText = dataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                //If there is text after all the whitespace has been cleared, clear the error text property regardless of whether or not it is set.
                if (adItemText.Replace(" ", "") != "")
                {
                    //Clear the error text since there is in fact an item entered.
                    dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "";
                    //Check for the reserved character, and remove it if it is found.
                    if (adItemText.Contains(":"))
                    {
                        _console.WriteToLog(FrmLogConsole.Level.Warning, "Colon (:) is a reserved character and therefore not allowed to be used in an ad item name.");
                        adItemText = adItemText.Replace(":", "");
                    }
                    //Pretty up the ad item's name by uppercasing the name.
                    adItemText = textInfo.ToTitleCase(adItemText);
                    //Eliminate the upper cased pound abbreviation ("Lb") with a standard "lb".
                    var rgx = new Regex("Lb");
                    adItemText = rgx.Replace(adItemText, "lb");
                    dataGridView.Rows[e.RowIndex].Cells[0].Value = adItemText;
                    dataGridView.RefreshEdit();
                    return;
                }
                if (e.ColumnIndex == 0 && adItemText == "") //Otherwise, set the error text to inform the user.
                {
                    dataGridView.Rows[e.RowIndex].Cells[0].ErrorText = "Ad Item Needed";
                    return;
                }
            }
            //Check to make sure the column isn't the ad item and to make sure its not empty
            if (e.ColumnIndex != 0 && string.IsNullOrEmpty(dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString()))
                return;
            //Create a pattern that allows for the syntax x bin(s) in the 
            var invenotryStringCheck = new Regex(@"^[0-9]{1,2} \bbin(s){0,1}\b", RegexOptions.IgnoreCase);
            if (
                invenotryStringCheck.IsMatch(
                    dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString()))
            {
                //IF a match has been found, then make sure the word 'bin(s)' is capitalized to keep things looking pretty.
                var input = textInfo.ToTitleCase(dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString());
                dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = input;
                dataGridView.RefreshEdit();
                return;
            }
            //Else try to parse the entered value to a double
            else
            {
                double valueOut = 0;
                if(!TryParse(dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString(), out valueOut))
                {
                    MessageBox.Show("Values must be numeric.", "Invalid Format");
                    dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Selected = true;
                    e.Cancel = true;
                }
            }
        }

        /// <summary>
        /// Event Used: RowsRemoved
        /// Fires after the row has been removed. This function removes the other DataGridView's row at the index 
        /// specified in the e.RowIndex argument, ensuring that the tables are uniform as to not cause data corruption in the database.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnInventoryRowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
        {
            var dataGridView = ((DataGridView)sender);
            //Provide protection against overflows
            if ((e.RowIndex + 1) > dataGridView.Rows.Count)
            {
                return;
            }
            //Un-subscribe all event handlers from the other tables to prevent misfires.
            projectionsDataGridView.RowsRemoved -= OnProjectionsRowsRemoved;
            projectionsDataGridView.UserDeletingRow -= OnRowRemoving;
            actualSalesDataGridView.RowsRemoved -= OnActualSalesRowsRemoved;
            actualSalesDataGridView.UserDeletingRow -= OnRowRemoving;

            if (inventoryDataGridView.RowCount <= projectionsDataGridView.RowCount)
            {
                //IF the row at the same index as the one that was deleted is not a new row, then delete it.
                if (!projectionsDataGridView.Rows[e.RowIndex].IsNewRow)
                {
                    projectionsDataGridView.Rows.RemoveAt(e.RowIndex);
                }
            }
            
            if(inventoryDataGridView.RowCount <= actualSalesDataGridView.RowCount)
            {
                //IF the row at the same index as the one that was deleted is not a new row, then delete it.
                if (!actualSalesDataGridView.Rows[e.RowIndex].IsNewRow)
                {
                    actualSalesDataGridView.Rows.RemoveAt(e.RowIndex);
                }
            }
            //Decrement the ad special index IF the row that was deleted is above the ad special group.
            if (e.RowIndex < _adSpecialIndex && _adSpecialIndex != -1)
            {
                _adSpecialIndex--;
            }

            projectionsDataGridView.RowsRemoved += OnProjectionsRowsRemoved;
            projectionsDataGridView.UserDeletingRow += OnRowRemoving;
            actualSalesDataGridView.RowsRemoved += OnActualSalesRowsRemoved;
            actualSalesDataGridView.UserDeletingRow += OnRowRemoving;
        }
        /// <summary>
        /// Event Used: RowValidating.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateProjectionsAndActualSalesTables(object sender, DataGridViewCellCancelEventArgs e)
        {
            if (inventoryDataGridView.Rows[e.RowIndex].IsNewRow)
            {
                return;
            }
            //IF the current row's Ad Item cell (column 0) is not empty then...
            if (inventoryDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString() == "")
            {
                //Display the error to the user and select the first cell.
                MessageBox.Show(@"An Ad Item is required.", @"Invalid Ad Item");
                inventoryDataGridView.Rows[e.RowIndex].Cells[0].Selected = true;
                e.Cancel = true;
            }
            //Check to make sure the user didn't simply leave a row that already exists.
            if (string.Equals(inventoryDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                return;
            }
            //Check to see if the user updated the ad item text in the row, and if so update it in the sales tables.
            if (!string.Equals(inventoryDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                if (inventoryDataGridView.RowCount == actualSalesDataGridView.RowCount)
                {
                    var adItemText = inventoryDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                    actualSalesDataGridView.Rows[e.RowIndex].Cells[0].Value = adItemText;
                    projectionsDataGridView.Rows[e.RowIndex].Cells[0].Value = adItemText;
                    actualSalesDataGridView.RefreshEdit();
                    projectionsDataGridView.RefreshEdit();
                    //Remove the last ad item from the UsedAdItem array, the new one will be added in the OnRowLeave event handler.
                    if (_adSpecialIndex == -1 || e.RowIndex < _adSpecialIndex)
                    {
                        _usedAdItems.RemoveAll(I => I == _lastAdItemEntered + ":1");
                    }
                    else
                    {
                        _usedAdItems.RemoveAll(I => I == _lastAdItemEntered + ":2");
                    }
                }
            }
            //Create an object to represent the sales tables' row, and fill it with place holder data.
            var salesTablesRow = new object[7];
            salesTablesRow[0] = inventoryDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue;
            for(var i = 1; i < 7; i++)
            {
                salesTablesRow[i] = 0;
            }
            //Add the new row to the sales tables.
            projectionsDataGridView.Rows.Add(salesTablesRow);
            actualSalesDataGridView.Rows.Add(salesTablesRow);
        }

        private void InventoryDragAndDrop(object sender, DragEventArgs e)
        {
            // The mouse locations are relative to the screen, so they must be 
            // converted to client coordinates.
            var clientPoint = inventoryDataGridView.PointToClient(new Point(e.X, e.Y));
            var parser = new RowParsing();
            if (e.Effect != DragDropEffects.Move) return;
            // Get the row index of the item the mouse is below. 
            _rowIndexOfItemUnderMouseToDrop = inventoryDataGridView.HitTest(clientPoint.X, clientPoint.Y).RowIndex;
            //Check to see if the row that was previously selected from the mouse down and the row the mouse is currently over is the same.
            if (_rowIndexFromMouseDown == _rowIndexOfItemUnderMouseToDrop)
            {
                //IF so, no action is required as the user is trying to drag and drop onto the row that they selected to begin with.
                return;
            }
            //Before proceeding any further, make sure the object being dropped is in fact a DataGridViewRow.
            if (!e.Data.GetDataPresent(typeof(DataGridViewRow))) return;
            //Disable all events for the DataGridViews.
            BuildDataGridViewEvents(false);
            var rowToMove = e.Data.GetData(typeof(DataGridViewRow)) as DataGridViewRow;
            //Just make sure nothing breaks.
            if (rowToMove == null || _rowIndexOfItemUnderMouseToDrop == -1) return;
            if (rowToMove.IsNewRow)
            {
                notificationLabel.Text = "The last row cannot be moved.";
                return;
            }
            //If the row being moved is a header row...
            if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) == RowAttribute.HeaderRow)
            {
                //Move the row to where the user dragged it, then check to see if the row has any members.    
                if (!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown))
                {
                    _adSpecialIndexOffset = 0;
                    BuildDataGridViewEvents();
                    return;
                }
                //Now update all the grid views
                inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, rowToMove);

                var tempRow = actualSalesDataGridView.Rows[_rowIndexFromMouseDown];
                actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);

                tempRow = projectionsDataGridView.Rows[_rowIndexFromMouseDown];
                projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);

                //... then check to see if it has any member rows.
                var offest = 1;
                if (_rowIndexFromMouseDown > _rowIndexOfItemUnderMouseToDrop)
                {
                    foreach (var row in actualSalesDataGridView.Rows)
                    {
                        if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown + offest]) == RowAttribute.MemberRow)
                        {
                            rowToMove = inventoryDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            var copyOfRowToMove = new DataGridViewRow();
                            for (var index = 0; index < rowToMove.Cells.Count; index++)
                            {
                                var cell = new DataGridViewTextBoxCell { Value = rowToMove.Cells[index].EditedFormattedValue };
                                copyOfRowToMove.Cells.Add(cell);
                            }
                            if(!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown + offest)) continue;
                            //Move the rows to the data grid views
                            inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest, copyOfRowToMove);

                            tempRow = actualSalesDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest,
                                tempRow);

                            tempRow = projectionsDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest, tempRow);
                            offest++;
                        }
                        else
                        {
                            //IF the next row(s) are anything but member rows, then break out of the loop.
                            break;
                        }
                    }
                }
                else
                {
                    foreach (var row in actualSalesDataGridView.Rows)
                    {
                        if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) == RowAttribute.MemberRow)
                        {
                            rowToMove = inventoryDataGridView.Rows[_rowIndexFromMouseDown];
                            var copyOfRowToMove = new DataGridViewRow();
                            for (var index = 0; index < rowToMove.Cells.Count; index++)
                            {
                                var cell = new DataGridViewTextBoxCell { Value = rowToMove.Cells[index].EditedFormattedValue };
                                copyOfRowToMove.Cells.Add(cell);
                            }
                            if (!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown)) break;
                            //Update the grid views
                            inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                            inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, copyOfRowToMove);

                            tempRow = actualSalesDataGridView.Rows[_rowIndexFromMouseDown];
                            actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                            actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop,
                                tempRow);

                            tempRow = projectionsDataGridView.Rows[_rowIndexFromMouseDown];
                            projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                            projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);
                        }
                        else
                        {
                            //IF the next row(s) are anything but member rows, then break out of the loop.
                            break;
                        }
                    }
                }

            }
            else if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) ==
         RowAttribute.MemberRow)
            {
                notificationLabel.Text = "Rows that are members (shown in light blue) cannot be dragged freely.\nThey can only be moved by dragging their group header row (light gray). ";
            }
            //Clear the ad special offset to prevent corruption.
            _adSpecialIndexOffset = 0;
            UpdateRowHeaderText(projectionsDataGridView);
            UpdateRowHeaderText(inventoryDataGridView);
            UpdateRowHeaderText(actualSalesDataGridView);
            BuildDataGridViewEvents();
        }
        #endregion

        #region Actual Sales DataGridView Event Handlers

        /// <summary>
        /// Event Used: RowValidating.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateProjectionsAndInventory(object sender, DataGridViewCellCancelEventArgs e)
        {
            //Check to see if the current row is a new row.
            if (actualSalesDataGridView.Rows[e.RowIndex].IsNewRow)
            {
                return;
            }
            //IF the current row's Ad Item cell (column 0) is empty then throw an error.
            if (actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString() == "")
            {
                MessageBox.Show(@"An Ad Item is required.", @"Invalid Ad Item");
                projectionsDataGridView.Rows[e.RowIndex].Cells[0].Selected = true;
                e.Cancel = true;
            }
            var parser = new RowParsing();
            var projectionsRowContents = new object[7];
            var inventoryRowContents = new object[5];
            //Check to make sure the user didn't simply leave a row that already exists.
            if (string.Equals(actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                return;
            }
            //Next check to see if the user changed the ad item is the corresponding row.
            if (!string.Equals(actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), inventoryDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                if (actualSalesDataGridView.RowCount == projectionsDataGridView.RowCount)
                {
                    var adItemText = actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                    //Update the other tables.
                    inventoryDataGridView.Rows[e.RowIndex].Cells[0].Value = adItemText;
                    projectionsDataGridView.Rows[e.RowIndex].Cells[0].Value = adItemText;
                    inventoryDataGridView.RefreshEdit();
                    projectionsDataGridView.RefreshEdit();
                    //Remove the last ad item from the UsedAdItem array, the new one will be added in the OnRowLeave event handler.
                    if (_adSpecialIndex == -1 || e.RowIndex < _adSpecialIndex)
                    {
                        _usedAdItems.RemoveAll(I => I == _lastAdItemEntered + ":1");
                    }
                    else
                    {
                        _usedAdItems.RemoveAll(I => I == _lastAdItemEntered + ":2");
                    }
                    return;
                }
            }
            //IF the current row is either a MemberRow OR an AdSpecialRow then only add the item in the first cell, place holder values are not needed.
            if (parser.GetRowAttribute(actualSalesDataGridView.Rows[e.RowIndex]) == RowAttribute.MemberRow || parser.GetRowAttribute(actualSalesDataGridView.Rows[e.RowIndex]) == RowAttribute.AdSpecialRow)
            {
                projectionsRowContents[0] = actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                projectionsDataGridView.Rows.Add(projectionsRowContents);
                inventoryRowContents[0] = actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                inventoryDataGridView.Rows.Add(inventoryRowContents); //Add a single row to the Inventory table to keep the numbers consistent.
                //Use the more optimized overload to paint the rows.
                PaintRowGroups(e.RowIndex, actualSalesDataGridView);
                return;
            }
            //Spin through the DataGridViewCells in the row and add their contents to an array.
            for (var i = 0; i < 7; i++)
            {
                //Grab the Ad Item in the first cell and add it into the array.
                switch (i)
                {
                    case 0:
                        projectionsRowContents[0] = actualSalesDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
                        break;
                    case 2:
                        //IF the Sale Price cell is empty then place 0.00 into the array as a place holder value (assuming this row is a HeaderRow).
                        if (actualSalesDataGridView.Rows[e.RowIndex].Cells[2].EditedFormattedValue.ToString() == "")
                        {
                            projectionsRowContents[2] = "0.00";
                        }
                        //ELSE place the value from the Projections table into the array, since Sale Price can be determined before actual data is used.
                        else
                        {
                            projectionsRowContents[2] = actualSalesDataGridView.Rows[e.RowIndex].Cells[2].EditedFormattedValue.ToString();
                        }
                        break;
                    case 4:
                        //IF the Cost cell is empty then place 0.00 into the array as a place holder value (assuming this row is a HeaderRow).
                        if (actualSalesDataGridView.Rows[e.RowIndex].Cells[4].EditedFormattedValue.ToString() == "")
                        {
                            projectionsRowContents[4] = "0.00";
                        }
                        //ELSE place the value from the Projections table into the array, since Cost can be determined before actual data is used.
                        else
                        {
                            projectionsRowContents[4] = actualSalesDataGridView.Rows[e.RowIndex].Cells[4].EditedFormattedValue.ToString();
                        }
                        break;
                    case 5:
                        //IF there is no value for the Profit Return column then insert a zero.
                        if (actualSalesDataGridView.Rows[e.RowIndex].Cells[4].EditedFormattedValue.ToString() == "")
                        {
                            projectionsRowContents[5] = "0.00";
                        }
                        //ELSE place the value entered for the Profit Return into the array.
                        else
                        {
                            projectionsRowContents[5] = actualSalesDataGridView.Rows[e.RowIndex].Cells[5].EditedFormattedValue.ToString();
                        }
                        break;
                    default:
                        projectionsRowContents[i] = "0.00";
                        break;
                }
            }
            projectionsDataGridView.Rows.Add(projectionsRowContents);
            inventoryRowContents[0] = projectionsDataGridView.Rows[e.RowIndex].Cells[0].EditedFormattedValue.ToString();
            inventoryDataGridView.Rows.Add(inventoryRowContents); //Add a single row to the Inventory table to keep the numbers consistent.
        }

        private void OnActualSalesRowsRemoved(object sender, DataGridViewRowsRemovedEventArgs e)
        {
            var dataGridView = (DataGridView)sender;
            //Provide overflow protection
            if ((e.RowIndex + 1) > dataGridView.Rows.Count)
            {
                return;
            }
            //Un-subscribe the event handlers of the other DataGridview tables. 
            inventoryDataGridView.RowsRemoved -= OnInventoryRowsRemoved;
            inventoryDataGridView.UserDeletingRow -= OnRowRemoving;
            projectionsDataGridView.RowsRemoved -= OnProjectionsRowsRemoved;
            projectionsDataGridView.UserDeletingRow -= OnRowRemoving;

            if (actualSalesDataGridView.RowCount <= projectionsDataGridView.RowCount)
            {
                //IF the row at the same index as the one that was deleted is not a new row, then delete it.
                if (!projectionsDataGridView.Rows[e.RowIndex].IsNewRow)
                {
                    projectionsDataGridView.Rows.RemoveAt(e.RowIndex);
                }
            }

            if (actualSalesDataGridView.RowCount <= inventoryDataGridView.RowCount)
            {
                //IF the row at the same index as the one that was deleted is not a new row, then delete it.
                if (!inventoryDataGridView.Rows[e.RowIndex].IsNewRow)
                {
                   inventoryDataGridView.Rows.RemoveAt(e.RowIndex);
                }
            }
            //Decrement the ad special index IF the row that was deleted is above the ad special group.
            if (e.RowIndex < _adSpecialIndex && _adSpecialIndex != -1)
            {
                _adSpecialIndex--;
            }
            inventoryDataGridView.RowsRemoved += OnInventoryRowsRemoved;
            inventoryDataGridView.UserDeletingRow += OnRowRemoving;
            projectionsDataGridView.RowsRemoved += OnProjectionsRowsRemoved;
            projectionsDataGridView.UserDeletingRow += OnRowRemoving;
        }

        private void ActualSalesDragAndDrop(object sender, DragEventArgs e)
        {
            //Clear the notifications label.
            notificationLabel.Text = "";
            // The mouse locations are relative to the screen, so they must be 
            // converted to client coordinates.
            var clientPoint = actualSalesDataGridView.PointToClient(new Point(e.X, e.Y));
            var parser = new RowParsing();
            if (e.Effect != DragDropEffects.Move) return;
            // Get the row index of the item the mouse is below. 
            _rowIndexOfItemUnderMouseToDrop = actualSalesDataGridView.HitTest(clientPoint.X, clientPoint.Y).RowIndex;
            //Check to see if the row that was previously selected from the mouse down and the row the mouse is currently over is the same.
            if (_rowIndexFromMouseDown == _rowIndexOfItemUnderMouseToDrop)
            {
                //IF so, no action is required as the user is trying to drag and drop onto the row that they selected to begin with.
                return;
            }
            //Before proceeding any further, make sure the object being dropped is in fact a DataGridViewRow.
            if (!e.Data.GetDataPresent(typeof(DataGridViewRow))) return;
            //Disable all events for the DataGridViews.
            BuildDataGridViewEvents(false);
            var rowToMove = e.Data.GetData(typeof(DataGridViewRow)) as DataGridViewRow;
            //Just make sure nothing breaks.
            if (rowToMove == null || _rowIndexOfItemUnderMouseToDrop == -1) return;
            if (rowToMove.IsNewRow)
            {
                notificationLabel.Text = "The last row cannot be moved.";
                return;
            }
            //If the row being moved is a header row...
            if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) == RowAttribute.HeaderRow)
            {
                //Move the row to where the user dragged it, then check to see if the row has any members.    
                if (!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown))
                {
                    _adSpecialIndexOffset = 0;
                    BuildDataGridViewEvents();
                    return;
                }
                //Now update all the grid views
                actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, rowToMove);

                var tempRow = inventoryDataGridView.Rows[_rowIndexFromMouseDown];
                inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);
                inventoryDataGridView.Refresh();

                tempRow = projectionsDataGridView.Rows[_rowIndexFromMouseDown];
                projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);
                projectionsDataGridView.Refresh();

                //... then check to see if it has any member rows.
                var offest = 1;
                if (_rowIndexFromMouseDown > _rowIndexOfItemUnderMouseToDrop)
                {
                    foreach (var row in actualSalesDataGridView.Rows)
                    {
                        if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown + offest]) == RowAttribute.MemberRow)
                        {
                            rowToMove = actualSalesDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            var copyOfRowToMove = new DataGridViewRow();
                            for (var index = 0; index < rowToMove.Cells.Count; index++)
                            {
                                var cell = new DataGridViewTextBoxCell {Value = rowToMove.Cells[index].EditedFormattedValue};
                                copyOfRowToMove.Cells.Add(cell);
                            }
                            if (!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown + offest)) continue;
                            //Move the rows to the data grid views
                            actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest,
                                copyOfRowToMove);
                            tempRow = inventoryDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest, tempRow);

                            tempRow = projectionsDataGridView.Rows[_rowIndexFromMouseDown + offest];
                            projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown + offest);
                            projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop + offest, tempRow);
                            offest++;
                        }
                        else
                        {
                            //IF the next row(s) are anything but member rows, then break out of the loop.
                            break;
                        }
                    }
                }
                else
                {
                    foreach (var row in actualSalesDataGridView.Rows)
                    {
                         if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) == RowAttribute.MemberRow)
                        {
                            rowToMove = actualSalesDataGridView.Rows[_rowIndexFromMouseDown];
                            var copyOfRowToMove = new DataGridViewRow();
                            for (var index = 0; index < rowToMove.Cells.Count; index++)
                            {
                                var cell = new DataGridViewTextBoxCell { Value = rowToMove.Cells[index].EditedFormattedValue };
                                copyOfRowToMove.Cells.Add(cell);
                            }
                            if(!UpdateUsedAdItemsOnDragAndDrop(_rowIndexFromMouseDown)) continue;
                            //Update the grid views
                            actualSalesDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                                actualSalesDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop,
                                    copyOfRowToMove);
                            tempRow = inventoryDataGridView.Rows[_rowIndexFromMouseDown];
                            inventoryDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                            inventoryDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);

                            tempRow = projectionsDataGridView.Rows[_rowIndexFromMouseDown];
                            projectionsDataGridView.Rows.RemoveAt(_rowIndexFromMouseDown);
                            projectionsDataGridView.Rows.Insert(_rowIndexOfItemUnderMouseToDrop, tempRow);
                        }
                        else
                        {
                            //IF the next row(s) are anything but member rows, then break out of the loop.
                            break;
                        }                       
                    }
                }

            }
            else if (parser.GetRowAttribute(actualSalesDataGridView.Rows[_rowIndexFromMouseDown]) ==
                     RowAttribute.MemberRow)
            {
                notificationLabel.Text = "Rows that are members (shown in light blue) cannot be dragged freely.\nThey can only be moved by dragging their group header row (light gray). ";
            }
            //Clear the offset to prevent corruption
            _adSpecialIndexOffset = 0;
            UpdateRowHeaderText(projectionsDataGridView);
            UpdateRowHeaderText(inventoryDataGridView);
            UpdateRowHeaderText(actualSalesDataGridView);
            BuildDataGridViewEvents();
        }
        #endregion

        #region Invoices DataGridView Event Handlers
        /// <summary>
        /// Event Used: CellValidating
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SupplierOnCellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        {
            var dataGridView = ((DataGridView)sender);
            //IF the row that fires the event is a new row, then return. Validation is not required on a new row.
            if (dataGridView.Rows[e.RowIndex].IsNewRow) return;
            if (dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString() == "") return;

            if (e.ColumnIndex == 2)
            {
                DateTime input;
                if (!DateTime.TryParse(dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString(), out input))
                {
                    MessageBox.Show("You must enter a valid date with the format MM/DD/YYYY.", "Invalid Date");
                    dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = "";
                    dataGridView.RefreshEdit();
                    return;
                }
                dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = input.ToString("MM/dd/yyyy");
                dataGridView.RefreshEdit();
            }

            if (e.ColumnIndex == 0)
            {
                var input = dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString();
                var textInfo = new CultureInfo("en-US", false).TextInfo;
                dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = textInfo.ToTitleCase(input);
                dataGridView.RefreshEdit();
            }

            if (e.ColumnIndex == 3 || e.ColumnIndex == 4)
            {
                double input = 0;
                if (
                    !TryParse(
                        dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].EditedFormattedValue.ToString(), out input))
                {
                    MessageBox.Show("Only numbers may be entered in this column.", "Non Numeric Value");
                    dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = "";
                    dataGridView.RefreshEdit();
                    return;
                }

                dataGridView.Rows[e.RowIndex].Cells[e.ColumnIndex].Value = Math.Round(input, 2).ToString("N", new CultureInfo("en-US"));
                dataGridView.RefreshEdit();
            }
        }

        /// <summary>
        /// Event Used: EditingControlShowing
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DisplaySupplierNameAutoComplete(object sender, DataGridViewEditingControlShowingEventArgs e)
        {
            var dataGridView = ((DataGridView)sender);
            var autoText = e.Control as TextBox;
            //IF the control is in fact an editing control and it is column two (2), then ...
            if (e.Control is DataGridViewTextBoxEditingControl && dataGridView.CurrentCell.ColumnIndex == 0)
            {
                autoText.AutoCompleteMode = AutoCompleteMode.Suggest;
                autoText.AutoCompleteSource = AutoCompleteSource.CustomSource;
                autoText.AutoCompleteCustomSource = _supplierCollection;
            }
        }
        #endregion

        #region WeeklySales DataGridView Event Handlers
        /// <summary>
        /// Event Used: CellValidating
        /// Simply applies formatting to the Weekly Sales table hen the user clicks out of a cell.
        /// Also replaces null cells with zeros to have a value for the database to enter.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void FormatWeeklySalesOnCellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        {
            if (weeklySalesDataGridView.Rows[0].Cells[e.ColumnIndex].EditedFormattedValue.ToString() == "0.00" || weeklySalesDataGridView.Rows[0].Cells[e.ColumnIndex].EditedFormattedValue.ToString() == "")
            {
                return;
            }
            double numberToFormat = 0;

            if (Double.TryParse(weeklySalesDataGridView.Rows[0].Cells[e.ColumnIndex].EditedFormattedValue.ToString(), out numberToFormat))
            {

                weeklySalesDataGridView.Rows[0].Cells[e.ColumnIndex].Value = Math.Round(numberToFormat, 2).ToString("N", new CultureInfo("en-US"));
                weeklySalesDataGridView.RefreshEdit();
            }
            else
            {
                MessageBox.Show("Only numeric values can be entered.", "Non Numeric Characters");
                e.Cancel = true;
            }
        }
        #endregion
        
        #region Comments Event Handlers

        /// <summary>
        /// Event Used: Leave
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateCommentsOnLeave(object sender, EventArgs e)
        {
            var databaseTracker = new DatabaseTracker();
            var databaseReader = new DatabaseReader();
            var databaseWriter = new DatabaseWriter(databaseTracker.DatabaseConnectionString);

            if (commentsTextBox.Text == _lastComment)
            {
                return;
            }

            var dateId =
    databaseReader.RetrieveDateIdByDateString(monthComboBox.Text + "/" + dayComboBox.Text + "/" +
                                              yearComboBox.Text, databaseTracker.DatabaseConnectionString);
            if (dateId == "0") { notificationLabel.Text = "unable to find date in database."; return; }
            var recordsAffected = databaseWriter.UpdateCommentsByDateId(commentsTextBox.Text, dateId);
            if (recordsAffected == true)
            {
                notificationLabel.Text = "Successfully updated the comments for the selected date.";
            }
            else
            {
                //Try adding a new record as there could be no comments in the database.
                var rowsEffected = databaseWriter.RedundantlessInsertIntoComments(commentsTextBox.Text, dateId);
                if (rowsEffected)
                {
                    notificationLabel.Text = "Successfully added the comments for the selected date.";
                }
                else
                {
                    notificationLabel.Text = "Failed to update the comments for the selected date.";
                }
            }
            _lastComment = commentsTextBox.Text;

        }

        #endregion

        #region Drag and Drop
        //Source Code:
        //http://www.inforbiro.com/blog-eng/c-sharp-datagridview-drag-and-drop-rows-reorder/
        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            var dataGridView = (DataGridView) sender;
            if ((e.Button & MouseButtons.Left) == MouseButtons.Left)
            {
                // If the mouse moves outside the rectangle, start the drag.
                if (_dragBoxFromMouseDown != Rectangle.Empty &&
                !_dragBoxFromMouseDown.Contains(e.X, e.Y))
                {
                    // Proceed with the drag and drop, passing in the list item.                    
                    DragDropEffects dropEffect = dataGridView.DoDragDrop(
                          dataGridView.Rows[_rowIndexFromMouseDown],
                          DragDropEffects.Move);
                }
            }
        }

        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            var dataGridView = (DataGridView)sender;
            // Get the index of the item the mouse is below.
            _rowIndexFromMouseDown = dataGridView.HitTest(e.X, e.Y).RowIndex;

            if (_rowIndexFromMouseDown != -1)
            {
                // Remember the point where the mouse down occurred. 
                // The DragSize indicates the size that the mouse can move 
                // before a drag event should be started.                
                Size dragSize = SystemInformation.DragSize;

                // Create a rectangle using the DragSize, with the mouse position being
                // at the center of the rectangle.
                _dragBoxFromMouseDown = new Rectangle(
                          new Point(
                            e.X - (dragSize.Width / 2),
                            e.Y - (dragSize.Height / 2)),
                      dragSize);
            }
            else
                // Reset the rectangle if the mouse is not over an item in the ListBox.
                _dragBoxFromMouseDown = Rectangle.Empty;
        }

        private static void OnRowDragOver(object sender, DragEventArgs e)
        {
            e.Effect = DragDropEffects.Move;
        }
        #endregion


        private bool UpdateUsedAdItemsOnDragAndDrop(int rowIndex)
        {
            var canBeMoved = true;
            //Row being dragged is coming from before the ad special row.
            if (_rowIndexFromMouseDown < _adSpecialIndex && _adSpecialIndex != -1)
            {
                //IF the row being dragged is dropped after the ad special row...
                if (_rowIndexOfItemUnderMouseToDrop > _adSpecialIndex && _adSpecialIndex != -1)
                {
                    if (
                        !_usedAdItems.Contains(
                            actualSalesDataGridView.Rows[rowIndex].Cells[0].EditedFormattedValue +
                            ":2"))
                    {
                        _usedAdItems.Remove(
                            actualSalesDataGridView.Rows[rowIndex].Cells[0].EditedFormattedValue +
                            ":1");
                        _usedAdItems.Add(
                            actualSalesDataGridView.Rows[rowIndex].Cells[0].EditedFormattedValue +
                            ":2");
                        //Ad Special row being pushed up.
                        _adSpecialIndex--;
                    }
                    else
                    {
                        notificationLabel.Text = "Row " + (rowIndex + 1) + " cannot be moved to the Ad Special section\n as that section already contains " + actualSalesDataGridView.Rows[_rowIndexFromMouseDown].Cells[0].EditedFormattedValue + ".";
                        canBeMoved = false;
                    }
                }
            }
            //IF an Ad Special is being dragged to the regular section...
            if (_rowIndexFromMouseDown > _adSpecialIndex && _adSpecialIndex != -1)
            {
                //... Check to see if it is being dragged.
                if (_rowIndexOfItemUnderMouseToDrop < _adSpecialIndex && _adSpecialIndex != -1)
                {
                    if (
!_usedAdItems.Contains(
    actualSalesDataGridView.Rows[rowIndex].Cells[0].EditedFormattedValue +
    ":1"))
                    {
                        _usedAdItems.Remove(
                            actualSalesDataGridView.Rows[rowIndex].Cells[0].EditedFormattedValue +
                            ":2");
                        _usedAdItems.Add(
                            actualSalesDataGridView.Rows[rowIndex].Cells[0].EditedFormattedValue +
                            ":1");
                        //Ad Special row being pushed down a row.
                        _adSpecialIndex++;
                    }
                    else
                    {
                        notificationLabel.Text = "Row " + (rowIndex + 1) + " cannot be moved to the Ad Special section\n as that section already contains " + actualSalesDataGridView.Rows[_rowIndexFromMouseDown].Cells[0].EditedFormattedValue + ".";
                        canBeMoved = false;
                    }
                }
            }
            return canBeMoved;
        }

        private void UpdateRowHeaderText(DataGridView dataGridView)
        {
            for (var i = 0; i < dataGridView.Rows.Count; i++)
            {
                dataGridView.Rows[i].HeaderCell.Value = (i + 1).ToString();
            }
            dataGridView.RefreshEdit();
        }

        private void getAttributeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Ad Special index " + _adSpecialIndex + ".");
        }

        private void addRecords_Click(object sender, EventArgs e)
        {
            var databaseTracker = new DatabaseTracker();
            var databaseReader = new DatabaseReader();
            var dateId = databaseReader.RetrieveDateIdByDateString(monthComboBox.Text + "/" + dayComboBox.Text + "/" + yearComboBox.Text, databaseTracker.DatabaseConnectionString);
            UpdateApcTables(dateId);
            UpdateInvoicesTable(dateId);
            UpdateWeeklySales(dateId);
        }
    }
}
