using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Microsoft.Win32;
using System.Diagnostics;
using Little_System_Cleaner.Misc;
using System.Xml.Serialization;
using System.Windows;
using System.Xml;

namespace Little_System_Cleaner.Registry_Cleaner.Helpers.Backup
{
    public class BackupRegistry : IDisposable
    {
        private readonly string _filePath;
        private RegistryEntries _registryEntries;
        private Stream _stream = FileStream.Null;

        private bool disposed = false;

        public string FilePath
        {
            get { return this._filePath; }
        }

        public Stream Stream
        {
            get { return this._stream; }
        }

        public RegistryEntries RegistryEntries
        {
            get { return this._registryEntries; }
        }

        public DateTime Created
        {
            get
            {
                if (this.RegistryEntries == null)
                    return DateTime.MinValue;

                return this.RegistryEntries.CreatedDateTime;
            }
        }

        public BackupRegistry(string file)
        {
            if (string.IsNullOrEmpty(file))
                throw new ArgumentNullException("file");

            this._filePath = file;
            this._registryEntries = new RegistryEntries();
        }

        public bool Open(bool openExisting)
        {
            try
            {
                if (openExisting) {
                    // Open for reading
                    this._stream = File.OpenRead(this.FilePath);
                }
                else
                {
                    this._stream = File.OpenWrite(this.FilePath);

                    if (this.Stream.Length > 0)
                        (this.Stream as FileStream).SetLength(0);
                }
                    
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Unable to open file ({0}).\nError: {1}", this.FilePath, ex.Message);

                this._stream = FileStream.Null;
                return false;
            }

            return true;
        }

        public void Serialize()
        {
            if (this.Stream == FileStream.Null)
            {
                Console.WriteLine("Unable to serialize file as the file stream isn't open.");
                return;
            }

            var serializer = new XmlSerializer(this.RegistryEntries.GetType());
            serializer.Serialize(this.Stream, this.RegistryEntries);
        }

        public bool Deserialize(out string errorMsg)
        {
            if (this.Stream == FileStream.Null)
            {
                errorMsg = "Unable to deserialize from file as the file stream isn't open.";
                return false;
            }

            if (this.Stream.Length == 0)
            {
                errorMsg = "File seems to be blank.";
                return false;
            }

            if (this.Stream.Position > 0)
                this.Stream.Position = 0;

            XmlSerializer serializer = new XmlSerializer(this.RegistryEntries.GetType());

            try
            {
                using (XmlTextReader reader = new XmlTextReader(this.Stream))
                {
                    if (serializer.CanDeserialize(reader))
                    {
                        this._registryEntries = (RegistryEntries)serializer.Deserialize(reader);
                    }
                    else
                    {
                        throw new Exception("Backup file is in the wrong format.");
                    }
                }
            }
            catch (Exception ex)
            {
                errorMsg = string.Format("The following error occurred trying to restore the registry: {0}", ex.Message);

                return false;
            }


            errorMsg = "";
            return true;
        }

        public bool Restore()
        {
            // Just in case
            if (this.RegistryEntries.Count == 0)
                return false;

            foreach (RegistryEntry regEntry in this.RegistryEntries.RegEntries)
            {
                RegistryKey regKey = regEntry.CreateSubKey();

                if (regKey == null)
                {
                    string message = string.Format("Unable to create registry sub key ({0})", regEntry.RegistryKeyPath);

                    MessageBox.Show(App.Current.MainWindow, message, Utils.ProductName, MessageBoxButton.OK, MessageBoxImage.Error);
                    Debug.WriteLine(message);

                    continue;
                }

                if (regEntry.Values.Count > 0)
                {
                    foreach (RegistryValue regValue in regEntry.Values)
                    {
                        string valueName = regValue.Name;
                        object value = regValue.Value;
                        RegistryValueKind type = regValue.Type;

                        try
                        {
                            regKey.SetValue(valueName, value, type);
                        }
                        catch (Exception ex)
                        {
                            string message = string.Format("Unable to restore value name ({0}) for registry key ({1}).\nError: {2}", valueName, regKey.ToString(), ex.Message);

                            MessageBox.Show(App.Current.MainWindow, message, Utils.ProductName, MessageBoxButton.OK, MessageBoxImage.Error);
                            Debug.WriteLine(message);
                        }
                    }
                }
            }

            return true;
        }

        public bool Store(BadRegistryKey brk)
        {
            RegistryEntry regEntry = new RegistryEntry(brk.RegKeyPath);

            if (!string.IsNullOrEmpty(brk.ValueName))
            {
                if (this.RegistryEntries.Contains(regEntry))
                {
                    regEntry = this.RegistryEntries[this.RegistryEntries.IndexOf(regEntry)];

                    return regEntry.AddValue(brk.ValueName);
                }
                else
                {
                    bool ret = regEntry.AddValue(brk.ValueName);

                    this.RegistryEntries.Add(regEntry);
                    return ret;
                }
            }
            else
            {
                // Recurse subkeys
                this.RecurseSubKeys(brk.RegKeyPath);

                return true;
            }
        }

        private void RecurseSubKeys(string regPath)
        {
            RegistryEntry regEntry = new RegistryEntry(regPath);

            if (regEntry.RegistryKey == null)
                return;

            bool alreadyAdded = this.RegistryEntries.Contains(regEntry);

            if (alreadyAdded)
                regEntry = this.RegistryEntries[this.RegistryEntries.IndexOf(regEntry)];

            // Add all values
            regEntry.AddValues();

            if (!alreadyAdded)
                this.RegistryEntries.Add(regEntry);

            foreach (string subKey in regEntry.RegistryKey.GetSubKeyNames())
            {
                string subKeyPath = regPath + "\\" + subKey;
                this.RecurseSubKeys(subKeyPath);
            }
        }

        #region IDisposable Members
        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // Dispose managed resources.
                    if (this.Stream != FileStream.Null)
                    {
                        this.Stream.Close();
                        this._stream = FileStream.Null;
                    }

                    if (this.RegistryEntries.Count > 0)
                        this.RegistryEntries.Clear();
                }

                disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}
