/**
 * Copyright (C) 2014 Stratio (http://stratio.com)
 *
 * 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.
 */
package com.stratio.ingestion.source.rest;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;

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

import org.apache.commons.collections.CollectionUtils;
import org.apache.flume.Event;
import org.codehaus.jackson.map.ObjectMapper;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.SchedulerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.stratio.ingestion.source.rest.exception.RestSourceException;
import com.stratio.ingestion.source.rest.handler.RestSourceHandler;
import com.stratio.ingestion.source.rest.url.UrlHandler;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;

/**
 * RequestJob. Quartz Job that make a request to a RESTful service.
 */
public class RequestJob implements Job {

    private static final Logger log = LoggerFactory.getLogger(RequestJob.class);

    public static final String APPLICATION_TYPE = "applicationType";
    public static final String METHOD = "method";
    public static final String HEADERS = "headers";
    public static final String DEFAULT_REST_SOURCE_HANDLER = "com.stratio.ingestion.source"
            + ".rest.DefaultRestSourceHandler";
    public static final String HANDLER = "restSourceHandler";
    public static final String URL_HANDLER = "urlHandler";

    private Map<String, String> properties;
    private LinkedBlockingQueue<Event> queue;
    private Client client;
    private MediaType mediaType;
    private JobExecutionContext context;
    private RestSourceHandler restSourceHandler;
    private UrlHandler urlHandler;

    /**
     * {@inheritDoc}
     *
     * @param context
     */
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        this.context = context;
        SchedulerContext schedulerContext = null;
        try {
            log.debug("Executing quartz job");
            schedulerContext = context.getScheduler().getContext();
            initProperties(schedulerContext);

            WebResource.Builder resourceBuilder = getBuilder();
            ClientResponse response = getResponse(resourceBuilder);

            if (response != null) {
                String responseAsString = response.getEntity(String.class);
                final List<Event> events = restSourceHandler
                        .getEvents(responseAsString, responseToHeaders(response.getHeaders()));
                queue.addAll(events);
                urlHandler.updateFilterParameters(getLastEvent(events));
            }

        } catch (Exception e) {
            log.error("Error getting Response: " + e.getMessage());
        }
    }

    protected String getLastEvent(List<Event> events) {
        String lastEventAsString = "";
        if (CollectionUtils.isNotEmpty(events)) {
            final Event lastEvent = events.get(events.size() - 1);
            lastEventAsString = new String(lastEvent.getBody());
        }
        return lastEventAsString;
    }

    private WebResource.Builder getBuilder() {
        WebResource resource = client.resource(urlHandler.buildUrl(properties));
        WebResource.Builder resourceBuilder = setApplicationType(resource, properties.get(APPLICATION_TYPE));
        addHeaders(resourceBuilder, properties.get(HEADERS));
        return resourceBuilder;
    }

    private ClientResponse getResponse(WebResource.Builder webResource) {
        ClientResponse response;
        if ("GET".equals(properties.get(METHOD))) {
            response = webResource.get(ClientResponse.class);
        } else {
            //TODO pending review POST request implementation
            response = webResource.post(ClientResponse.class);
        }

        return response;
    }

    /**
     * Convert Multivalued Headers to Plain Map Headers accepted by Flume Event.
     *
     * @param map multivaluedMap.
     * @return plain Map.
     */
    private Map<String, String> responseToHeaders(MultivaluedMap<String, String> map) {
        Map<String, String> newMap = new HashMap<String, String>();
        for (Map.Entry<String, List<String>> entry : map.entrySet()) {
            newMap.put(entry.getKey(), multiValueHeaderToString(entry.getValue()));
        }
        return newMap;
    }

    /**
     * Convert a multivalue header to String.
     *
     * @param list
     * @return
     * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">w3.org</a>
     */
    private String multiValueHeaderToString(List<String> list) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < list.size(); ++i) {
            sb.append(list.get(i));
            if (i != list.size() - 1) {
                sb.append(", ");
            }
        }

        return sb.toString();
    }

    /**
     * Initialize properties that are received in the {@code SchedulerContext}.
     *
     * @param context
     */
    @SuppressWarnings("unchecked")
    public void initProperties(SchedulerContext context) {
        queue = (LinkedBlockingQueue<Event>) context.get("queue");
        properties = (Map<String, String>) context.get("properties");
        client = (Client) context.get("client");
        restSourceHandler = (RestSourceHandler) context.get(HANDLER);
        urlHandler = (UrlHandler) context.get(URL_HANDLER);

    }

    /**
     * Set an Application Type to the request depending on a parameter and its corresponding
     * {@code MediaType}.
     *
     * @param webResource     Current target url.
     * @param applicationType ApplicationType to set.
     * @return
     */
    public WebResource.Builder setApplicationType(WebResource webResource, String applicationType) {
        if ("TEXT".equals(applicationType)) {
            mediaType = MediaType.TEXT_PLAIN_TYPE;
        } else {
            mediaType = MediaType.APPLICATION_JSON_TYPE;
        }

        return webResource.accept(mediaType);
    }

    /**
     * Map raw Json to an object and add each key-value to a headers request.
     *
     * @param builder     Current REST request.
     * @param jsonHeaders raw json.
     * @return
     */
    private WebResource.Builder addHeaders(WebResource.Builder builder, String jsonHeaders) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            Map<String, Object> headers = mapper.readValue(jsonHeaders, Map.class);
            for (Map.Entry<String, Object> entry : headers.entrySet()) {
                builder.header(entry.getKey(), entry.getValue());
            }
        } catch (Exception e) {
            throw new RestSourceException("An error occurred during headers parsing", e);
        }

        return builder;
    }

}
