//Copyright 2010 Microsoft Corporation
//
//Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
//You may obtain a copy of the License at 
//
//http://www.apache.org/licenses/LICENSE-2.0 
//
//Unless required by applicable law or agreed to in writing, software distributed under the License 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.


namespace System.Data.Services.Client
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Text;
#if !ASTORIA_LIGHT    
    using System.Net;
#else
    using System.Data.Services.Http;
#endif
    using System.Xml;

    public abstract class DataServiceRequest
    {
        internal DataServiceRequest()
        {
        }

        public abstract Type ElementType
        {
            get;
        }

        public abstract Uri RequestUri
        {
            get;
        }

        internal abstract ProjectionPlan Plan
        {
            get;
        }

        internal abstract QueryComponents QueryComponents
        {
            get;
        }

        public override string ToString()
        {
            return this.QueryComponents.Uri.ToString();
        }   

        internal static MaterializeAtom Materialize(DataServiceContext context, QueryComponents queryComponents, ProjectionPlan plan, string contentType, Stream response)
        {
            Debug.Assert(null != queryComponents, "querycomponents");

            string mime = null;
            Encoding encoding = null;
            if (!String.IsNullOrEmpty(contentType))
            {
                HttpProcessUtility.ReadContentType(contentType, out mime, out encoding);
            }

            if (String.Equals(mime, XmlConstants.MimeApplicationAtom, StringComparison.OrdinalIgnoreCase) ||
                String.Equals(mime, XmlConstants.MimeApplicationXml, StringComparison.OrdinalIgnoreCase))
            {
                if (null != response)
                {
                    XmlReader reader = XmlUtil.CreateXmlReader(response, encoding);
                    return new MaterializeAtom(context, reader, queryComponents, plan, context.MergeOption);
                }
            }

            return MaterializeAtom.EmptyResults;
        }

        internal static DataServiceRequest GetInstance(Type elementType, Uri requestUri)
        {
            Type genericType = typeof(DataServiceRequest<>).MakeGenericType(elementType);
            return (DataServiceRequest)Activator.CreateInstance(genericType, new object[] { requestUri });
        }

        internal static IEnumerable<TElement> EndExecute<TElement>(object source, DataServiceContext context, IAsyncResult asyncResult)
        {
            QueryResult result = null;
            try
            {
                result = QueryResult.EndExecute<TElement>(source, asyncResult);
                return result.ProcessResult<TElement>(context, result.ServiceRequest.Plan);
            }
            catch (DataServiceQueryException ex)
            {
                Exception inEx = ex;
                while (inEx.InnerException != null)
                {
                    inEx = inEx.InnerException;
                }

                DataServiceClientException serviceEx = inEx as DataServiceClientException;
                if (context.IgnoreResourceNotFoundException && serviceEx != null && serviceEx.StatusCode == (int)HttpStatusCode.NotFound)
                {
                    QueryOperationResponse qor = new QueryOperationResponse<TElement>(new Dictionary<string, string>(ex.Response.Headers), ex.Response.Query, MaterializeAtom.EmptyResults);
                    qor.StatusCode = (int)HttpStatusCode.NotFound;
                    return (IEnumerable<TElement>)qor;
                }

                throw;
            }
        }

#if !ASTORIA_LIGHT        
        internal QueryOperationResponse<TElement> Execute<TElement>(DataServiceContext context, QueryComponents queryComponents)
        {
            QueryResult result = null;
            try
            {
                DataServiceRequest<TElement> serviceRequest = new DataServiceRequest<TElement>(queryComponents, this.Plan);
                result = serviceRequest.CreateResult(this, context, null, null);
                result.Execute();
                return result.ProcessResult<TElement>(context, this.Plan);
            }
            catch (InvalidOperationException ex)
            {
                QueryOperationResponse operationResponse = result.GetResponse<TElement>(MaterializeAtom.EmptyResults);

                if (null != operationResponse)
                {
                    if (context.IgnoreResourceNotFoundException)
                    {
                        DataServiceClientException cex = ex as DataServiceClientException;
                        if (cex != null && cex.StatusCode == (int)HttpStatusCode.NotFound)
                        {
                            return (QueryOperationResponse<TElement>)operationResponse;
                        }
                    }

                    operationResponse.Error = ex;
                    throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, operationResponse);
                }

                throw;
            }
        }

        internal long GetQuerySetCount(DataServiceContext context)
        {
            Debug.Assert(null != context, "context is null");
            this.QueryComponents.Version = Util.DataServiceVersion2;

            QueryResult response = null;
            DataServiceRequest<long> serviceRequest = new DataServiceRequest<long>(this.QueryComponents, null);
            HttpWebRequest request = context.CreateRequest(this.QueryComponents.Uri, XmlConstants.HttpMethodGet, false, null, this.QueryComponents.Version, false);
            
            request.Accept = "text/plain";
            response = new QueryResult(this, "Execute", serviceRequest, request, null, null);

            try
            {
                response.Execute();

                if (HttpStatusCode.NoContent != response.StatusCode)
                {
                    StreamReader sr = new StreamReader(response.GetResponseStream());
                    long r = -1;
                    try
                    {
                        r = XmlConvert.ToInt64(sr.ReadToEnd());
                    }
                    finally
                    {
                        sr.Close();
                    }

                    return r;
                }
                else
                {
                    throw new DataServiceQueryException(Strings.DataServiceRequest_FailGetCount, response.Failure);
                }
            }
            catch (InvalidOperationException ex)
            {
                QueryOperationResponse operationResponse = null;
                operationResponse = response.GetResponse<long>(MaterializeAtom.EmptyResults);
                if (null != operationResponse)
                {
                    operationResponse.Error = ex;
                    throw new DataServiceQueryException(Strings.DataServiceException_GeneralError, ex, operationResponse);
                }

                throw;
            }
        }
#endif

        internal IAsyncResult BeginExecute(object source, DataServiceContext context, AsyncCallback callback, object state)
        {
            QueryResult result = this.CreateResult(source, context, callback, state);
            result.BeginExecute();
            return result;
        }

        private QueryResult CreateResult(object source, DataServiceContext context, AsyncCallback callback, object state)
        {
            Debug.Assert(null != context, "context is null");
            HttpWebRequest request = context.CreateRequest(this.QueryComponents.Uri, XmlConstants.HttpMethodGet, false, null, this.QueryComponents.Version, false);
            return new QueryResult(source, "Execute", this, request, callback, state);
        }
    }
}
