using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace Little_System_Cleaner.Registry_Cleaner.Helpers.Backup
{
    public class RegistryValue : IEquatable<RegistryValue>, IXmlSerializable
    {
        private string _name;
        private RegistryValueKind _type;
        private object _value;

        /// <summary>
        /// Returns value name
        /// </summary>
        /// <remarks>Returns string.Empty if default value name</remarks>
        [XmlAttribute("Name")]
        public string Name
        {
            get
            {
                if (string.IsNullOrEmpty(this._name))
                    return string.Empty;

                if (this._name.ToUpper() == "(DEFAULT)")
                    return string.Empty;

                return this._name;
            }
            set { this._name = value; }
        }

        [XmlAttribute("Type")]
        public RegistryValueKind Type
        {
            get { return this._type; }
            set { this._type = value; }
        }

        [XmlAttribute("Value")]
        public string XmlValue
        {
            get;
            set;
        }

        [XmlIgnore]
        public object Value
        {
            get { return this._value; }
            set { this._value = value; }
        }

        public RegistryValue()
        {

        }

        /// <summary>
        /// Only used for comparing values
        /// </summary>
        /// <param name="valueName">Value name</param>
        public RegistryValue(string valueName)
        {
            this._name = valueName;
        }

        public RegistryValue(string valueName, RegistryValueKind type, object value)
        {
            this._name = valueName;
            this._type = type;
            this._value = value;
        }

        #region IXmlSerializable Members
        public void ReadXml(System.Xml.XmlReader reader)
        {
            reader.MoveToContent();

            this.Name = reader.GetAttribute("Name");

            string strType = reader.GetAttribute("Type");
            this.Type = (RegistryValueKind)Enum.Parse(typeof(RegistryValueKind), strType);

            byte[] valByte = new byte[1];
            byte[] buffer = new byte[50];
            int readBytes = 0;

            if (this.Type != RegistryValueKind.MultiString)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    using (BinaryWriter bw = new BinaryWriter(ms))
                    {
                        while ((readBytes = reader.ReadElementContentAsBase64(buffer, 0, 50)) > 0)
                        {
                            bw.Write(buffer, 0, readBytes);
                        }
                    }

                    valByte = ms.ToArray();
                }
            }

            switch (this.Type)
            {
                case RegistryValueKind.Binary:
                    {
                        this.Value = valByte;

                        break;
                    }
                case RegistryValueKind.DWord:
                    {
                        string strValue = Encoding.UTF8.GetString(valByte);

                        uint val = Convert.ToUInt32(strValue);

                        this.Value = val;

                        break;
                    }
                case RegistryValueKind.QWord:
                    {
                        string strValue = Encoding.UTF8.GetString(valByte);

                        ulong val = Convert.ToUInt64(strValue);

                        this.Value = val;

                        break;
                    }
                case RegistryValueKind.MultiString:
                    {
                        List<string> strings = new List<string>();

                        if (reader.IsEmptyElement)
                        {
                            this.Value = strings.ToArray();
                            break;
                        }

                        XmlReader children = reader.ReadSubtree();

                        while (children.Read())
                        {
                            if (children.Name == "string" && !children.IsEmptyElement)
                            {
                                string s = children.ReadString();

                                if (string.IsNullOrWhiteSpace(s))
                                {
                                    strings.Add(string.Empty);
                                }
                                else
                                {
                                    byte[] base64Bytes = Convert.FromBase64String(s.Trim());
                                    string base64Str = Encoding.UTF8.GetString(base64Bytes);

                                    strings.Add(base64Str);
                                }
                            }
                        }

                        this.Value = strings.ToArray();

                        break;
                    }
                case RegistryValueKind.String:
                case RegistryValueKind.ExpandString:
                    {
                        this.Value = Encoding.UTF8.GetString(valByte);

                        break;
                    }
                default:
                    {
                        this.Value = valByte;

                        break;
                    }
            }

            if (this.Value == null)
                this.Value = string.Empty;
        }

        public void WriteXml(System.Xml.XmlWriter writer)
        {
            writer.WriteAttributeString("Name", this.Name);

            writer.WriteAttributeString("Type", this.Type.ToString());

            string strValue = string.Empty;

            switch (this.Type)
            {
                case RegistryValueKind.Binary:
                    {
                        byte[] bRawBuffer = (byte[])this.Value;
                        int bufLen = bRawBuffer.Length;

                        writer.WriteBase64(bRawBuffer, 0, bufLen);

                        break;
                    }
                case RegistryValueKind.DWord: // == REG_DWORD_LITTLE_ENDIAN
                    {
                        uint val = Convert.ToUInt32(this.Value);

                        strValue = Convert.ToString(val);

                        this.WriteBase64(writer, strValue);

                        break;
                    }
                case RegistryValueKind.QWord: // QWORD, QWORD_LITTLE_ENDIAN (64-bit integer)
                    {
                        ulong val = Convert.ToUInt64(this.Value);

                        strValue = Convert.ToString(val);

                        this.WriteBase64(writer, strValue);

                        break;
                    }
                case RegistryValueKind.MultiString:
                    {
                        string[] val = (string[])this.Value;

                        foreach (string s in val)
                        {
                            writer.WriteStartElement("string");

                            this.WriteBase64(writer, s);

                            writer.WriteEndElement();
                        }

                        break;
                    }

                case RegistryValueKind.String:
                case RegistryValueKind.ExpandString:
                    {
                        strValue = (string)this.Value;

                        this.WriteBase64(writer, strValue);

                        break;
                    }
                default:
                    {
                        byte[] bRawBuffer;
                        int nLen;

                        BinaryFormatter bf = new BinaryFormatter();
                        using (MemoryStream ms = new MemoryStream())
                        {
                            bf.Serialize(ms, this.Value);
                            nLen = (int)ms.Length;
                            bRawBuffer = ms.ToArray();
                        }

                        // Convert the new byte[] into a char[] and then into a string.
                        char[] asciiChars = new char[Encoding.ASCII.GetCharCount(bRawBuffer, 0, (int)nLen)];
                        Encoding.ASCII.GetChars(bRawBuffer, 0, (int)nLen, asciiChars, 0);

                        strValue = new string(asciiChars);

                        this.WriteBase64(writer, strValue);

                        break;
                    }
            }
        }

        private void WriteBase64(XmlWriter writer, string val)
        {
            int byteLen = Encoding.UTF8.GetByteCount(val);
            byte[] bytes = Encoding.UTF8.GetBytes(val);
            writer.WriteBase64(bytes, 0, byteLen);
        }

        public XmlSchema GetSchema()
        {
            return null;
        }
        #endregion

        #region IEquatable Members
        public bool Equals(RegistryValue regValue)
        {
            if (this.Name == regValue.Name)
                return true;
            else
                return false;
        }

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

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

        public static bool operator ==(RegistryValue regValue1, RegistryValue regValue2)
        {
            if ((object)regValue1 == null || ((object)regValue2) == null)
                return Object.Equals(regValue1, regValue2);

            return regValue1.Equals(regValue2);
        }

        public static bool operator !=(RegistryValue regValue1, RegistryValue regValue2)
        {
            if (regValue1 == null || regValue2 == null)
                return !Object.Equals(regValue1, regValue2);

            return !(regValue1.Equals(regValue2));
        }
        #endregion

    }
}
