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

namespace Little_System_Cleaner.Registry_Cleaner.Helpers.Backup
{
    public class RegistryEntry : IEquatable<RegistryEntry>
    {
        private string _rootKey;
        private string _subKey;
        private List<RegistryValue> _values;


        [NonSerialized]
        private RegistryKey _regKey = null;

        /// <summary>
        /// Gets the root key (HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER, etc)
        /// </summary>
        [XmlAttribute("RootKey")]
        public string RootKey
        {
            get { return this._rootKey.ToUpper(); }
            set { this._rootKey = value; }
        }

        /// <summary>
        /// Gets the subkey
        /// </summary>
        /// <remarks>This can be null/empty if no subkey exists</remarks>
        [XmlAttribute("SubKey")]
        public string SubKey
        {
            get { return this._subKey; }
            set { this._subKey = value; }
        }

        /// <summary>
        /// Gets a list contains values of subkey
        /// </summary>
        [XmlArray("Values"), XmlArrayItem("Value")]
        public List<RegistryValue> Values
        {
            get { return this._values; }
            set { this._values = value; }
        }

        /// <summary>
        /// Gets the path for the registry key
        /// </summary>
        [XmlIgnore]
        public string RegistryKeyPath
        {
            get
            {
                if (string.IsNullOrEmpty(this._subKey))
                    return this.RootKey;
                else
                    return this.RootKey + "\\" + this.SubKey;
            }
        }

        /// <summary>
        /// Gets RegistryKey for registry key path
        /// </summary>
        /// <remarks>This will ONLY open a registry key. To create it, use CreateSubKey()</remarks>
        [XmlIgnore]
        public RegistryKey RegistryKey
        {
            get
            {
                if (string.IsNullOrEmpty(this.RootKey))
                    return null;

                if (this._regKey == null)
                {
                    try
                    {
                        if (this.RootKey.CompareTo("HKEY_CLASSES_ROOT") == 0)
                            this._regKey = Registry.ClassesRoot;
                        else if (this.RootKey.CompareTo("HKEY_CURRENT_USER") == 0)
                            this._regKey = Registry.CurrentUser;
                        else if (this.RootKey.CompareTo("HKEY_LOCAL_MACHINE") == 0)
                            this._regKey = Registry.LocalMachine;
                        else if (this.RootKey.ToUpper().CompareTo("HKEY_USERS") == 0)
                            this._regKey = Registry.Users;
                        else if (this.RootKey.ToUpper().CompareTo("HKEY_CURRENT_CONFIG") == 0)
                            this._regKey = Registry.CurrentConfig;

                        if (!string.IsNullOrEmpty(this.SubKey))
                            this._regKey = this._regKey.OpenSubKey(this.SubKey);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("Unable to open registry key using registry path ({0}).\nError: {1}", this.RegistryKeyPath, ex.Message);
                        this._regKey = null;
                    }
                    
                }

                return this._regKey;
            }
        }

        public RegistryEntry()
        {

        }

        public RegistryEntry(string regPath)
        {
            this._values = new List<RegistryValue>();

            int slashIndex = regPath.IndexOf('\\');

            if (slashIndex >= 0)
            {
                this._rootKey = regPath.Substring(0, regPath.IndexOf('\\'));
                this._subKey = regPath.Substring(regPath.IndexOf('\\') + 1).Trim().Trim('\\');
            }
            else
            {
                this._rootKey = regPath;
            }
        }

        /// <summary>
        /// Adds subkey to registry key
        /// </summary>
        /// <returns>Registry key, or null if unable to be created</returns>
        public RegistryKey CreateSubKey()
        {
            RegistryKey regKey = null;

            if (this.RootKey.CompareTo("HKEY_CLASSES_ROOT") == 0)
                regKey = Registry.ClassesRoot;
            else if (this.RootKey.CompareTo("HKEY_CURRENT_USER") == 0)
                regKey = Registry.CurrentUser;
            else if (this.RootKey.CompareTo("HKEY_LOCAL_MACHINE") == 0)
                regKey = Registry.LocalMachine;
            else if (this.RootKey.ToUpper().CompareTo("HKEY_USERS") == 0)
                regKey = Registry.Users;
            else if (this.RootKey.ToUpper().CompareTo("HKEY_CURRENT_CONFIG") == 0)
                regKey = Registry.CurrentConfig;

            if (string.IsNullOrEmpty(this.SubKey))
                return regKey;

            if (this.SubKey.IndexOf('\\') > 0)
            {
                foreach (string subKey in this.SubKey.Split('\\'))
                {
                    try
                    {
                        regKey = regKey.CreateSubKey(subKey);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("Unable to create sub key ({0}) for registry path ({1}).\nError: {2}", subKey, this.RegistryKeyPath, ex.Message);
                        regKey = null;
                    }
                    
                    if (regKey == null)
                        break;
                }
            }
            else
            {
                try
                {
                    regKey = regKey.CreateSubKey(this.SubKey);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("Unable to create sub key ({0}) for registry path ({1}).\nError: {2}", this.SubKey, this.RegistryKeyPath, ex.Message);
                    regKey = null;
                }
            }

            return regKey;
        }

        /// <summary>
        /// Gets all values from registry key
        /// </summary>
        /// <returns>True if values added</returns>
        public bool AddValues()
        {
            if (this.RegistryKey == null)
            {
                string message = string.Format("Failed to open registry key ({0}) to get all values", this.RegistryKeyPath);
                Debug.WriteLine(message);
                return false;
            }

            if (this.RegistryKey.ValueCount > 0)
            {
                string[] valueNames = null;

                try
                {
                    valueNames = this.RegistryKey.GetValueNames();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("Failed to get value names from registry key ({0}).\nError: {1}", this.RegistryKeyPath, ex.Message);
                    return false;
                }

                // Add values
                foreach (string valueName in valueNames)
                {
                    this.AddValue(valueName);
                }
            }

            return true;
        }

        /// <summary>
        /// Adds value from registry key
        /// </summary>
        /// <param name="valueName">Value name</param>
        /// <returns>True if value added</returns>
        public bool AddValue(string valueName)
        {
            RegistryValueKind regValueKind;
            object value;

            if (this.RegistryKey == null)
            {
                Debug.WriteLine("Failed to open registry key ({0}) to get a value ({1})", this.RegistryKeyPath, valueName);
                return false;
            }

            try
            {
                regValueKind = this.RegistryKey.GetValueKind(valueName);
                value = this.RegistryKey.GetValue(valueName);

                if (value == null)
                    throw new Exception("Value doesn't seem to exist or there is insufficient permissions to access it.");
            }
            catch (Exception ex)
            {
                Debug.WriteLine("Failed to get value ({0} from registry key ({1}).\nError: {2}", valueName, this.RegistryKeyPath, ex.Message);
                return false;
            }

            RegistryValue regValue = new RegistryValue(valueName, regValueKind, value);

            if (!this.Values.Contains(regValue))
                this.Values.Add(regValue);

            return true;
        }

        #region IEquatable Methods
        public bool Equals(RegistryEntry regEntry)
        {
            if (regEntry.RegistryKeyPath == this.RegistryKeyPath)
                return true;
            else
                return false;
        }

        public override bool Equals(object obj)
        {
            if (obj is RegistryEntry)
                return this.Equals(obj as RegistryEntry);
            else
                return false;
        }

        public override int GetHashCode()
        {
            return this.RegistryKeyPath.GetHashCode();
        }

        public static bool operator ==(RegistryEntry regEntry1, RegistryEntry regEntry2)
        {
            if ((object)regEntry1 == null || ((object)regEntry2) == null)
                return Object.Equals(regEntry1, regEntry2);

            return regEntry1.Equals(regEntry2);
        }

        public static bool operator !=(RegistryEntry regEntry1, RegistryEntry regEntry2)
        {
            if (regEntry1 == null || regEntry2 == null)
                return !Object.Equals(regEntry1, regEntry2);

            return !(regEntry1.Equals(regEntry2));
        }
        #endregion
    }
}
