using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
// Imported class for drive insertion event handling
using Dolinay; 

namespace DriveLogger
{
    /// <summary>
    /// Struct used for storing detailed information for each drive that is detected
    /// </summary>
    public struct DriveEntry
    {
        public DateTime time;
        public string drive;
        public string label;
        public string size;
        public string owner;
    }
    public partial class MainForm : Form
    {
        // List of drives that are displayed in the listview
        private static List<DriveEntry> driveList = new List<DriveEntry>();
        private static DateTime lastDriveInsertedTime;
        // Comparison time is set here for between drive detections
        // Format is in hours, minutes, seconds
        TimeSpan detectionTime = new TimeSpan(0, 0, 0, 10, 0);
        // Log location for the debug and logging texts
        private static string logLocation = "C:\\DriveLog.txt";
        private static int deviceRemovalRefreshInterval = 1000;
        
        public MainForm()
        {
            // Disable thread safe checking.
            // !! -- NEED TO IMPLEMENT THREAD SAFE UPDATING OF FORM -- !!
            CheckForIllegalCrossThreadCalls = false;

            // Clears the list of DriveEntry structs
            driveList.Clear();
            InitializeComponent();
            // Initializes a new DriveDetector class used to detect new drive events
            DriveDetector driveDetector = new DriveDetector();
            driveDetector.DeviceArrived += new DriveDetectorEventHandler(driveDetector_DeviceArrived);
            //driveDetector.DeviceRemoved += new DriveDetectorEventHandler(driveDetector_DeviceRemoved);
            this.listView_Drives.AfterLabelEdit += new LabelEditEventHandler(listView_Drives_AfterLabelEdit);

            // KeyPreview events for keyboard shortcuts
            this.KeyPreview = true;
            this.KeyDown += new KeyEventHandler(MainForm_KeyDown);
            
            // Appends new session start text to log file
            using (StreamWriter sw = File.AppendText(logLocation))
            {
                sw.WriteLine("-- New Session Started --");
                sw.WriteLine("Initializing form details");
            }
            
            // Adds columns to the listview
            this.listView_Drives.Columns.Add("Owner", 145, HorizontalAlignment.Left);
            this.listView_Drives.Columns.Add("Time", 130, HorizontalAlignment.Center);
            this.listView_Drives.Columns.Add("Drive", 40, HorizontalAlignment.Center);
            this.listView_Drives.Columns.Add("Label", 109, HorizontalAlignment.Center);
            this.listView_Drives.Columns.Add("Size", 60, HorizontalAlignment.Center);

            paintDriveListbox();

            // Creates and runs a background worker for detecting if drives have been
            // removed and refreshing the listview accordingly
            BackgroundWorker bgWorker = new BackgroundWorker();
            bgWorker.DoWork += new DoWorkEventHandler(bgWorker_DoWork);

            // Starts the background worker thread
            bgWorker.RunWorkerAsync();
        }  
        void MainForm_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.OemQuestion:
                    AboutBox window = new AboutBox();
                    window.ShowDialog();
                    break;
                case Keys.F5:
                    refreshDrives();
                    break;
            }

        }
        void bgWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            // currentDrives holds a list of 'previous' drives to compare to
            List<string> currentDrives = new List<string>();
            // newDrives holds a list of the most current drives detected
            DriveInfo[] newDrives = DriveInfo.GetDrives();
            // Sets the two lists of drives to hold the same drives
            foreach (DriveInfo drive in newDrives)
            {
                currentDrives.Add(drive.Name);
            }
            // Loop that checks for drives that are removed
            while (true)
            {
                // Updates the list of current drives
                newDrives = DriveInfo.GetDrives();
                // If the count of current drives is more than the count of previous drives
                if (newDrives.Length < currentDrives.Count)
                {
                    // Goes through each list and finds the drive that was recently removed
                    bool removedDriveFound = false;
                    foreach (string str in currentDrives)
                    {
                        removedDriveFound = false;
                        // Loop here checks for non-matching entries in the two lists
                        foreach (DriveInfo drive in newDrives)
                        {
                            // If entries match, drive was not removed
                            if (str == drive.Name)
                            {
                                removedDriveFound = true;
                                break;
                            }
                        }
                        // If list mismatch is detected, remove from driveList
                        if (removedDriveFound == false)
                        {
                            // Removes drive from driveList
                            foreach (DriveEntry entry in driveList)
                            {
                                if (str == entry.drive)
                                {
                                    driveList.Remove(entry);
                                    using (StreamWriter sw = File.AppendText(logLocation))
                                    {
                                        sw.WriteLine("Drive Removed --  [" + entry.time.ToString() + "]\t" + entry.drive + "\t\"" + entry.label + "\"\t" + entry.size);
                                    }
                                    break;
                                }
                            }

                            paintDriveListbox();
                        }
                    }
                    // Clears and refreshes the currentDrives list
                    currentDrives.Clear();
                    foreach (DriveInfo drive in newDrives)
                    {
                        currentDrives.Add(drive.Name);
                    }
                }
                else
                {
                    // Sets the two lists to hold the same drives
                    currentDrives.Clear();
                    foreach (DriveInfo drive in newDrives)
                    {
                        currentDrives.Add(drive.Name);
                    }
                }
                // Sleeps the thread for a second
                System.Threading.Thread.Sleep(deviceRemovalRefreshInterval);
            }
        }
        void driveDetector_DeviceArrived(object sender, DriveDetectorEventArgs e)
        {
            // Event call for when a new drive is detected by DriveDetector

            // Disable e.HookQueryRemoved to prevent a hook being attached to the volume
            //e.HookQueryRemove = true;

            // Creates and populates a new DriveEntry
            DriveEntry newEntry = new DriveEntry();
            newEntry.time = DateTime.Now;
            newEntry.drive = e.Drive;

            DriveInfo tempDrive = null;
            DriveInfo[] allDrives = DriveInfo.GetDrives();
            foreach (DriveInfo drive in allDrives)
            {
                if (drive.IsReady)
                {
                    if (drive.Name == newEntry.drive)
                    {
                        tempDrive = drive;
                        break;
                    }
                }
            }
            newEntry.label = tempDrive.VolumeLabel;

            // Determines the size of the attached drive
            if ((tempDrive.TotalSize / 1073741824) > 0)
                newEntry.size = (tempDrive.TotalSize / 1073741824).ToString() + " GB";
            else
                newEntry.size = (tempDrive.TotalSize / 1048576).ToString() + " MB";

            // Checks if the drive was detected within a second of the previous drive
            // If so, set the drive label to point to the previous drive
            int compare = (newEntry.time.Subtract(lastDriveInsertedTime)).CompareTo(detectionTime);
            // Sets the last time to be the time of the recently detected drive
            lastDriveInsertedTime = newEntry.time;
            if (compare <= 0)
            {
                newEntry.owner = " ^ -- Same As -- ^ ";
                lastDriveInsertedTime = newEntry.time;
            }
            else
            {
                LabelPrompt label = new LabelPrompt();
                label.ShowDialog();
                newEntry.owner = label.driveLabel;
            }

            // Adds the new DriveEntry into driveList
            driveList.Add(newEntry);

            using (StreamWriter sw = File.AppendText(logLocation))
            {
                sw.WriteLine("Drive Attached -- [" + newEntry.time.ToString() + "]\t\"" + newEntry.owner + "\"\t" + newEntry.drive + "\t\"" + newEntry.label + "\"\t" + newEntry.size);
            }

            paintDriveListbox();
        }
        void listView_Drives_AfterLabelEdit(object sender, LabelEditEventArgs e)
        {
            // Logs the new label if it is changed
            if (e.Label != null)
            {
                using (StreamWriter sw = File.AppendText(logLocation))
                {
                    ListViewItem entry = listView_Drives.Items[e.Item];

                    sw.WriteLine("Label \"" + e.Label + "\" added to drive " + entry.SubItems[2].Text);
                }
            }
        }
        private void paintDriveListbox()
        {
            // Updates the listview
            this.listView_Drives.BeginUpdate();

            this.listView_Drives.Items.Clear();

            // Adds each entry from the driveList into the listView
            foreach (DriveEntry entry in driveList)
            {
                if (entry.owner != "System Reserved")
                {
                    ListViewItem item = new ListViewItem();
                    item.Text = entry.owner;
                    ListViewItem.ListViewSubItem subTime = new ListViewItem.ListViewSubItem();
                    subTime.Text = entry.time.ToString();
                    item.SubItems.Add(subTime);
                    ListViewItem.ListViewSubItem subDrive = new ListViewItem.ListViewSubItem();
                    subDrive.Text = entry.drive;
                    item.SubItems.Add(subDrive);
                    ListViewItem.ListViewSubItem subLabel = new ListViewItem.ListViewSubItem();
                    subLabel.Text = entry.label;
                    item.SubItems.Add(subLabel);
                    ListViewItem.ListViewSubItem subSize = new ListViewItem.ListViewSubItem();
                    subSize.Text = entry.size;
                    item.SubItems.Add(subSize);

                    this.listView_Drives.Items.Add(item);
                }
            }

            this.listView_Drives.EndUpdate();
        }
        private void refreshDrives()
        {
            bool removedDriveFound;
            List<DriveEntry> drivesToRemove = new List<DriveEntry>();
            // Checks each entry in driveList to see if matching drive is found on list
            // of current drives. Removes entry from driveList if drive is not found.
            DriveInfo[] currentDrives = DriveInfo.GetDrives();
            for (int i = 0; i < driveList.Count; i++)
            //foreach (DriveEntry storedDrives in driveList)
            {
                DriveEntry storedDrive = driveList[i];
                removedDriveFound = false;
                // Loop here checks for non-matching entries in the two lists
                foreach (DriveInfo currentDrive in currentDrives)
                {
                    // If entries match, drive was not removed
                    if (storedDrive.drive == currentDrive.Name)
                    {
                        removedDriveFound = true;
                        break;
                    }
                }
                // If list mismatch is detected, remove from driveList
                if (removedDriveFound == false)
                {
                    drivesToRemove.Add(storedDrive);
                }
            }
            // Removes drive from driveList
            foreach (DriveEntry entry in drivesToRemove)
            {
                // Removes drive from driveList
                foreach (DriveEntry entry2 in driveList)
                {
                    if (entry.drive == entry2.drive)
                    {
                        driveList.Remove(entry);
                        break;
                    }
                }
            }

            paintDriveListbox();
        }
        //void driveDetector_DeviceRemoved(object sender, DriveDetectorEventArgs e)
        //{
        //    //DriveEntry entryToRemove = new DriveEntry();
        //    foreach (DriveEntry entry in driveList)
        //    {
        //        if (e.Drive == entry.drive)
        //        {
        //            //entryToRemove = entry;
        //            driveList.Remove(entry);
        //            break;
        //        }
        //    }
        //    //driveList.Remove(entryToRemove);

        //    using (StreamWriter sw = File.AppendText(logLocation))
        //    {
        //        //sw.WriteLine("Drive Removed --  [" + entryToRemove.time.ToString() + "]\t" + entryToRemove.drive + "\t\"" + entryToRemove.label + "\"\t" + entryToRemove.size);
        //    }

        //    paintDriveListbox();
        //}
    }
}
