#region Copyright notice and license
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using Grpc.Core.Utils;
namespace Grpc.Core
{
    /// 
    /// A collection of metadata entries that can be exchanged during a call.
    /// gRPC supports these types of metadata:
    /// 
    /// - Request headersare sent by the client at the beginning of a remote call before any request messages are sent.
 
    /// - Response headersare sent by the server at the beginning of a remote call handler before any response messages are sent.
 
    /// - Response trailersare sent by the server at the end of a remote call along with resulting call status.
 
    /// 
    /// 
    public sealed class Metadata : IList
    {
        /// 
        /// All binary headers should have this suffix.
        /// 
        public const string BinaryHeaderSuffix = "-bin";
        /// 
        /// An read-only instance of metadata containing no entries.
        /// 
        public static readonly Metadata Empty = new Metadata().Freeze();
        readonly List entries;
        bool readOnly;
        /// 
        /// Initializes a new instance of Metadata.
        /// 
        public Metadata()
        {
            this.entries = new List();
        }
        /// 
        /// Makes this object read-only.
        /// 
        /// this object
        internal Metadata Freeze()
        {
            this.readOnly = true;
            return this;
        }
        // TODO: add support for access by key
        #region IList members
        public int IndexOf(Metadata.Entry item)
        {
            return entries.IndexOf(item);
        }
        public void Insert(int index, Metadata.Entry item)
        {
            CheckWriteable();
            entries.Insert(index, item);
        }
        public void RemoveAt(int index)
        {
            CheckWriteable();
            entries.RemoveAt(index);
        }
        public Metadata.Entry this[int index]
        {
            get
            {
                return entries[index];
            }
            set
            {
                CheckWriteable();
                entries[index] = value;
            }
        }
        public void Add(Metadata.Entry item)
        {
            CheckWriteable();
            entries.Add(item);
        }
        public void Add(string key, string value)
        {
            Add(new Entry(key, value));
        }
        public void Add(string key, byte[] valueBytes)
        {
            Add(new Entry(key, valueBytes));
        }
        public void Clear()
        {
            CheckWriteable();
            entries.Clear();
        }
        public bool Contains(Metadata.Entry item)
        {
            return entries.Contains(item);
        }
        public void CopyTo(Metadata.Entry[] array, int arrayIndex)
        {
            entries.CopyTo(array, arrayIndex);
        }
        public int Count
        {
            get { return entries.Count; }
        }
        public bool IsReadOnly
        {
            get { return readOnly; }
        }
        public bool Remove(Metadata.Entry item)
        {
            CheckWriteable();
            return entries.Remove(item);
        }
        public IEnumerator GetEnumerator()
        {
            return entries.GetEnumerator();
        }
        IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return entries.GetEnumerator();
        }
        private void CheckWriteable()
        {
            Preconditions.CheckState(!readOnly, "Object is read only");
        }
        #endregion
        /// 
        /// Metadata entry
        /// 
        public struct Entry
        {
            private static readonly Encoding Encoding = Encoding.ASCII;
            private static readonly Regex ValidKeyRegex = new Regex("^[a-z0-9_-]+$");
            readonly string key;
            readonly string value;
            readonly byte[] valueBytes;
            private Entry(string key, string value, byte[] valueBytes)
            {
                this.key = key;
                this.value = value;
                this.valueBytes = valueBytes;
            }
            /// 
            /// Initializes a new instance of the  struct with a binary value.
            /// 
            /// Metadata key, needs to have suffix indicating a binary valued metadata entry.
            /// Value bytes.
            public Entry(string key, byte[] valueBytes)
            {
                this.key = NormalizeKey(key);
                Preconditions.CheckArgument(this.key.EndsWith(BinaryHeaderSuffix),
                    "Key for binary valued metadata entry needs to have suffix indicating binary value.");
                this.value = null;
                Preconditions.CheckNotNull(valueBytes, "valueBytes");
                this.valueBytes = new byte[valueBytes.Length];
                Buffer.BlockCopy(valueBytes, 0, this.valueBytes, 0, valueBytes.Length);  // defensive copy to guarantee immutability
            }
            /// 
            /// Initializes a new instance of the  struct holding an ASCII value.
            /// 
            /// Metadata key, must not use suffix indicating a binary valued metadata entry.
            /// Value string. Only ASCII characters are allowed.
            public Entry(string key, string value)
            {
                this.key = NormalizeKey(key);
                Preconditions.CheckArgument(!this.key.EndsWith(BinaryHeaderSuffix),
                    "Key for ASCII valued metadata entry cannot have suffix indicating binary value.");
                this.value = Preconditions.CheckNotNull(value, "value");
                this.valueBytes = null;
            }
            /// 
            /// Gets the metadata entry key.
            /// 
            public string Key
            {
                get
                {
                    return this.key;
                }
            }
            /// 
            /// Gets the binary value of this metadata entry.
            /// 
            public byte[] ValueBytes
            {
                get
                {
                    if (valueBytes == null)
                    {
                        return Encoding.GetBytes(value);
                    }
                    // defensive copy to guarantee immutability
                    var bytes = new byte[valueBytes.Length];
                    Buffer.BlockCopy(valueBytes, 0, bytes, 0, valueBytes.Length);
                    return bytes;
                }
            }
            /// 
            /// Gets the string value of this metadata entry.
            /// 
            public string Value
            {
                get
                {
                    Preconditions.CheckState(!IsBinary, "Cannot access string value of a binary metadata entry");
                    return value ?? Encoding.GetString(valueBytes);
                }
            }
            /// 
            /// Returns true if this entry is a binary-value entry.
            /// 
            public bool IsBinary
            {
                get
                {
                    return value == null;
                }
            }
            /// 
            /// Returns a  that represents the current .
            /// 
            public override string ToString()
            {
                if (IsBinary)
                {
                    return string.Format("[Entry: key={0}, valueBytes={1}]", key, valueBytes);
                }
                
                return string.Format("[Entry: key={0}, value={1}]", key, value);
            }
            /// 
            /// Gets the serialized value for this entry. For binary metadata entries, this leaks
            /// the internal valueBytes byte array and caller must not change contents of it.
            /// 
            internal byte[] GetSerializedValueUnsafe()
            {
                return valueBytes ?? Encoding.GetBytes(value);
            }
            /// 
            /// Creates a binary value or ascii value metadata entry from data received from the native layer.
            /// We trust C core to give us well-formed data, so we don't perform any checks or defensive copying.
            /// 
            internal static Entry CreateUnsafe(string key, byte[] valueBytes)
            {
                if (key.EndsWith(BinaryHeaderSuffix))
                {
                    return new Entry(key, null, valueBytes);
                }
                return new Entry(key, Encoding.GetString(valueBytes), null);
            }
            private static string NormalizeKey(string key)
            {
                var normalized = Preconditions.CheckNotNull(key, "key").ToLower(CultureInfo.InvariantCulture);
                Preconditions.CheckArgument(ValidKeyRegex.IsMatch(normalized), 
                    "Metadata entry key not valid. Keys can only contain lowercase alphanumeric characters, underscores and hyphens.");
                return normalized;
            }
        }
    }
}