using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml;
using System.Text;
using System.Threading.Tasks;

using ServiceClientGenerator;

namespace AWSSDK_DotNet35.UnitTests.TestTools
{
    public class XmlSampleGenerator
    {
        readonly ServiceModel _model;
        readonly Operation _operation;

        public XmlSampleGenerator(ServiceModel model, Operation operation)
        {
            this._model = model;
            this._operation = operation;
        }

        public string Execute()
        {
            var content = new StringWriter();
            var settings = new XmlWriterSettings() { ConformanceLevel = ConformanceLevel.Auto };
            using (var writer = XmlTextWriter.Create(content, settings))
            {
                if (_model.Type == ServiceType.Rest_Xml &&
                    _operation.ResponseStructure.PayloadMemberName == null)
                {
                    writer.WriteStartElement(this._operation.Name + "Response");
                }

                if (_model.Type == ServiceType.Query)
                {
                    writer.WriteStartElement(this._operation.Name + "Response");
                }

                if (this._operation.ResponseStructure != null)
                {
                    if (!string.IsNullOrEmpty(this._operation.ResultWrapper))
                    {
                        writer.WriteStartElement(this._operation.ResultWrapper);
                    }

                    //if (this._operation.IsResponsedWrapped)
                    //    writer.WriteStartElement(this._operation.ResponseStructure.MarshallName);

                    WriteStructure(writer, this._operation.ResponseStructure);

                    //if (this._operation.IsResponsedWrapped)
                    //    writer.WriteEndElement();

                    if (!string.IsNullOrEmpty(this._operation.ResultWrapper))
                    {
                        writer.WriteEndElement();
                    }
                }

                if (_model.Type == ServiceType.Rest_Xml && 
                    _operation.ResponseStructure.PayloadMemberName == null)
                {
                    writer.WriteEndElement();
                }

                if (_model.Type == ServiceType.Query)
                {
                    if (!_model.IsEC2Protocol)
                        writer.WriteStartElement("ResponseMetadata");
                    writer.WriteElementString("RequestId", "6fbe234c-80f9-11e2-a17f-9148EXAMPLE");
                    if (!_model.IsEC2Protocol)
                        writer.WriteEndElement();
                }
            }

            var xml = content.ToString();
            return xml;
        }

        public string Execute(ExceptionShape exceptionShape)
        {
            var content = new StringWriter();
            using (var writer = XmlWriter.Create(content))
            {
                writer.WriteStartElement("ErrorResponse");
                writer.WriteStartElement("Error");

                writer.WriteElementString("Code", exceptionShape.Code);
                writer.WriteElementString("Message", $"{exceptionShape.Name}_Message");
                
                WriteStructure(writer, exceptionShape);

                writer.WriteEndElement();

                writer.WriteElementString("RequestId", "6fbe234c-80f9-11e2-a17f-9148EXAMPLE");

                writer.WriteEndElement();
            }

            var xml = content.ToString();
            return xml;
        }

        private void WriteStructure(XmlWriter writer, Shape structure)
        {
            // Handle cases where a payload is not a structure, rather something like a blob
            if (structure.PayloadMemberName != null && !structure.PayloadMember.Shape.IsStructure)
            {
                this.Write(writer, structure.PayloadMember, structure.PayloadMember.Shape);
                return;
            }

            foreach (var member in structure.Members)
            {
                if (member.IsInHeader)
                    continue;

                if (member.Shape.IsMap)
                {
                    WriteMap(writer, member, member.Shape);  // map not used by EC2 protocol
                }
                else if (member.Shape.IsList)
                {
                    WriteArray(writer, member, member.Shape);
                }
                else
                {
                    // to allow for EC2's substitution of AttributeValue et al structure types with simple values,
                    // eg request.Description => Description.Value after marshall, we potentially need to create 
                    // sub elements for each '.' subexpression and wind back after writing the actual value
                    var unmarshallName = GeneratorHelpers.DetermineAWSQueryTestExpression(member);
                    var nameElements = unmarshallName.Split('/');
                    foreach (var el in nameElements)
                    {
                        writer.WriteStartElement(el);
                    }

                    this.Write(writer, member, member.Shape);

                    for (var i = 0; i < nameElements.Length; i++)
                    {
                        writer.WriteEndElement();
                    }
                }
            }
        }

        private void Write(XmlWriter writer, Member member, Shape shape)
        {
            if (shape.IsStructure)
                WriteStructure(writer, shape);
            else if (shape.IsEnum)
            {
                var enumerationWrapper = this._model.Enumerations(true).First(x => x.Name == shape.Name);
                writer.WriteValue(enumerationWrapper.EnumerationValues.ElementAt(0).MarshallName);
            }
            else if (shape.IsString)
                writer.WriteValue(shape.Name + "_Value");
            else if (shape.IsInt)
                writer.WriteValue(int.MaxValue);
            else if (shape.IsLong)
                writer.WriteValue(long.MaxValue);
            else if (shape.IsFloat)
                writer.WriteValue(float.MaxValue);
            else if (shape.IsDouble)
                writer.WriteValue(double.MaxValue);
            else if (shape.IsDateTime)
                writer.WriteValue(ValidatorUtils.GetTestDate(member, shape));
            else if (shape.IsBoolean)
                writer.WriteValue(true);
            else if (shape.IsMemoryStream)
                writer.WriteValue(Constants.DEFAULT_BLOB_ENCODED);
            else
                throw new Exception("Unknown Type for shape " + shape.Name);

        }

        private void WriteArray(XmlWriter writer, Member member, Shape shape)
        {
            if (!shape.IsFlattened)
            {
                writer.WriteStartElement(GeneratorHelpers.DetermineAWSQueryBaseUnmarshallName(member));
            }

            for (int i = 0; i < shape.Name.Length % 5 + 2; i++)
            {
                writer.WriteStartElement(shape.ListMarshallName ?? "member");
                Write(writer, member, shape.ModelListShape);
                writer.WriteEndElement();
            }

            if (!shape.IsFlattened)
                writer.WriteEndElement();
        }

        private void WriteMap(XmlWriter writer, Member member, Shape shape)
        {
            var memberMarshallName = member.MarshallName;
            if (!shape.IsFlattened)
                writer.WriteStartElement(memberMarshallName);

            for (int i = 0; i < shape.Name.Length % 5 + 2; i++)
            {
                if (!shape.IsFlattened)
                    writer.WriteStartElement("entry");
                else
                    writer.WriteStartElement(memberMarshallName);

                writer.WriteStartElement(shape.KeyMarshallName);
                writer.WriteValue("Key" + i);
                writer.WriteEndElement();

                writer.WriteStartElement(shape.ValueMarshallName);
                Write(writer, member, shape.ValueShape);
                writer.WriteEndElement();

                writer.WriteEndElement();
            }

            if (!shape.IsFlattened)
                writer.WriteEndElement();
        }
    }
}