/*
 *  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.isis.viewer.restfulobjects.rendering.service.conneg;

import java.util.List;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.isis.applib.annotation.DomainService;
import org.apache.isis.applib.annotation.NatureOfService;
import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
import org.apache.isis.viewer.restfulobjects.applib.RepresentationType;
import org.apache.isis.viewer.restfulobjects.applib.client.RestfulResponse;
import org.apache.isis.viewer.restfulobjects.rendering.RestfulObjectsApplicationException;
import org.apache.isis.viewer.restfulobjects.rendering.domainobjects.ObjectAndActionInvocation;
import org.apache.isis.viewer.restfulobjects.rendering.service.RepresentationService;
import org.apache.isis.viewer.restfulobjects.rendering.service.conmap.ContentMappingService;

/**
 * Handles content negotiation for accept headers requiring <code>application/json</code> or <code>application/xml</code>and specifying an x-ro-domain-type; will delegate to
 * any available {@link ContentMappingService}s to (try to) map the result object into the required representation if possible.
 *
 * <p>
 *     In the accept header the profile is also checked dependent on the resource being invoked; either <code>profile="urn:org.restfulobjects:repr-types/object"</code> for an object representation, or <code>profile="profile=urn:org.restfulobjects:repr-types/action-result"</code> for an action result.
 * </p>
 *
 * <p>
 *     If the accept header specifies <code>application/xml</code> then the service additionally verifies that the (mapped) domain object's
 *     runtime type is annotated with the JAXB {@link javax.xml.bind.annotation.XmlRootElement} annotation so that RestEasy is able to
 *     unambiguously serialize it.
 * </p>
 */
@DomainService(
        nature = NatureOfService.DOMAIN,
        // in effect, is the relative priority (lower numbers have higher priority)
        menuOrder = "" + (Integer.MAX_VALUE - 20)
)
public class ContentNegotiationServiceXRoDomainType extends ContentNegotiationServiceAbstract {

    public static final String X_RO_DOMAIN_TYPE = "x-ro-domain-type";

    /**
     * search for an accept header in form <code>application/xml;profile=urn:org.restfulobjects:repr-types/object;x-ro-domain-type=todoapp.dto.module.todoitem.ToDoItemDto</code>
     */
    @Override
    public Response.ResponseBuilder buildResponse(
            final RepresentationService.Context2 renderContext2,
            final ObjectAdapter objectAdapter) {

        final Object domainObject = objectOf(objectAdapter);
        final RepresentationType representationType = RepresentationType.DOMAIN_OBJECT;

        final MediaType mediaType = mediaTypeFrom(renderContext2, representationType);
        if (mediaType == null) {
            return null;
        }

        return buildResponse(renderContext2, domainObject, representationType);
    }

    protected MediaType mediaTypeFrom(
            final RepresentationService.Context2 renderContext2,
            final RepresentationType representationType) {
        final List<MediaType> acceptableMediaTypes = renderContext2.getAcceptableMediaTypes();
        MediaType mediaType =
                representationType.matchesXmlProfileWithParameter(acceptableMediaTypes, X_RO_DOMAIN_TYPE);

        if (mediaType == null) {
            mediaType =
                    representationType.matchesJsonProfileWithParameter(acceptableMediaTypes, X_RO_DOMAIN_TYPE);
        }
        return mediaType;
    }

    /**
     * search for an accept header in form <code>application/xml;profile=urn:org.restfulobjects:repr-types/action-result;x-ro-domain-type=todoapp.dto.module.todoitem.ToDoItemDto</code>
     */
    @Override
    public Response.ResponseBuilder buildResponse(
            final RepresentationService.Context2 renderContext2,
            final ObjectAndActionInvocation objectAndActionInvocation) {

        final RepresentationType representationType = RepresentationType.ACTION_RESULT;

        final MediaType mediaType = mediaTypeFrom(renderContext2, representationType);
        if (mediaType == null) {
            return null;
        }

        final Object domainObject = returnedObjectOf(objectAndActionInvocation);
        if(domainObject == null) {
            throw RestfulObjectsApplicationException.create(RestfulResponse.HttpStatusCode.NOT_FOUND);
        }
        return buildResponse(renderContext2, domainObject, representationType);
    }

    protected Response.ResponseBuilder buildResponse(
            final RepresentationService.Context2 renderContext2,
            final Object domainObject,
            final RepresentationType representationType) {

        final List<MediaType> acceptableMediaTypes = renderContext2.getAcceptableMediaTypes();

        final MediaType mediaType = mediaTypeFrom(renderContext2, representationType);
        if (mediaType == null) {
            return null;
        }

        final String xRoDomainType = mediaType.getParameters().get(X_RO_DOMAIN_TYPE);
        final Class<?> domainType = loadClass(xRoDomainType);

        final Object mappedDomainObject = map(domainObject, acceptableMediaTypes, representationType);

        ensureDomainObjectAssignable(xRoDomainType, domainType, mappedDomainObject);

        if("xml".equals(mediaType.getSubtype())) {
            ensureJaxbAnnotated(mappedDomainObject.getClass());
        }

        return Response.ok(mappedDomainObject, mediaType);
    }

    /**
     * Delegates to either the applib {@link org.apache.isis.applib.conmap.ContentMappingService}, else the
     * original non-applib {@link ContentMappingService}.
     */
    protected Object map(
            final Object domainObject,
            final List<MediaType> acceptableMediaTypes,
            final RepresentationType representationType) {

        for (org.apache.isis.applib.conmap.ContentMappingService contentMappingService : contentMappingServices) {
            Object mappedObject = contentMappingService.map(domainObject, acceptableMediaTypes);
            if(mappedObject != null) {
                return mappedObject;
            }
        }

        for (ContentMappingService contentMappingService : legacyContentMappingServices) {
            Object mappedObject = contentMappingService.map(domainObject, acceptableMediaTypes, representationType);
            if(mappedObject != null) {
                return mappedObject;
            }
        }

        return domainObject;
    }

    @javax.inject.Inject
    List<org.apache.isis.applib.conmap.ContentMappingService> contentMappingServices;

    @javax.inject.Inject
    List<ContentMappingService> legacyContentMappingServices;
}
