package org.ovirt.engine.api.restapi.resource;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.HashMap;
import java.util.List;

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

import org.ovirt.engine.api.common.util.LinkHelper;
import org.ovirt.engine.api.common.util.QueryHelper;
import org.ovirt.engine.api.common.util.StatusUtils;
import org.ovirt.engine.api.model.ActionableResource;
import org.ovirt.engine.api.model.BaseResource;
import org.ovirt.engine.core.common.action.VdcActionParametersBase;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.interfaces.SearchType;
import org.ovirt.engine.core.common.queries.SearchParameters;
import org.ovirt.engine.core.common.queries.VdcQueryParametersBase;
import org.ovirt.engine.core.common.queries.VdcQueryType;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.utils.log.Log;
import org.ovirt.engine.core.utils.log.LogFactory;

public abstract class AbstractBackendCollectionResource<R extends BaseResource, Q /* extends IVdcQueryable */>
        extends AbstractBackendResource<R, Q> {

    private static final String EXPECT_HEADER = "Expect";
    private static final String BLOCKING_EXPECTATION = "201-created";
    private static final String CREATION_STATUS_REL = "creation_status";
    public static final String FROM_CONSTRAINT_PARAMETER = "from";
    public static final String CASE_SENSITIVE_CONSTRAINT_PARAMETER = "case_sensitive";
    protected static final Log LOG = LogFactory.getLog(AbstractBackendCollectionResource.class);

    protected AbstractBackendCollectionResource(Class<R> modelType, Class<Q> entityType, String... subCollections) {
        super(modelType, entityType, subCollections);
    }

    public Response remove(String id) {
        getEntity(id);  //will throw 404 if entity not found.
        return performRemove(id);
    }

    protected abstract Response performRemove(String id);

    protected List<Q> getBackendCollection(SearchType searchType) {
        return getBackendCollection(searchType, QueryHelper.getConstraint(getUriInfo(), "",  modelType));
    }

    protected List<Q> getBackendCollection(SearchType searchType, String constraint) {
        return getBackendCollection(entityType,
                VdcQueryType.Search,
                getSearchParameters(searchType, constraint));
    }

    private SearchParameters getSearchParameters(SearchType searchType, String constraint) {
        SearchParameters searchParams = new SearchParameters(constraint, searchType);
        HashMap<String, String> matrixConstraints = QueryHelper.getMatrixConstraints(getUriInfo(),
                                                                                     CASE_SENSITIVE_CONSTRAINT_PARAMETER,
                                                                                     FROM_CONSTRAINT_PARAMETER);

        //preserved in sake if backward compatibility until 4.0
        HashMap<String, String> queryConstraints = QueryHelper.getQueryConstraints(getUriInfo(),
                                                                                   FROM_CONSTRAINT_PARAMETER);

        if (matrixConstraints.containsKey(FROM_CONSTRAINT_PARAMETER)) {
            try {
                searchParams.setSearchFrom(Long.parseLong(matrixConstraints.get(FROM_CONSTRAINT_PARAMETER)));
            } catch (Exception ex) {
                LOG.error("Unwrapping of '"+FROM_CONSTRAINT_PARAMETER+"' matrix search parameter failed.", ex);
            }
        } else if (queryConstraints.containsKey(FROM_CONSTRAINT_PARAMETER)) {
            //preserved in sake if backward compatibility until 4.0
            try {
                searchParams.setSearchFrom(Long.parseLong(queryConstraints.get(FROM_CONSTRAINT_PARAMETER)));
            } catch (Exception ex) {
                LOG.error("Unwrapping of '"+FROM_CONSTRAINT_PARAMETER+"' query search parameter failed.", ex);
            }
        }
        if (matrixConstraints.containsKey(CASE_SENSITIVE_CONSTRAINT_PARAMETER)) {
            try {
                searchParams.setCaseSensitive(Boolean.parseBoolean(matrixConstraints.get(CASE_SENSITIVE_CONSTRAINT_PARAMETER)));
            } catch (Exception ex) {
                LOG.error("Unwrapping of '"+CASE_SENSITIVE_CONSTRAINT_PARAMETER+"' search parameter failed.", ex);
            }
        }

        if (QueryHelper.hasMatrixParam(getUriInfo(), MAX) && getMaxResults()!=NO_LIMIT) {
            searchParams.setMaxCount(getMaxResults());
        }
        return searchParams;
    }

    protected List<Q> getBackendCollection(VdcQueryType query, VdcQueryParametersBase queryParams) {
        return getBackendCollection(entityType, query, queryParams);
    }

    protected Response performCreation(VdcActionType task,
            VdcActionParametersBase taskParams,
            EntityIdResolver entityResolver,
            boolean block) {
        return performCreation(task, taskParams, entityResolver, block, null);
    }

    protected Response performCreation(VdcActionType task,
                                       VdcActionParametersBase taskParams,
                                       EntityIdResolver entityResolver,
                                       boolean block,
                                       Class<? extends BaseResource> suggestedParentType) {
        VdcReturnValueBase createResult;
        try {
            createResult = doAction(task, taskParams);
        } catch (Exception e) {
            return handleError(e, false);
        }

        R model = resolveCreated(createResult, entityResolver, suggestedParentType);
        Response response = null;
        if (createResult.getHasAsyncTasks()) {
            if (block) {
                awaitCompletion(createResult);
                // refresh model state
                model = resolveCreated(createResult, entityResolver, suggestedParentType);
                response = Response.created(URI.create(model.getHref())).entity(model).build();
            } else {
                if (model==null) {
                    response = Response.status(ACCEPTED_STATUS).build();
                } else {
                    handleAsynchrony(createResult, model);
                    response = Response.status(ACCEPTED_STATUS).entity(model).build();
                }
            }
        } else {
            if (model==null) {
                response = Response.status(ACCEPTED_STATUS).build();
            } else {
                response = Response.created(URI.create(model.getHref())).entity(model).build();
            }
        }
        return response;
    }

    protected Response performCreation(VdcActionType task,
            VdcActionParametersBase taskParams,
            EntityIdResolver entityResolver) {
        return performCreation(task, taskParams, entityResolver, expectBlocking());
    }

    protected Response performCreation(VdcActionType task,
            VdcActionParametersBase taskParams,
            EntityIdResolver entityResolver,
            Class<? extends BaseResource> suggestedParentType) {
        return performCreation(task, taskParams, entityResolver, expectBlocking(), suggestedParentType);
    }

    protected boolean expectBlocking() {
        boolean expectBlocking = false;
        List<String> expect = httpHeaders.getRequestHeader(EXPECT_HEADER);
        if (expect != null && expect.size() > 0) {
            expectBlocking = expect.get(0).equalsIgnoreCase(BLOCKING_EXPECTATION);
        }
        return expectBlocking;
    }

    protected void handleAsynchrony(VdcReturnValueBase result, R model) {
        model.setCreationStatus(StatusUtils.create(getAsynchronousStatus(result)));
        linkSubResource(model, CREATION_STATUS_REL, asString(result.getTaskIdList()));
    }

    protected R resolveCreated(VdcReturnValueBase result, EntityIdResolver entityResolver, Class<? extends BaseResource> suggestedParentType) {
        try {
            Q created = entityResolver.resolve((Guid)result.getActionReturnValue());
            return addLinks(populate(map(created), created), suggestedParentType);
        } catch (Exception e) {
            // we tolerate a failure in the entity resolution
            // as the substantive action (entity creation) has
            // already succeeded
            e.printStackTrace();
            return null;
        }
    }

    protected String asString(VdcReturnValueBase result) {
        Guid guid = (Guid)result.getActionReturnValue();
        return guid != null ? guid.toString() : null;
    }

    protected void getEntity(String id) {
        try {
            Method method = getMethod(this.getClass(), SingleEntityResource.class);
            if (method==null) {
                LOG.error("Could not find sub-resource in the collection resource");
            } else {
                Object entityResource = method.invoke(this, id);
                method = entityResource.getClass().getMethod("get");
                if (method==null) {
                    LOG.warn("Could not find GET method in " + entityResource.getClass().getName());
                } else {
                    method.invoke(entityResource);
                }
            }
        } catch (IllegalAccessException e) {
            LOG.error("Reflection Error", e);
        } catch (InvocationTargetException e) {
            if (e.getTargetException() instanceof WebApplicationException) {
                throw((WebApplicationException)e.getTargetException());
            } else {
                LOG.error("Reflection Error", e);
            }
        } catch (SecurityException e) {
            LOG.error("Reflection Error", e);
        } catch (NoSuchMethodException e) {
            LOG.error("Reflection Error", e);
        }
    }

    @SuppressWarnings("unchecked")
    private Method getMethod(Class<?> clazz, @SuppressWarnings("rawtypes") Class annotation) {
        for (Method m : clazz.getMethods()) {
            if (m.getAnnotation(annotation)!=null) {
                return m;
            }
        }
        return null;
    }

    /**
     *
     * @param model the resource to add actions to
     * @return collection with action links
     */
    protected <C extends ActionableResource> C addActions(C model) {
        LinkHelper.addActions(getUriInfo(), model, this);
        return model;
    }
}
