/*******************************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.
 ******************************************************************************/
package org.apache.olingo.odata2.core.rest;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.HEAD;
import javax.ws.rs.HeaderParam;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.core.Response;

import org.apache.olingo.odata2.api.ODataService;
import org.apache.olingo.odata2.api.ODataServiceFactory;
import org.apache.olingo.odata2.api.commons.ODataHttpMethod;
import org.apache.olingo.odata2.api.exception.MessageReference;
import org.apache.olingo.odata2.api.exception.ODataException;
import org.apache.olingo.odata2.api.exception.ODataInternalServerErrorException;
import org.apache.olingo.odata2.api.exception.ODataMessageException;
import org.apache.olingo.odata2.api.exception.ODataNotImplementedException;
import org.apache.olingo.odata2.api.processor.ODataContext;
import org.apache.olingo.odata2.api.processor.ODataRequest;
import org.apache.olingo.odata2.api.processor.ODataResponse;
import org.apache.olingo.odata2.core.ODataContextImpl;
import org.apache.olingo.odata2.core.ODataRequestHandler;

/**
 *  
 */
public final class ODataSubLocator {

  private ODataServiceFactory serviceFactory;
  private ODataRequest request;

  private HttpServletRequest httpRequest;

  @GET
  public Response handleGet() throws ODataException {
    return handle(ODataHttpMethod.GET);
  }

  @PUT
  public Response handlePut() throws ODataException {
    return handle(ODataHttpMethod.PUT);
  }

  @PATCH
  public Response handlePatch() throws ODataException {
    return handle(ODataHttpMethod.PATCH);
  }

  @MERGE
  public Response handleMerge() throws ODataException {
    return handle(ODataHttpMethod.MERGE);
  }

  @DELETE
  public Response handleDelete() throws ODataException {
    return handle(ODataHttpMethod.DELETE);
  }

  @POST
  public Response handlePost(@HeaderParam("X-HTTP-Method") final String xHttpMethod) throws ODataException {
    Response response;

    if (xHttpMethod == null) {
      response = handle(ODataHttpMethod.POST);
    } else {
      /* tunneling */
      if ("MERGE".equals(xHttpMethod)) {
        response = handle(ODataHttpMethod.MERGE);
      } else if ("PATCH".equals(xHttpMethod)) {
        response = handle(ODataHttpMethod.PATCH);
      } else if (HttpMethod.DELETE.equals(xHttpMethod)) {
        response = handle(ODataHttpMethod.DELETE);
      } else if (HttpMethod.PUT.equals(xHttpMethod)) {
        response = handle(ODataHttpMethod.PUT);
      } else if (HttpMethod.GET.equals(xHttpMethod)) {
        response = handle(ODataHttpMethod.GET);
      } else if (HttpMethod.POST.equals(xHttpMethod)) {
        response = handle(ODataHttpMethod.POST);
      } else if (HttpMethod.HEAD.equals(xHttpMethod)) {
        response = handleHead();
      } else if (HttpMethod.OPTIONS.equals(xHttpMethod)) {
        response = handleOptions();
      } else {
        response = returnNotImplementedResponse(ODataNotImplementedException.TUNNELING);
      }
    }
    return response;
  }

  private Response returnNotImplementedResponse(final MessageReference messageReference) {
    // RFC 2616, 5.1.1: "An origin server SHOULD return the status code [...]
    // 501 (Not Implemented) if the method is unrecognized [...] by the origin server."
    return returnException(new ODataNotImplementedException(messageReference));
  }

  private Response returnException(final ODataMessageException messageException) {
    ODataContextImpl context = new ODataContextImpl(request, serviceFactory);
    context.setRequest(request);
    context.setAcceptableLanguages(request.getAcceptableLanguages());
    context.setPathInfo(request.getPathInfo());
    context.setServiceFactory(serviceFactory);
    context.setParameter(ODataContext.HTTP_SERVLET_REQUEST_OBJECT, httpRequest);
    ODataExceptionWrapper exceptionWrapper =
        new ODataExceptionWrapper(context, request.getQueryParameters(), request.getAcceptHeaders());
    ODataResponse response =
        exceptionWrapper.wrapInExceptionResponse(messageException);
    return RestUtil.convertResponse(response);
  }
  
  private Response returnNoServiceResponse(MessageReference messageReference) {
    return returnException(new ODataInternalServerErrorException(messageReference));
  }

  @OPTIONS
  public Response handleOptions() throws ODataException {
    // RFC 2616, 5.1.1: "An origin server SHOULD return the status code [...]
    // 501 (Not Implemented) if the method is unrecognized or not implemented
    // by the origin server."
    return returnNotImplementedResponse(ODataNotImplementedException.COMMON);
  }

  @HEAD
  public Response handleHead() throws ODataException {
    return handleGet();
  }

  private Response handle(final ODataHttpMethod method) throws ODataException {
    request = ODataRequest.fromRequest(request).method(method).build();

    ODataContextImpl context = new ODataContextImpl(request, serviceFactory);
    context.setParameter(ODataContext.HTTP_SERVLET_REQUEST_OBJECT, httpRequest);

    ODataService service = serviceFactory.createService(context);
    if(service == null){
      return returnNoServiceResponse(ODataInternalServerErrorException.NOSERVICE);
    }
    service.getProcessor().setContext(context);
    context.setService(service);

    ODataRequestHandler requestHandler = new ODataRequestHandler(serviceFactory, service, context);

    final ODataResponse odataResponse = requestHandler.handle(request);
    final Response response = RestUtil.convertResponse(odataResponse);

    return response;
  }



  public static ODataSubLocator create(final SubLocatorParameter param) throws ODataException {
    ODataSubLocator subLocator = new ODataSubLocator();

    subLocator.serviceFactory = param.getServiceFactory();
    subLocator.request = ODataRequest.acceptableLanguages(param.getHttpHeaders().getAcceptableLanguages())
        .httpMethod(param.getServletRequest().getMethod())
        .acceptHeaders(RestUtil.extractAcceptHeaders(param))
        .body(RestUtil.contentAsStream(RestUtil.extractRequestContent(param)))
        .pathInfo(RestUtil.buildODataPathInfo(param))
        .allQueryParameters(param.getUriInfo().getQueryParameters())
        .requestHeaders(param.getHttpHeaders().getRequestHeaders())
        .contentType(RestUtil.extractRequestContentType(param).toContentTypeString())
        .build();

    subLocator.httpRequest = param.getServletRequest();

    return subLocator;
  }

  private ODataSubLocator() {
    super();
  }
}
