/*******************************************************************************
 *  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.
 * *****************************************************************************
 *    __  _    _  ___
 *   (  )( \/\/ )/ __)
 *   /__\ \    / \__ \
 *  (_)(_) \/\/  (___/
 *
 *  AWS SDK for .NET
 *
 */

using System;
using System.Collections.Generic;
using System.Net;
using System.Xml;
using System.Xml.Serialization;

namespace Amazon.S3.Util
{
    /// <summary>
    /// An exception detailing a failed HTTP POST upload atempt to Amazon S3.
    /// </summary>
    [Serializable]
    public class S3PostUploadException : Exception
    {
        /// <summary>
        /// Initializes a new instance of S3PostUploadException with a specified error message
        /// </summary>
        /// <param name="message">The error message</param>
        public S3PostUploadException(string message) : base(message) {}
        
        /// <summary>
        /// Initializes a new instance of S3PostUploadException with a specified error code and error message
        /// </summary>
        /// <param name="errorCode">The error code</param>
        /// <param name="message">The error message</param>
        public S3PostUploadException(string errorCode, string message) : base(message)
        {
            this.ErrorCode = errorCode;
        }

        /// <summary>
        /// The error code returned by S3
        /// </summary>
        public string ErrorCode { get; set; }

        /// <summary>
        /// The S3 request id
        /// </summary>
        public string RequestId { get; set; }

        /// <summary>
        /// The S3 host id
        /// </summary>
        public string HostId { get; set; }

        /// <summary>
        /// The HTTP error status code returned by S3
        /// </summary>
        public HttpStatusCode StatusCode { get; set; }

        /// <summary>
        /// Additional information about the error
        /// </summary>
        /// <remarks>
        /// Some errors are accompanied by more specific information, which vary from error to error
        /// </remarks>
        public IDictionary<string, string> ExtraFields { get; set; }

        /// <summary>
        /// Parse an S3 Error response and create an S3PostUploadException
        /// </summary>
        /// <param name="response">The response from S3</param>
        /// <returns>An S3PostUploadException with the information from the response</returns>
        public static S3PostUploadException FromResponse(HttpWebResponse response)
        {
            var serializer = new XmlSerializer(typeof(S3PostUploadError));

            S3PostUploadError err = null;

            try
            {
                err = serializer.Deserialize(response.GetResponseStream()) as S3PostUploadError;
            }
            catch
            {
                return new S3PostUploadException("Unknown", "Unknown error");
            }

            var ex = new S3PostUploadException(err.ErrorCode, err.ErrorMessage)
            {
                RequestId = err.RequestId,
                HostId = err.HostId,
            };

            ex.StatusCode = response.StatusCode;
            ex.ExtraFields = new Dictionary<string, string>();
            if (err.elements != null)
            {
                foreach (var f in err.elements)
                {
                    ex.ExtraFields.Add(f.LocalName, f.InnerText);
                }
            }

            return ex;
        }

        /// <summary>
        /// Constructs a new instance of the S3PostUploadException class with serialized data.
        /// </summary>
        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
        /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="info" /> parameter is null. </exception>
        /// <exception cref="T:System.Runtime.Serialization.SerializationException">The class name is null or <see cref="P:System.Exception.HResult" /> is zero (0). </exception>
        protected S3PostUploadException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
            : base(info, context)
        {
            if (info != null)
            {
                this.ErrorCode = info.GetString("ErrorCode");
                this.RequestId = info.GetString("RequestId");
                this.HostId = info.GetString("HostId");
                this.StatusCode = (HttpStatusCode)info.GetValue("StatusCode", typeof(HttpStatusCode));
                this.ExtraFields = (IDictionary<string, string>)info.GetValue("ExtraFields", typeof(IDictionary<string, string>));
            }
        }

        /// <summary>
        /// Sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
        /// </summary>
        /// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
        /// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
        /// <exception cref="T:System.ArgumentNullException">The <paramref name="info" /> parameter is a null reference (Nothing in Visual Basic). </exception>
#if BCL35
        [System.Security.Permissions.SecurityPermission(
            System.Security.Permissions.SecurityAction.LinkDemand,
            Flags = System.Security.Permissions.SecurityPermissionFlag.SerializationFormatter)]
#endif
        [System.Security.SecurityCritical]
        //// These FxCop rules are giving false-positives for this method
        //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2123:OverrideLinkDemandsShouldBeIdenticalToBase")]
        //[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2134:MethodsMustOverrideWithConsistentTransparencyFxCopRule")]
        public override void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            base.GetObjectData(info, context);

            if (info != null)
            {
                info.AddValue("ErrorCode", this.ErrorCode);
                info.AddValue("RequestId", this.RequestId);
                info.AddValue("HostId", this.HostId);
                info.AddValue("StatusCode", this.StatusCode);
                info.AddValue("ExtraFields", this.ExtraFields);
            }
        }
    }

    /// <summary>
    /// Class for unmarshalling response XML
    /// </summary>
    [XmlRoot("Error")]
    public class S3PostUploadError
    {
        /// <summary>
        /// Gets and sets the ErrorCode property.
        /// </summary>
        [XmlElement("Code")]
        public string ErrorCode { get; set; }

        /// <summary>
        /// Gets and sets the ErrorMessage property.
        /// </summary>
        [XmlElement("Message")]
        public string ErrorMessage { get; set; }

        /// <summary>
        /// Gets and sets the RequestId property.
        /// </summary>
        [XmlElement("RequestId")]
        public string RequestId { get; set; }

        /// <summary>
        /// Gets and sets the HostId property.
        /// </summary>
        [XmlElement("HostId")]
        public string HostId { get; set; }

        /// <summary>
        /// Gets and sets the elements property.
        /// </summary>
        [XmlAnyElement()]
        public XmlElement[] elements { get; set; }
    }
}