/**
 * AbstractPushServlet
 * Copyright 27.07.2015 by Dang Hai An, @zyzo
 * <p/>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p/>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * <p/>
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program in the file lgpl21.txt
 * If not, see <http://www.gnu.org/licenses/>.
 */

package org.loklak.api.server.push;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.github.fge.jsonschema.core.report.ProcessingReport;

import org.loklak.api.client.ClientConnection;
import org.loklak.api.server.RemoteAccess;
import org.loklak.data.DAO;
import org.loklak.data.MessageEntry;
import org.loklak.geo.LocationSource;
import org.loklak.geo.PlaceContext;
import org.loklak.harvester.JsonFieldConverter;
import org.loklak.harvester.JsonValidator;
import org.loklak.harvester.SourceType;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public abstract class AbstractPushServlet extends HttpServlet {

    private JsonValidator validator;
    private JsonFieldConverter converter;

    @Override
    public void init() throws ServletException {
        try {
            validator = new JsonValidator(this.getValidatorSchema());
        } catch (IOException e) {
            DAO.log("Unable to initialize push servlet validator : " + e.getMessage());
            e.printStackTrace();
        }
        try {
            converter = new JsonFieldConverter(this.getConversionSchema());
        } catch (IOException e) {
            DAO.log("Unable to initialize push servlet field converter : " + e.getMessage());
            e.printStackTrace();
        }
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        RemoteAccess.Post post = RemoteAccess.evaluate(request);
        String remoteHash = Integer.toHexString(Math.abs(post.getClientHost().hashCode()));

        // manage DoS
        if (post.isDoS_blackout()) {
            response.sendError(503, "your request frequency is too high");
            return;
        }

        String url = post.get("url", "");
        if (url == null || url.length() == 0) {
            response.sendError(400, "your request does not contain an url to your data object");
            return;
        }
        String screen_name = post.get("screen_name", "");
        if (screen_name == null || screen_name.length() == 0) {
            response.sendError(400, "your request does not contain required screen_name parameter");
            return;
        }

        Map<String, Object> map;
        byte[] jsonText;
        try {
            jsonText = ClientConnection.download(url);
            map = DAO.jsonMapper.readValue(jsonText, DAO.jsonTypeRef);
        } catch (Exception e) {
            response.sendError(400, "error reading json file from url");
            return;
        }

        // validation phase
        ProcessingReport report = this.validator.validate(new String(jsonText));
        if (!report.isSuccess()) {
            response.sendError(400, "json does not conform to schema : " + this.getValidatorSchema().name() + "\n" + report);
            return;
        }

        // conversion phase
        Object extractResults = extractMessages(map);
        List<Map<String, Object>> messages;
        if (extractResults instanceof List) {
            messages = (List<Map<String, Object>>) extractResults;
        } else if (extractResults instanceof Map) {
            messages = new ArrayList<>();
            messages.add((Map<String, Object>) extractResults);
        } else {
            throw new IOException("extractMessages must return either a List or a Map");
        }
        List<Map<String, Object>> convertedMessages = this.converter.convert(messages);

        PushReport nodePushReport = new PushReport();
        ObjectWriter ow = new ObjectMapper().writerWithDefaultPrettyPrinter();
        // custom treatment for each message
        for (int i = 0; i < convertedMessages.size(); i++) {
            Map<String, Object> message = convertedMessages.get(i);
            message.put("source_type", this.getSourceType().name());
            message.put("location_source", LocationSource.USER.name());
            message.put("place_context", PlaceContext.ABOUT.name());
            if (message.get("text") == null) {
                message.put("text", "");
            }

            // append rich-text attachment
            String jsonToText = ow.writeValueAsString(messages.get(i));
            message.put("text", message.get("text") + MessageEntry.RICH_TEXT_SEPARATOR + jsonToText);
            customProcessing(message);

            if (message.get("mtime") == null) {
                String existed = PushServletHelper.checkMessageExistence(message);
                // message known
                if (existed != null) {
                    messages.remove(i);
                    nodePushReport.incrementKnownCount(existed);
                    continue;
                }
                // updated message -> save with new mtime value
                message.put("mtime", Long.toString(System.currentTimeMillis()));
            }

            try {
                message.put("id_str", PushServletHelper.computeMessageId(message, getSourceType()));
            } catch (Exception e) {
                DAO.log("Problem computing id : " + e.getMessage());
            }
        }
        try {
            PushReport savingReport = PushServletHelper.saveMessagesAndImportProfile(convertedMessages, Arrays.hashCode(jsonText), post, getSourceType(), screen_name);
            nodePushReport.combine(savingReport);
        } catch (IOException e) {
            response.sendError(404, e.getMessage());
            return;
        }
        String res = PushServletHelper.buildJSONResponse(post.get("callback", ""), nodePushReport);

        post.setResponse(response, "application/javascript");
        response.getOutputStream().println(res);
        DAO.log(request.getServletPath()
                + " -> records = " + nodePushReport.getRecordCount()
                + ", new = " + nodePushReport.getNewCount()
                + ", known = " + nodePushReport.getKnownCount()
                + ", error = " + nodePushReport.getErrorCount()
                + ", from host hash " + remoteHash);
    }

    protected abstract SourceType getSourceType();

    protected abstract JsonValidator.JsonSchemaEnum getValidatorSchema();

    protected abstract JsonFieldConverter.JsonConversionSchemaEnum getConversionSchema();

    // return either a list or a map of <String,Object>
    protected abstract Object extractMessages(Map<String, Object> data);

    protected abstract void customProcessing(Map<String, Object> message);
}

@@ -4653,16 +4653,118 @@
 or a Map
+. Get %22 + (extractResults == null ? %22null%22 : extractResults.getClass().getCanonicalName()) + %22 instead
 %22);%0A    
@@ -5064,26 +5064,17 @@
  0; i %3C 
-convertedM
+m
 essages.

Fix IndexOutOfBoundException when performing push request