﻿import { ArgumentNullException } from "../../Exceptions/ArgumentException";
import { DateTimePrecision } from "../../Enumerations/DateTimePrecision";
import { EwsLogging } from "../EwsLogging";
import { EwsServiceXmlReader } from "../EwsServiceXmlReader";
import { EwsServiceXmlWriter } from "../EwsServiceXmlWriter";
import { EwsUtilities } from "../EwsUtilities";
import { ExchangeServerInfo } from "../ExchangeServerInfo";
import { ExchangeService } from "../ExchangeService";
import { ExchangeVersion } from "../../Enumerations/ExchangeVersion";
import { IXHROptions } from "../../Interfaces";
import { RenderingMode } from "../../Enumerations/RenderingMode";
import { ServiceResponse } from "../Responses/ServiceResponse";
import { ServiceVersionException } from "../../Exceptions/ServiceVersionException";
import { SoapFaultDetails } from "../../Misc/SoapFaultDetails";
import { StringHelper } from "../../ExtensionMethods";
import { Strings } from "../../Strings";
import { XmlAttributeNames } from "../XmlAttributeNames";
import { XmlElementNames } from "../XmlElementNames";
import { XmlNamespace } from "../../Enumerations/XmlNamespace";
import { HttpStatusCode } from "../../../lib/HttpStatusCode";

/**
 * @internal Represents an abstract service request.
 */
export abstract class ServiceRequestBase {

    //#region private static and const
    //ref:this may be from newer code overall i am using, update when updating from ews base code library //todo:

    /**
     * The two contants below are used to set the AnchorMailbox and ExplicitLogonUser values in the request header.
     * 
     * /remarks/    Note: Setting this values will route the request directly to the backend hosting the AnchorMailbox. These headers should be used primarily for UnifiedGroup scenario where a request needs to be routed directly to the group mailbox versus the user mailbox.
     */
    protected static AnchorMailboxHeaderName: string = "X-AnchorMailbox";
    protected static ExplicitLogonUserHeaderName: string = "X-OWA-ExplicitLogonUser";

    protected static RequestIdResponseHeaders: string[] = ["RequestId", "request-id"];
    protected static XMLSchemaNamespace: string = "http://www.w3.org/2001/XMLSchema";
    protected static XMLSchemaInstanceNamespace: string = "http://www.w3.org/2001/XMLSchema-instance";
    protected static ClientStatisticsRequestHeader: string = "X-ClientStatistics";

    /**
     * Maintains the collection of client side statistics for requests already completed
     */
    protected static clientStatisticsCache: string[] = [];

    private service: ExchangeService;

    /**
     * @internal Gets the service.
     * 
     * @value   The service.
     */
    get Service(): ExchangeService {
        return this.service;
    }
    //#endregion

    /**
     * @internal Gets or sets the anchor mailbox associated with the request
     *
     * /remarks/    Setting this value will add special headers to the request which in turn will route the request directly to the mailbox server against which the request is to be executed.
     */
    AnchorMailbox: string = null;

    /** ews-javascript-api specific */
    private SoapFaultDetails: SoapFaultDetails;


    /**
     *  @internal Initializes a new instance of the **ServiceRequestBase** class.
     *
     * @param   {ExchangeService}   service   The service.
     */
    constructor(service: ExchangeService) {
        if (service == null) {
            throw new ArgumentNullException("service");
        }
        this.service = service;
        this.ThrowIfNotSupportedByRequestedServerVersion();
    }

    // #region abstract Methods for subclasses to override

    /**
     *  @internal Gets a value indicating whether the TimeZoneContext SOAP header should be eimitted.
     * 
     * @value   true if the time zone should be emitted; otherwise, false.
     */
    get EmitTimeZoneHeader(): boolean {
        return false;
    }

    /**
     * @internal Gets the name of the XML element.
     *
     * @return  {string}      XML element name,
     */
    GetXmlElementName(): string { throw new Error("abstract method, must override"); }

    /**
     * @internal Gets the minimum server version required to process this request.
     *
     * @return  {ExchangeVersion}      Exchange server version.
     */
    GetMinimumRequiredServerVersion(): ExchangeVersion { throw new Error("abstract method, must override"); }

    /**
     * @internal Gets the name of the response XML element.
     *
     * @return  {string}      XML element name,
     */
    GetResponseXmlElementName(): string { throw new Error("abstract method, must override"); }

    /**
     * @internal Parses the response.
     *
     * @param   {any}   jsonBody   The js object response body.
     * @return  {any}              Response object.
     */
    ParseResponse(jsonBody: any): any {
        var serviceResponse: ServiceResponse = new ServiceResponse();
        serviceResponse.LoadFromXmlJsObject(jsonBody, this.Service);
        return serviceResponse;
    }

    /**
     * @internal Writes XML elements.
     *
     * @param   {EwsServiceXmlWriter}   writer   The writer.
     */
    WriteElementsToXml(writer: EwsServiceXmlWriter): void { throw new Error("abstract method, must override"); }

    //#endregion

    /**
     * @internal Allows the subclasses to add their own header information
     *
     * @param   {any}   webHeaderCollection   The HTTP request headers
     */
    AddHeaders(webHeaderCollection: any /*WebHeaderCollection*/): void {
        if (!StringHelper.IsNullOrEmpty(this.AnchorMailbox)) {
            webHeaderCollection.Set(ServiceRequestBase.AnchorMailboxHeaderName, this.AnchorMailbox);
            webHeaderCollection.Set(ServiceRequestBase.ExplicitLogonUserHeaderName, this.AnchorMailbox);
        }
    }

    //BuildEwsHttpWebRequest(): IEwsHttpWebRequest { throw new Error("Could not implemented."); }
    BuildXHR(): IXHROptions {

        var request = this.Service.PrepareHttpWebRequest(this.GetXmlElementName());
        //try
        //{
        //    IEwsHttpWebRequest request = this.Service.PrepareHttpWebRequest(this.GetXmlElementName());

        //    this.Service.TraceHttpRequestHeaders(TraceFlags.EwsRequestHttpHeaders, request);

        //    bool needSignature = this.Service.Credentials != null && this.Service.Credentials.NeedSignature;
        //    bool needTrace = this.Service.IsTraceEnabledFor(TraceFlags.EwsRequest);

        //    // If tracing is enabled, we generate the request in-memory so that we
        //    // can pass it along to the ITraceListener. Then we copy the stream to
        //    // the request stream.
        //    if (needSignature || needTrace) {
        //        this.TraceAndEmitRequest(request, needSignature, needTrace);
        //    }
        //    else {
        this.EmitRequest(request);
        //    }

        return request;
        //}
        //catch (WebException ex)
        //{
        //    if (ex.Status == WebExceptionStatus.ProtocolError && ex.Response != null) {
        //        this.ProcessWebException(ex);
        //    }

        //    // Wrap exception if the above code block didn't throw
        //    throw new ServiceRequestException(string.Format(Strings.ServiceRequestFailed, ex.Message), ex);
        //}
        //catch (IOException e)
        //{
        //    // Wrap exception.
        //    throw new ServiceRequestException(string.Format(Strings.ServiceRequestFailed, e.Message), e);
        //}
    }
    BuildResponseObjectFromJson(jsObject: any): any {
        if (jsObject[XmlElementNames.SOAPHeaderElementName]) {
            this.ReadSoapHeader(jsObject[XmlElementNames.SOAPHeaderElementName]);
        }

        return this.ParseResponse(jsObject[XmlElementNames.SOAPBodyElementName]);
    }
    //CreateJsonHeaders(): JsonObject { throw new Error("Could not implemented."); }
    //CreateJsonRequest(): JsonObject { throw new Error("Could not implemented."); }

    /**
     * Emits the request.
     *
     * @param   {IXHROptions}   request   The request.
     */
    private EmitRequest(request: IXHROptions): void {
        if (this.Service.RenderingMethod === RenderingMode.Xml) {

            var writer: EwsServiceXmlWriter = new EwsServiceXmlWriter(this.service);//writer.Service
            this.WriteToXml(writer);
            request.data = writer.GetXML();

        }
        else if (this.Service.RenderingMethod === RenderingMode.JSON) {
            //JsonObject requestObject = this.CreateJsonRequest();

            //using(Stream serviceRequestStream = this.GetWebRequestStream(request))
            //{
            //    requestObject.SerializeToJson(serviceRequestStream);
            //}
            //debugger;
        }
    }
    //EndGetEwsHttpWebResponse(request: IEwsHttpWebRequest, asyncResult: any /*System.IAsyncResult*/): IEwsHttpWebResponse { throw new Error("Could not implemented."); }
    GetEwsHttpWebResponse(request: IXHROptions /*IEwsHttpWebRequest*/): Promise<XMLHttpRequest> { return this.service.XHRApi.xhr(request); }

    /**
     * Gets string representation of requested server version.
     *
     * /remarks/    In order to support E12 RTM servers, ExchangeService has another flag indicating that we should use "Exchange2007" as the server version string rather than Exchange2007_SP1.
     * @return  {string}      String representation of requested server version.
     */
    private GetRequestedServiceVersionString(): string {
        if (this.Service.Exchange2007CompatibilityMode && this.Service.RequestedServerVersion == ExchangeVersion.Exchange2007_SP1) {
            return "Exchange2007";
        }
        else {
            return ExchangeVersion[this.Service.RequestedServerVersion];
        }
    }
    //GetResponseStream(response: IEwsHttpWebResponse): any /*System.IO.Stream*/ { throw new Error("Could not implemented."); }
    //GetResponseStream(response: IEwsHttpWebResponse, readTimeout: number):any /*System.IO.Stream*/{ throw new Error("ServiceRequestBase.ts - GetResponseStream : Not implemented.");}
    //GetWebRequestStream(request: IEwsHttpWebRequest): any /*System.IO.Stream*/ { throw new Error("Could not implemented."); }

    /**
     * Processes the web exception.
     *
     * @param   {XMLHttpRequest}   webException   The web response XHR object.
     * @return  {SoapFaultDetails}      Soap fault details if any.
     */
    protected ProcessWebException(webException: XMLHttpRequest): SoapFaultDetails {
        if (webException) {
            //IEwsHttpWebResponse httpWebResponse = this.Service.HttpWebRequestFactory.CreateExceptionResponse(webException);
            var soapFaultDetails: SoapFaultDetails = null;

            if (webException.status == HttpStatusCode.InternalServerError) {
                //this.Service.ProcessHttpResponseHeaders(TraceFlags.EwsResponseHttpHeaders, httpWebResponse);

                // If tracing is enabled, we read the entire response into a MemoryStream so that we
                // can pass it along to the ITraceListener. Then we parse the response from the
                // MemoryStream.
                //if (this.Service.IsTraceEnabledFor(TraceFlags.EwsResponse)) {
                //    using(MemoryStream memoryStream = new MemoryStream())
                //    {
                //        using(Stream serviceResponseStream = ServiceRequestBase.GetResponseStream(httpWebResponse))
                //        {
                //            // Copy response to in-memory stream and reset position to start.
                //            EwsUtilities.CopyStream(serviceResponseStream, memoryStream);
                //            memoryStream.Position = 0;
                //        }

                //        if (this.Service.RenderingMethod == ExchangeService.RenderingMode.Xml) {
                //            this.TraceResponseXml(httpWebResponse, memoryStream);

                //            EwsServiceXmlReader reader = new EwsServiceXmlReader(memoryStream, this.Service);
                //            soapFaultDetails = this.ReadSoapFault(reader);
                //        }
                //        else if (this.Service.RenderingMethod == ExchangeService.RenderingMode.JSON) {
                //            this.TraceResponseJson(httpWebResponse, memoryStream);

                //            try {
                //                JsonObject jsonSoapFault = new JsonParser(memoryStream).Parse();
                //                soapFaultDetails = this.ReadSoapFault(jsonSoapFault);
                //            }
                //            catch (ServiceJsonDeserializationException) {
                //                // If no valid JSON response was returned, just return null SoapFault details
                //            }
                //        }
                //        else {
                //            throw new InvalidOperationException();
                //        }
                //    }
                //}
                //else {
                //    using(Stream stream = ServiceRequestBase.GetResponseStream(httpWebResponse))
                //    {
                //        if (this.Service.RenderingMethod == ExchangeService.RenderingMode.Xml) {
                //            EwsServiceXmlReader reader = new EwsServiceXmlReader(stream, this.Service);
                //            soapFaultDetails = this.ReadSoapFault(reader);
                //        }
                //        else if (this.Service.RenderingMethod == ExchangeService.RenderingMode.JSON) {
                //            try {
                //                JsonObject jsonSoapFault = new JsonParser(stream).Parse();
                //                soapFaultDetails = this.ReadSoapFault(jsonSoapFault);
                //            }
                //            catch (ServiceJsonDeserializationException) {
                //                // If no valid JSON response was returned, just return null SoapFault details
                //            }
                //        }
                //        else {
                //            throw new InvalidOperationException();
                //        }
                //    }
                //}
                //todo: fix tracing and other operations here
                var reader = new EwsServiceXmlReader(webException.responseText, this.Service);
                soapFaultDetails = this.ReadSoapFault(reader.JsObject);

                if (soapFaultDetails != null) {
                    //todo: implement soap fault error throw
                    this.SoapFaultDetails = soapFaultDetails;
                    soapFaultDetails.HttpStatusCode = webException.status;

                    //    switch (soapFaultDetails.ResponseCode) {
                    //        case ServiceError.ErrorInvalidServerVersion:
                    //            throw new Exceptions.ServiceVersionException(Strings.ServerVersionNotSupported);

                    //        case ServiceError.ErrorSchemaValidation:
                    //            // If we're talking to an E12 server (8.00.xxxx.xxx), a schema validation error is the same as a version mismatch error.
                    //            // (Which only will happen if we send a request that's not valid for E12).
                    //            if ((this.Service.ServerInfo != null) &&
                    //                (this.Service.ServerInfo.MajorVersion == 8) && (this.Service.ServerInfo.MinorVersion == 0)) {
                    //                throw new Exceptions.ServiceVersionException(Strings.ServerVersionNotSupported);
                    //            }

                    //            break;

                    //        case ServiceError.ErrorIncorrectSchemaVersion:
                    //            // This shouldn't happen. It indicates that a request wasn't valid for the version that was specified.
                    //            EwsUtilities.Assert(
                    //                false,
                    //                "ServiceRequestBase.ProcessWebException",
                    //                "Exchange server supports requested version but request was invalid for that version");
                    //            break;

                    //        case ServiceError.ErrorServerBusy:
                    //            throw new ServerBusyException(new ServiceResponse(soapFaultDetails));

                    //        default:
                    //            // Other error codes will be reported as remote error
                    //            break;
                    //    }

                    //    // General fall-through case: throw a ServiceResponseException
                    //    throw new ServiceResponseException(new ServiceResponse(soapFaultDetails));
                }
            }
            else {
                soapFaultDetails = new SoapFaultDetails(webException && (<any>webException).message ? (<any>webException).message : '');
                soapFaultDetails.HttpStatusCode = webException.status;
                if ((<any>webException).stack)
                    soapFaultDetails.stack = (<any>webException).stack;

                this.Service.ProcessHttpErrorResponse(webException, soapFaultDetails);
            }

            return soapFaultDetails;
        }
        return null;
    }

    /**
     * Reads the response from converted XML JS Object Soap enevlop is is omited, object has Body and Header as direct member.
     *
     * @param   {any}   jsObject    The converted XMl JS Object.
     * @return  {any}               Service response.
     */
    protected ReadResponseXmlJsObject(jsObject: any): any {
        if (jsObject[XmlElementNames.SOAPHeaderElementName]) {
            this.ReadSoapHeader(jsObject[XmlElementNames.SOAPHeaderElementName]);
        }

        if (!jsObject[XmlElementNames.SOAPBodyElementName]) {
            throw new Error("invalid soap message");
        }
        var serviceResponse: any;
        let jsBody = jsObject[XmlElementNames.SOAPBodyElementName]
        let jsResponse = jsBody[this.GetResponseXmlElementName()];
        serviceResponse = this.ParseResponse(jsResponse);
        return serviceResponse;
    }

    /**
     * Reads the SOAP fault.
     *
     * @param   {any}   jsonSoapFault   The SOAP fault.
     * @return  {SoapFaultDetails}      Parsed SoapFaultDetails
     */
    private ReadSoapFault(jsSoapFault: any): SoapFaultDetails {
        let soapFaultDetails: SoapFaultDetails = null;

        if (jsSoapFault[XmlElementNames.SOAPHeaderElementName]) {
            this.ReadSoapHeader(jsSoapFault[XmlElementNames.SOAPHeaderElementName]);
        }

        if (jsSoapFault[XmlElementNames.SOAPBodyElementName]) {
            let jsSoapBody = jsSoapFault[XmlElementNames.SOAPBodyElementName];
            if (jsSoapBody[XmlElementNames.SOAPFaultElementName]) {
                soapFaultDetails = SoapFaultDetails.Parse(jsSoapBody[XmlElementNames.SOAPFaultElementName]);
            }
        }

        return soapFaultDetails;
    }

    /**
     * Read SOAP header and extract server version
     *
     * @param   {any}   jsObject   Header value in JsObject
     */
    private ReadSoapHeader(jsObject: any): void {
        if (jsObject[XmlElementNames.ServerVersionInfo]) {
            this.Service.ServerInfo = ExchangeServerInfo.Parse(jsObject[XmlElementNames.ServerVersionInfo]);
        }
    }

    /**
     * @internal Throw exception if request is not supported in requested server version.
     * 
     * @exception   {ServiceVersionException}   Raised if request requires a later version of Exchange.
     */
    ThrowIfNotSupportedByRequestedServerVersion(): void {

        if (this.Service.RequestedServerVersion < this.GetMinimumRequiredServerVersion()) {
            throw new ServiceVersionException(
                StringHelper.Format(
                    Strings.RequestIncompatibleWithRequestVersion,
                    this.GetXmlElementName(),
                    ExchangeVersion[this.GetMinimumRequiredServerVersion()]), null);
        }
    }

    //TraceAndEmitRequest(request: IEwsHttpWebRequest, needSignature: boolean, needTrace: boolean): any { throw new Error("Could not implemented."); }
    //TraceJsonRequest(requestObject: JsonObject): any { throw new Error("Could not implemented."); }
    //TraceResponseJson(response: IEwsHttpWebResponse, memoryStream: any): any { throw new Error("Could not implemented."); }
    //TraceResponseXml(response: IEwsHttpWebResponse, memoryStream: any): any { throw new Error("Could not implemented."); }
    //TraceXmlRequest(memoryStream: any): any { throw new Error("Could not implemented."); }

    /**
     * @internal Validate request.
     */
    Validate(): void {
        this.Service.Validate();
    }

    /**
     * Validates request parameters, and emits the request to the server.
     *
     * @param   {IXHROptions}               request   The request.
     * @return  {Promise<XMLHttpRequest>}  The response returned by the server.
     */
    protected ValidateAndEmitRequest(request: IXHROptions): Promise<XMLHttpRequest> {
        this.Validate();

        //var request = this.BuildXHR();

        if (this.service.SendClientLatencies) {
            var clientStatisticsToAdd: string = '';

            //lock(clientStatisticsCache)
            //{
            if (ServiceRequestBase.clientStatisticsCache.length > 0) {
                clientStatisticsToAdd = ServiceRequestBase.clientStatisticsCache[0];
                ServiceRequestBase.clientStatisticsCache.splice(0, 1);
            }
            //}

            if (!StringHelper.IsNullOrEmpty(clientStatisticsToAdd)) {
                if (request.headers[ServiceRequestBase.ClientStatisticsRequestHeader]) {
                    request.headers[ServiceRequestBase.ClientStatisticsRequestHeader] =
                        request.headers[ServiceRequestBase.ClientStatisticsRequestHeader] + clientStatisticsToAdd;
                }
                else {
                    request.headers[ServiceRequestBase.ClientStatisticsRequestHeader] = clientStatisticsToAdd;
                }
            }
        }

        //var startTime = Date.now();// DateTime.UtcNow;
        //var response = XHR(request);
        EwsLogging.DebugLog("sending ews request");
        EwsLogging.DebugLog({ ...request, ...{ headers: { ...request.headers, Authorization: "REDACTED" } }}, true);

        return this.service.XHRApi.xhr(request);

        //try
        //{
        //    var response = this.GetEwsHttpWebResponse(request);
        //}
        //finally {
        //    if (this.service.SendClientLatencies) {
        //        int clientSideLatency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
        //        string requestId = string.Empty;
        //        string soapAction = this.GetType().Name.Replace("Request", string.Empty);

        //        if (response != null && response.Headers != null) {
        //            foreach(string requestIdHeader in ServiceRequestBase.RequestIdResponseHeaders)
        //            {
        //                string requestIdValue = response.Headers.Get(requestIdHeader);
        //                if (!string.IsNullOrEmpty(requestIdValue)) {
        //                    requestId = requestIdValue;
        //                    break;
        //                }
        //            }
        //        }

        //        StringBuilder sb = new StringBuilder();
        //        sb.Append("MessageId=");
        //        sb.Append(requestId);
        //        sb.Append(",ResponseTime=");
        //        sb.Append(clientSideLatency);
        //        sb.Append(",SoapAction=");
        //        sb.Append(soapAction);
        //        sb.Append(";");

        //        lock(clientStatisticsCache)
        //        {
        //            clientStatisticsCache.Add(sb.ToString());
        //        }
        //    }
        //}

        //return response;
    }

    //WrapStream(responseStream: any /*System.IO.Stream*/, contentEncoding: string): any /*System.IO.Stream*/ { throw new Error("Could not implemented."); }

    /**
     * @internal Writes XML attributes.
     *
     * /remarks/    Subclass will override if it has XML attributes.
     * @param   {EwsServiceXmlWriter}   writer   The writer.
     */
    WriteAttributesToXml(writer: EwsServiceXmlWriter): void { }

    /**
     * @internal Writes XML body.
     *
     * @param   {EwsServiceXmlWriter}   writer   The writer.
     */
    WriteBodyToXml(writer: EwsServiceXmlWriter): void {
        writer.WriteStartElement(XmlNamespace.Messages, this.GetXmlElementName());
        this.WriteAttributesToXml(writer);
        this.WriteElementsToXml(writer);
        writer.WriteEndElement(); // m:this.GetXmlElementName()
    }

    //#region HttpWebRequest-based implementation

    /**
     * @internal Writes XML.
     *
     * @param   {EwsServiceXmlWriter}   writer   The writer.
     */
    WriteToXml(writer: EwsServiceXmlWriter): void {
        writer.WriteStartElement(XmlNamespace.Soap, XmlElementNames.SOAPEnvelopeElementName);
        writer.WriteAttributeValue("xmlns", EwsUtilities.EwsXmlSchemaInstanceNamespacePrefix, EwsUtilities.EwsXmlSchemaInstanceNamespace);
        writer.WriteAttributeValue("xmlns", EwsUtilities.EwsMessagesNamespacePrefix, EwsUtilities.EwsMessagesNamespace);
        writer.WriteAttributeValue("xmlns", EwsUtilities.EwsTypesNamespacePrefix, EwsUtilities.EwsTypesNamespace);
        if (writer.RequireWSSecurityUtilityNamespace) {
            writer.WriteAttributeValue("xmlns", EwsUtilities.WSSecurityUtilityNamespacePrefix, EwsUtilities.WSSecurityUtilityNamespace);
        }

        writer.WriteStartElement(XmlNamespace.Soap, XmlElementNames.SOAPHeaderElementName);

        if (this.Service.Credentials != null) {
            //todo: fix extra soap headers if required
            this.Service.Credentials.EmitExtraSoapHeaderNamespaceAliases(null);
        }

        // Emit the RequestServerVersion header
        if (!this.Service.SuppressXmlVersionHeader) {
            writer.WriteStartElement(XmlNamespace.Types, XmlElementNames.RequestServerVersion);
            writer.WriteAttributeValue(XmlAttributeNames.Version, this.GetRequestedServiceVersionString());
            writer.WriteEndElement(); // RequestServerVersion
        }

        // Against Exchange 2007 SP1, we always emit the simplified time zone header. It adds very little to
        // the request, so bandwidth consumption is not an issue. Against Exchange 2010 and above, we emit
        // the full time zone header but only when the request actually needs it.
        //
        // The exception to this is if we are in Exchange2007 Compat Mode, in which case we should never emit
        // the header.  (Note: Exchange2007 Compat Mode is enabled for testability purposes only.)
        //
        if ((this.Service.RequestedServerVersion == ExchangeVersion.Exchange2007_SP1 || this.EmitTimeZoneHeader) &&
            (!this.Service.Exchange2007CompatibilityMode)) {
            writer.WriteStartElement(XmlNamespace.Types, XmlElementNames.TimeZoneContext);

            this.Service.TimeZoneDefinition.WriteToXml(writer);

            writer.WriteEndElement(); // TimeZoneContext

            writer.IsTimeZoneHeaderEmitted = true;
        }

        // Emit the MailboxCulture header
        // if (this.Service.PreferredCulture != null) {
        //     //todo: fix preferred culture.
        //     writer.WriteElementValue(
        //         XmlNamespace.Types,
        //         XmlElementNames.MailboxCulture,
        //         this.Service.PreferredCulture.Name);
        // }

        // Emit the DateTimePrecision header
        if (this.Service.DateTimePrecision != DateTimePrecision.Default) {
            writer.WriteElementValue(
                XmlNamespace.Types,
                XmlElementNames.DateTimePrecision,
                DateTimePrecision[this.Service.DateTimePrecision]);
            //this.Service.DateTimePrecision.ToString());
        }

        // Emit the ExchangeImpersonation header
        if (this.Service.ImpersonatedUserId != null) {
            this.Service.ImpersonatedUserId.WriteToXml(writer);
        }
        else if (this.Service.PrivilegedUserId != null) {
            //todo: find scenario if this is used anywhere, did not find any reference in MSDN
            this.Service.PrivilegedUserId.WriteToXml(writer, this.Service.RequestedServerVersion);
        }
        else if (this.Service.ManagementRoles != null) {
            this.Service.ManagementRoles.WriteToXml(writer);
        }

        if (this.Service.Credentials != null) {
            this.Service.Credentials.SerializeExtraSoapHeaders(writer, this.GetXmlElementName());
        }

        this.Service.DoOnSerializeCustomSoapHeaders(writer);

        writer.WriteEndElement(); // soap:Header

        writer.WriteStartElement(XmlNamespace.Soap, XmlElementNames.SOAPBodyElementName);

        this.WriteBodyToXml(writer);

        writer.WriteEndElement(); // soap:Body
        writer.WriteEndElement(); // soap:Envelope
    }

    //#endregion
}