/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 * 
 *  http://aws.amazon.com/apache2.0
 * 
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Amazon.DynamoDBv2.Model;
using Amazon.Runtime;
using Amazon.Runtime.Internal.Util;
using Amazon.Util;
namespace Amazon.DynamoDBv2.DocumentModel
{
    /// 
    /// A collection of attribute key-value pairs that defines
    /// an item in DynamoDB.
    /// 
    public class Document : DynamoDBEntry, IDictionary
    {
        #region Private/internal members
        private Dictionary originalValues;
        private Dictionary currentValues;
        #endregion
        #region Constructors
        /// 
        /// Constructs an empty Document.
        /// 
        public Document()
        {
            originalValues = new Dictionary();
            currentValues = new Dictionary();
        }
        /// 
        /// Constructs a Document with the passed-in values as its attribute values.
        /// 
        /// 
        public Document(Dictionary values)
        {
            originalValues = new Dictionary();
            currentValues = new Dictionary(values);
        }
        internal Document(Document source)
        {
            originalValues = new Dictionary(source.originalValues);
            currentValues = new Dictionary(source.currentValues);
        }
        #endregion
        #region Properties/accessors
        /// 
        /// Attribute accessor, allows getting or setting of an individual attribute.
        /// 
        /// Name of the attribute.
        /// Current value of the attribute.
        public DynamoDBEntry this[string key]
        {
            get
            {
                return currentValues[key];
            }
            set
            {
                if (value == null)
                {
                    currentValues[key] = new Primitive();
                }
                else
                {
                    currentValues[key] = value;
                }
            }
        }
        /// 
        /// Returns true if the attribute has been changed.
        /// 
        /// Name of the attribute.
        /// True if the attribute has been changed.
        public bool IsAttributeChanged(string attributeName)
        {
            DynamoDBEntry original;
            this.originalValues.TryGetValue(attributeName, out original);
            DynamoDBEntry current;
            this.currentValues.TryGetValue(attributeName, out current);
            if ((original != null && current == null) || (original == null && current != null))
                return true;
            if (original == null && current == null)
                return false;
            return !original.Equals(current);
        }
        /// 
        /// Returns true if the document contains attributes that have not been saved.
        /// 
        /// True if the document contains attributes that have not been saved.
        public bool IsDirty()
        {
            Dictionary keys = new Dictionary();
            foreach (string key in currentValues.Keys)
            {
                keys.Add(key, key);
            }
            foreach (string key in originalValues.Keys)
            {
                if (!keys.ContainsKey(key))
                {
                    keys.Add(key, key);
                }
            }
            foreach (string key in keys.Keys)
            {
                if (this.IsAttributeChanged(key))
                {
                    return true;
                }
            }
            return false;
        }
        /// 
        /// Gets the value associated with the specified attribute value.
        /// 
        /// Attribute name
        /// 
        /// If the specified attribute value is found, returns the value associated with the 
        /// attribute; otherwise, null.
        /// 
        /// True if attribute is found; false otherwise
        public bool TryGetValue(string attributeName, out DynamoDBEntry entry)
        {
            return this.currentValues.TryGetValue(attributeName, out entry);
        }
        /// 
        /// Determines if a specific attribute is set on the Document.
        /// 
        /// Attribute name
        /// Returns true if the specified attribute is found; false otherwise.
        public bool Contains(string attributeName)
        {
            return this.currentValues.ContainsKey(attributeName);
        }
        /// 
        /// Returns a new instance of Document where all unconverted .NET types
        /// are converted to DynamoDBEntry types using a specific conversion.
        /// 
        /// 
        /// 
        public Document ForceConversion(DynamoDBEntryConversion conversion)
        {
            Document newDocument = new Document();
            foreach(var kvp in this)
            {
                string name = kvp.Key;
                DynamoDBEntry entry = kvp.Value;
                var unconvertedEntry = entry as UnconvertedDynamoDBEntry;
                if (unconvertedEntry != null)
                    entry = unconvertedEntry.Convert(conversion);
                var doc = entry as Document;
                if (doc != null)
                    entry = doc.ForceConversion(conversion);
                var list = entry as DynamoDBList;
                if (list != null)
                    entry = list.ForceConversion(conversion);
                newDocument[name] = entry;
            }
            return newDocument;
        }
        #endregion
        #region Private/internal methods
        internal void CommitChanges()
        {
            this.originalValues.Clear();
            foreach (var kvp in currentValues)
            {
                this.originalValues[kvp.Key] = kvp.Value.Clone() as DynamoDBEntry;
            }
        }
        // Converts a Numeric Primitive value from a service attribute to a DynamoDBEntry that can
        // be converted to a DateTime.
        internal static DynamoDBEntry EpochSecondsToDateTime(DynamoDBEntry entry, string attributeName)
        {
            var primitive = entry.AsPrimitive();
            // only try to convert N types to epoch time
            if (primitive != null &&
                primitive.Type == DynamoDBEntryType.Numeric)
            {
                DateTime? dateTime = null;
                try
                {
                    var epochSeconds = primitive.AsInt();
                    dateTime = AWSSDKUtils.ConvertFromUnixEpochSeconds(epochSeconds);
                }
                catch (Exception e)
                {
                    var logger = Logger.GetLogger(typeof(Document));
                    logger.InfoFormat(
                        "Encountered error attempting to convert attribute '{0}' with value '{1}' to DateTime: {2}",
                        attributeName, entry, e);
                }
                if (dateTime.HasValue)
                {
                    entry = (Primitive)(dateTime.Value);
                }
            }
            return entry;
        }
        // Converts a user-supplied DateTime-convertible DynamoDBEntry to epoch seconds stored in a Numeric Primitive.
        internal static DynamoDBEntry DateTimeToEpochSeconds(DynamoDBEntry entry, string attributeName)
        {
            int? epochSeconds = null;
            try
            {
                var dateTime = entry.AsDateTime();
                epochSeconds = AWSSDKUtils.ConvertToUnixEpochSeconds(dateTime);
            }
            catch (Exception e)
            {
                var logger = Logger.GetLogger(typeof(Document));
                logger.InfoFormat(
                    "Encountered error attempting to convert '{0}' with value '{1}' to epoch seconds: {1}",
                    attributeName, entry, e);
            }
            if (epochSeconds.HasValue)
            {
                entry = (Primitive)(epochSeconds.Value);
            }
            return entry;
        }
        internal static Document FromAttributeMap(Dictionary data, IEnumerable epochAttributes)
        {
            Document doc = new Document();
            if (data != null)
            {
                // Add Primitives and PrimitiveLists
                foreach (var attribute in data)
                {
                    string wholeKey = attribute.Key;
                    AttributeValue value = attribute.Value;
                    DynamoDBEntry convertedValue = AttributeValueToDynamoDBEntry(value);
                    if (convertedValue != null)
                        doc.currentValues[wholeKey] = convertedValue;
                }
            }
            if (epochAttributes != null)
            {
                foreach (var epochAttribute in epochAttributes)
                {
                    DynamoDBEntry epochEntry;
                    if (doc.currentValues.TryGetValue(epochAttribute, out epochEntry))
                    {
                        doc.currentValues[epochAttribute] = EpochSecondsToDateTime(epochEntry, epochAttribute);
                    }
                }
            }
            doc.CommitChanges();
            return doc;
        }
        internal Dictionary ToAttributeMap(DynamoDBEntryConversion conversion,
            IEnumerable epochAttributes, bool isEmptyStringValueEnabled)
        {
            if (conversion == null) throw new ArgumentNullException("conversion");
            Dictionary ret = new Dictionary();
            foreach (var kvp in currentValues)
            {
                var attributeName = kvp.Key;
                var entry = kvp.Value;
                ApplyEpochRules(epochAttributes, attributeName, ref entry);
                var attributeConversionConfig = new AttributeConversionConfig(conversion, isEmptyStringValueEnabled);
                var value = entry.ConvertToAttributeValue(attributeConversionConfig);
                if (value != null)
                {
                    ret.Add(attributeName, value);
                }
            }
            return ret;
        }
        internal Dictionary ToExpectedAttributeMap(DynamoDBEntryConversion conversion,
            IEnumerable epochAttributes, bool isEmptyStringValueEnabled)
        {
            if (conversion == null) throw new ArgumentNullException("conversion");
            Dictionary ret = new Dictionary();
            foreach (var kvp in currentValues)
            {
                var attributeName = kvp.Key;
                var entry = kvp.Value;
                ApplyEpochRules(epochAttributes, attributeName, ref entry);
                var attributeConversionConfig = new AttributeConversionConfig(conversion, isEmptyStringValueEnabled);
                ret.Add(attributeName, entry.ConvertToExpectedAttributeValue(attributeConversionConfig));
            }
            return ret;
        }
        internal Dictionary ToAttributeUpdateMap(DynamoDBEntryConversion conversion,
            bool changedAttributesOnly, IEnumerable epochAttributes, bool isEmptyStringValueEnabled)
        {
            if (conversion == null) throw new ArgumentNullException("conversion");
            Dictionary ret = new Dictionary();
            foreach (var kvp in currentValues)
            {
                string attributeName = kvp.Key;
                DynamoDBEntry entry = kvp.Value;
                ApplyEpochRules(epochAttributes, attributeName, ref entry);
                if (!changedAttributesOnly || this.IsAttributeChanged(attributeName))
                {
                    var attributeConversionConfig = new AttributeConversionConfig(conversion, isEmptyStringValueEnabled);
                    ret.Add(attributeName, entry.ConvertToAttributeUpdateValue(attributeConversionConfig));
                }
            }
            return ret;
        }
        private static void ApplyEpochRules(IEnumerable epochAttributes, string attributeName, ref DynamoDBEntry entry)
        {
            if (epochAttributes != null)
            {
                foreach (var epochAttribute in epochAttributes)
                {
                    if (string.Equals(epochAttribute, attributeName, StringComparison.Ordinal))
                    {
                        entry = DateTimeToEpochSeconds(entry, attributeName);
                    }
                }
            }
        }
        #endregion
        #region DynamoDB conversion
        /// 
        /// 
        /// Converts the current Document into a matching JSON string.
        /// 
        /// 
        /// DynamoDB types are a superset of JSON types, thus the following DynamoDB cannot
        /// be properly represented as JSON data:
        ///  PrimitiveList (SS, NS, BS types) - these sets will be converted to JSON arrays
        ///  Binary Primitive (B type) - binary data will be converted to Base64 strings
        /// 
        /// 
        /// If the resultant JSON is passed to Document.FromJson, the binary values will be
        /// treated as Base64 strings. Invoke Document.DecodeBase64Attributes to decode these
        /// strings into binary data.
        /// 
        /// 
        /// JSON string corresponding to the current Document.
        public string ToJson()
        {
            return JsonUtils.ToJson(this, prettyPrint: false);
        }
        /// 
        /// 
        /// Converts the current Document into a matching pretty JSON string.
        /// 
        /// 
        /// DynamoDB types are a superset of JSON types, thus the following DynamoDB cannot
        /// be properly represented as JSON data:
        ///  PrimitiveList (SS, NS, BS types) - these sets will be converted to JSON arrays
        ///  Binary Primitive (B type) - binary data will be converted to Base64 strings
        /// 
        /// 
        /// If the resultant JSON is passed to Document.FromJson, the binary values will be
        /// treated as Base64 strings. Invoke Document.DecodeBase64Attributes to decode these
        /// strings into binary data.
        /// 
        /// 
        /// JSON string corresponding to the current Document.
        public string ToJsonPretty()
        {
            return JsonUtils.ToJson(this, prettyPrint: true);
        }
        /// 
        /// Creates a map of attribute names mapped to AttributeValue objects.
        /// Converts .NET types using the conversion specified by AWSConfigs.DynamoDBConfig.ConversionSchema
        /// 
        /// 
        public Dictionary ToAttributeMap()
        {
            return ToAttributeMap(DynamoDBEntryConversion.CurrentConversion);
        }
        /// 
        /// Creates a map of attribute names mapped to AttributeValue objects.
        /// 
        /// Conversion to use for converting .NET values to DynamoDB values.
        /// 
        public Dictionary ToAttributeMap(DynamoDBEntryConversion conversion)
        {
            return ToAttributeMap(conversion, epochAttributes: null, isEmptyStringValueEnabled: false);
        }
        /// 
        /// Creates a map of attribute names mapped to AttributeValue objects.
        /// 
        /// Conversion to use for converting .NET values to DynamoDB values.
        /// If the property is false, empty string values will be interpreted as null values.
        /// 
        public Dictionary ToAttributeMap(DynamoDBEntryConversion conversion, bool isEmptyStringValueEnabled)
        {
            return ToAttributeMap(conversion, epochAttributes: null, isEmptyStringValueEnabled: isEmptyStringValueEnabled);
        }
        /// 
        /// Creates a map of attribute names mapped to ExpectedAttributeValue objects.
        /// 
        /// 
        public Dictionary ToExpectedAttributeMap()
        {
            return ToExpectedAttributeMap(DynamoDBEntryConversion.CurrentConversion);
        }
        /// 
        /// Creates a map of attribute names mapped to ExpectedAttributeValue objects.
        /// 
        /// Conversion to use for converting .NET values to DynamoDB values.
        /// 
        public Dictionary ToExpectedAttributeMap(DynamoDBEntryConversion conversion)
        {
            return ToExpectedAttributeMap(conversion, false);
        }
        /// 
        /// Creates a map of attribute names mapped to ExpectedAttributeValue objects.
        /// 
        /// Conversion to use for converting .NET values to DynamoDB values.
        /// If the property is false, empty string values will be interpreted as null values.
        /// 
        public Dictionary ToExpectedAttributeMap(DynamoDBEntryConversion conversion,
            bool isEmptyStringValueEnabled)
        {
            return ToExpectedAttributeMap(conversion, epochAttributes: null, isEmptyStringValueEnabled: isEmptyStringValueEnabled);
        }
        /// 
        /// Creates a map of attribute names mapped to AttributeValueUpdate objects.
        /// 
        /// If true, only attributes that have been changed will be in the map.
        /// 
        public Dictionary ToAttributeUpdateMap(bool changedAttributesOnly)
        {
            return ToAttributeUpdateMap(DynamoDBEntryConversion.CurrentConversion, changedAttributesOnly, false);
        }
        /// 
        /// Creates a map of attribute names mapped to AttributeValueUpdate objects.
        /// 
        /// If true, only attributes that have been changed will be in the map.
        /// Conversion to use for converting .NET values to DynamoDB values.
        /// 
        public Dictionary ToAttributeUpdateMap(DynamoDBEntryConversion conversion, bool changedAttributesOnly)
        {
            return ToAttributeUpdateMap(conversion, changedAttributesOnly, epochAttributes: null, isEmptyStringValueEnabled: false);
        }
        /// 
        /// Creates a map of attribute names mapped to AttributeValueUpdate objects.
        /// 
        /// If true, only attributes that have been changed will be in the map.
        /// Conversion to use for converting .NET values to DynamoDB values.
        /// If the property is false, empty string values will be interpreted as null values.
        /// 
        public Dictionary ToAttributeUpdateMap(DynamoDBEntryConversion conversion,
            bool changedAttributesOnly, bool isEmptyStringValueEnabled)
        {
            return ToAttributeUpdateMap(conversion, changedAttributesOnly, epochAttributes: null, isEmptyStringValueEnabled: isEmptyStringValueEnabled);
        }
        /// 
        /// Returns the names of all the attributes.
        /// 
        /// List of attribute names.
        public List GetAttributeNames()
        {
            return new List(this.currentValues.Keys);
        }
        /// 
        /// 
        /// Decodes root-level Base64-encoded strings to their binary representations.
        /// Use this method if the Document was constructed from JSON that contains
        /// base64-encoded binary values, which result from calling ToJson on a Document
        /// with binary data.
        /// 
        /// 
        /// Individual strings become binary data.
        /// List and sets of Base64-encoded strings become lists and sets of binary data.
        /// 
        /// 
        /// Names of root-level attributes to decode.
        public void DecodeBase64Attributes(params string[] attributeNames)
        {
            JsonUtils.DecodeBase64Attributes(this, attributeNames);
        }
        #endregion
        #region Attribute to DynamoDBEntry conversion
        internal static DynamoDBEntry AttributeValueToDynamoDBEntry(AttributeValue attributeValue)
        {
            Primitive primitive;
            if (TryToPrimitive(attributeValue, out primitive))
                return primitive;
            PrimitiveList primitiveList;
            if (TryToPrimitiveList(attributeValue, out primitiveList))
                return primitiveList;
            DynamoDBBool ddbBool;
            if (TryToDynamoDBBool(attributeValue, out ddbBool))
                return ddbBool;
            DynamoDBList ddbList;
            if (TryToDynamoDBList(attributeValue, out ddbList))
                return ddbList;
            Document document;
            if (TryToDocument(attributeValue, out document))
                return document;
            DynamoDBNull ddbNull;
            if (TryToDynamoDBNull(attributeValue, out ddbNull))
                return ddbNull;
            return null;
        }
        private static bool TryToPrimitiveList(AttributeValue attributeValue, out PrimitiveList primitiveList)
        {
            primitiveList = null;
            Primitive primitive;
            if (attributeValue.IsSetSS())
            {
                primitiveList = new PrimitiveList(DynamoDBEntryType.String);
                foreach (string item in attributeValue.SS)
                {
                    primitive = new Primitive(item);
                    primitiveList.Add(primitive);
                }
            }
            else if (attributeValue.IsSetNS())
            {
                primitiveList = new PrimitiveList(DynamoDBEntryType.Numeric);
                foreach (string item in attributeValue.NS)
                {
                    primitive = new Primitive(item, true);
                    primitiveList.Add(primitive);
                }
            }
            else if (attributeValue.IsSetBS())
            {
                primitiveList = new PrimitiveList(DynamoDBEntryType.Binary);
                foreach (MemoryStream item in attributeValue.BS)
                {
                    primitive = new Primitive(item);
                    primitiveList.Add(primitive);
                }
            }
            return (primitiveList != null);
        }
        private static bool TryToPrimitive(AttributeValue attributeValue, out Primitive primitive)
        {
            primitive = null;
            if (attributeValue.IsSetS())
            {
                primitive = new Primitive(attributeValue.S);
            }
            else if (attributeValue.IsSetN())
            {
                primitive = new Primitive(attributeValue.N, true);
            }
            else if (attributeValue.IsSetB())
            {
                primitive = new Primitive(attributeValue.B);
            }
            return (primitive != null);
        }
        private static bool TryToDynamoDBBool(AttributeValue attributeValue, out DynamoDBBool ddbBool)
        {
            ddbBool = null;
            if (attributeValue.IsSetBOOL())
            {
                ddbBool = new DynamoDBBool(attributeValue.BOOL);
            }
            return (ddbBool != null);
        }
        private static bool TryToDynamoDBNull(AttributeValue attributeValue, out DynamoDBNull ddbNull)
        {
            ddbNull = null;
            if (attributeValue.IsSetNULL())
            {
                ddbNull = new DynamoDBNull();
            }
            return (ddbNull != null);
        }
        private static bool TryToDynamoDBList(AttributeValue attributeValue, out DynamoDBList list)
        {
            list = null;
            if (attributeValue.IsSetL())
            {
                var items = attributeValue.L;
                var entries = items.Select(AttributeValueToDynamoDBEntry).Where(item => item != null);
                list = new DynamoDBList(entries);
            }
            return (list != null);
        }
        private static bool TryToDocument(AttributeValue attributeValue, out Document document)
        {
            document = null;
            
            if (attributeValue.IsSetM())
            {
                document = new Document();
                var items = attributeValue.M;
                foreach(var kvp in items)
                {
                    var name = kvp.Key;
                    var value = kvp.Value;
                    var entry = AttributeValueToDynamoDBEntry(value);
                    document[name] = entry;
                }
            }
            return (document != null);
        }
        #endregion
        #region Static methods
        
        /// 
        /// Creates a Document from an attribute map.
        /// 
        /// Map of attribute names to attribute values.
        /// Document representing the data.
        public static Document FromAttributeMap(Dictionary data)
        {
            return FromAttributeMap(data, epochAttributes: null);
        }
        /// 
        /// Creates a document from a JSON string.
        /// The conversion is as follows:
        ///  Objects are converted to DynamoDB M types.
        ///  Arrays are converted to DynamoDB L types.
        ///  Boolean types are converted to DynamoDB BOOL types.
        ///  Null values are converted to DynamoDB NULL types.
        ///  Numerics are converted to DynamoDB N types.
        ///  Strings are converted to DynamoDB S types.
        /// 
        /// JSON string.
        /// Document representing the JSON data.
        public static Document FromJson(string json)
        {
            return JsonUtils.FromJson(json);
        }
        
        /// 
        /// Parses JSON text to produce an array of .
        /// 
        /// 
        /// An  of type .
        public static IEnumerable FromJsonArray(string jsonText)
        {
            return JsonUtils.FromJsonArray(jsonText);
        }
        #endregion
        #region IDictionary Members
        /// 
        /// Add value to Doucment.
        /// 
        /// 
        /// 
        public void Add(string key, DynamoDBEntry value)
        {
            currentValues.Add(key, value);
        }
        /// 
        /// Check to see if the value is set on the document.
        /// 
        /// 
        /// 
        public bool ContainsKey(string key)
        {
            return currentValues.ContainsKey(key);
        }
        /// 
        /// This list of attribute keys for the document.
        /// 
        public ICollection Keys
        {
            get { return currentValues.Keys; }
        }
        /// 
        /// Remove the attribute from the Document.
        /// 
        /// 
        /// 
        public bool Remove(string key)
        {
            return currentValues.Remove(key);
        }
        /// 
        /// Get a list of all the values in the Document.
        /// 
        public ICollection Values
        {
            get { return currentValues.Values; }
        }
        #endregion
        #region ICollection> Members
        /// 
        /// Add attributes to Document.
        /// 
        /// 
        public void Add(KeyValuePair item)
        {
            currentValues.Add(item.Key, item.Value);
        }
        /// 
        /// Clear attributes from document.
        /// 
        public void Clear()
        {
            currentValues.Clear();
        }
        /// 
        /// Check to see if the attributes are in the Document.
        /// 
        /// 
        /// 
        public bool Contains(KeyValuePair item)
        {
            //DynamoDBEntry value;
            //if (!this.TryGetValue(item.Key, out value))
            //    return false;
            //return value.Equals(item.Value);
            var icollection = (ICollection>)currentValues;
            return icollection.Contains(item);
        }
        /// 
        /// Copies the attributes to the array.
        /// 
        /// 
        /// 
        public void CopyTo(KeyValuePair[] array, int arrayIndex)
        {
            var icollection = (ICollection>)currentValues;
            icollection.CopyTo(array, arrayIndex);
        }
        /// 
        /// Gets the count of attributes.
        /// 
        public int Count
        {
            get { return currentValues.Count; }
        }
        /// 
        /// Returns true if the document is read only.
        /// 
        public bool IsReadOnly
        {
            get { return false; }
        }
        /// 
        /// Removes the attributes from the document.
        /// 
        /// 
        /// 
        public bool Remove(KeyValuePair item)
        {
            var icollection = (ICollection>)currentValues;
            return icollection.Remove(item);
        }
        #endregion
        #region IEnumerable> Members
        /// 
        /// Gets the enumerator for the attributes.
        /// 
        /// 
        public IEnumerator> GetEnumerator()
        {
            return currentValues.GetEnumerator();
        }
        #endregion
        #region IEnumerable Members
        /// 
        /// Gets the enumerator for the attributes.
        /// 
        /// 
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return currentValues.GetEnumerator();
        }
        #endregion
        #region DynamoDBEntry overrides
        internal override AttributeValue ConvertToAttributeValue(AttributeConversionConfig conversionConfig)
        {
            var map = new Dictionary(StringComparer.Ordinal);
            foreach(var item in currentValues)
            {
                var key = item.Key;
                var entry = item.Value;
                AttributeValue entryAttributeValue;
                using (conversionConfig.CRT.Track(entry))
                {
                    entryAttributeValue = entry.ConvertToAttributeValue(conversionConfig);
                }
                if (entryAttributeValue != null)
                {
                    map[key] = entryAttributeValue;
                }
            }
            var attributeValue = new AttributeValue();
            attributeValue.M = map;
            attributeValue.IsMSet = true;
            return attributeValue;
        }
        /// 
        /// Clones the Document
        /// 
        /// 
        public override object Clone()
        {
            var doc = new Document(this);
            return doc;
        }
        #endregion
        #region Overrides
        /// 
        /// Compare the document to see if it is equal.
        /// 
        /// 
        /// 
        public override bool Equals(object obj)
        {
            var otherDocument = obj as Document;
            if (otherDocument == null)
                return false;
            if (Keys.Count != otherDocument.Keys.Count)
                return false;
            foreach(var key in Keys)
            {
                if (!otherDocument.ContainsKey(key))
                    return false;
                var a = this[key];
                var b = otherDocument[key];
                if (!a.Equals(b))
                    return false;
            }
            return true;
        }
        /// 
        /// Implements the GetHashCode.
        /// 
        /// 
        public override int GetHashCode()
        {
            var hashCode = 0;
            foreach(var kvp in this)
            {
                string key = kvp.Key;
                DynamoDBEntry entry = kvp.Value;
                hashCode = Hashing.CombineHashes(hashCode, key.GetHashCode(), entry.GetHashCode());
            }
            return hashCode;
        }
        #endregion
    }
}