/* 
 * Copyright 2015 Adobe.
 *
 * 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.adobe.ags.curly.model;

import com.adobe.ags.curly.ApplicationState;
import static com.adobe.ags.curly.Messages.*;
import com.adobe.ags.curly.controller.ActionRunner;
import com.adobe.ags.curly.xml.ResultType;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;

public class ActionResult extends RunnerResult<RunnerResult> {

    public static enum RESULT_TYPE {
        NEUTRAL(
                // These are successful or unsuccessful depending on the status code
                Pattern.compile(".*?" + Pattern.quote("<div id=\"Message\">") + "(.*)")
        ),
        WARN(),
        FAIL(
                // Regardless of status code, if these are detected the operation should log as failure
                Pattern.compile(".*?" + Pattern.quote("<div class=\"error\">") + "(.*?)" + Pattern.quote("</div>"))
        );

        Pattern[] patterns;

        RESULT_TYPE(Pattern... p) {
            patterns = p;
        }
    };

    private static class ParsedResponseMessage {

        RESULT_TYPE type;
        String message;

        public ParsedResponseMessage(RESULT_TYPE type, String message) {
            this.type = type;
            this.message = message;
        }
    }

    boolean debugMode = false;
    ActionRunner runner;
    Exception failureCause = null;
    List<String> responseMessage;
    private final static ParsedResponseMessage UNKNOWN_RESPONSE = 
            new ParsedResponseMessage(RESULT_TYPE.WARN, ApplicationState.getMessage(COULD_NOT_DETECT_RESPONSE_STATUS));

    public ActionResult(ActionRunner runner) {
        this.runner = runner;
        reportRow().add(new SimpleStringProperty(runner.getAction().getName()));
        reportRow().add(new SimpleStringProperty(""));
        reportRow().add(new SimpleIntegerProperty(0));
        reportRow().add(new SimpleStringProperty(""));
        reportRow().add(getDuration());
        setStatus(NOT_STARTED, 0, "");
    }
    
    public ActionResult(String name, Exception ex) {
        reportRow().add(new SimpleStringProperty(name));
        reportRow().add(new SimpleStringProperty("Failure"));
        reportRow().add(new SimpleIntegerProperty(0));
        reportRow().add(new SimpleStringProperty(""));
        reportRow().add(getDuration());
        setException(ex);
    }   

    private void setStatus(String key, int responseCode, String statusMessage) {
        Platform.runLater(() -> {
            ((StringProperty) reportRow().get(1)).set(ApplicationState.getMessage(key));
            ((IntegerProperty) reportRow().get(2)).set(responseCode);
            ((StringProperty) reportRow().get(3)).set(statusMessage);
        });
    }

    public void setException(Exception ex) {
        percentSuccess().unbind();
        percentSuccess().set(0);
        percentComplete().unbind();
        percentComplete().set(1.0);
        failureCause = ex;
        setStatus(COMPLETED_UNSUCCESSFUL, -1, ex.getMessage());
    }

    @Override
    public void updateComputations() {
        // Do nothing, this action either worked or it didn't.
    }
    
    public void processHttpResponse(CloseableHttpResponse httpResponse, ResultType resultType) throws IOException {
        StatusLine status = httpResponse.getStatusLine();
        String statusKey = COMPLETED_SUCCESSFUL;
        boolean successfulResponseCode = false;
        if (status.getStatusCode() >= 200 && status.getStatusCode() < 400) {
            successfulResponseCode = true;
        } else {
            statusKey = COMPLETED_UNSUCCESSFUL;
        }
        String resultMessage = "";
        if (resultType == ResultType.HTML) {
            ParsedResponseMessage message = extractHtmlMessage(httpResponse).orElse(UNKNOWN_RESPONSE);
            if (message.type == RESULT_TYPE.FAIL) {
                successfulResponseCode = false;
                statusKey = COMPLETED_UNSUCCESSFUL;
            }
            resultMessage = status.getReasonPhrase() + " / " + message.message;
        } else {
            resultMessage = status.getReasonPhrase();
        }
        percentSuccess().unbind();
        percentSuccess().set(successfulResponseCode ? 1 : 0);
        invalidateBindings();
        setStatus(statusKey, status.getStatusCode(), resultMessage);
    }

    private Optional<ParsedResponseMessage> extractHtmlMessage(CloseableHttpResponse httpResponse) throws IOException {
        if (httpResponse == null || httpResponse.getEntity() == null) {
            return Optional.empty();
        }
        InputStreamReader reader = new InputStreamReader(httpResponse.getEntity().getContent());
        Stream<String> lines = new BufferedReader(reader).lines();
        if (debugMode) {
            responseMessage = lines.collect(Collectors.toList());
            lines = responseMessage.stream();
        }
        return lines.map(this::parseMessagePatterns).filter(Optional::isPresent).findFirst().orElse(Optional.empty());
    }

    private Optional<ParsedResponseMessage> parseMessagePatterns(String line) {
        for (RESULT_TYPE resultType : RESULT_TYPE.values()) {
            for (Pattern p : resultType.patterns) {
                Matcher m = p.matcher(line);
                if (m.matches()) {
                    return Optional.of(new ParsedResponseMessage(resultType, m.group(1)));
                }
            }
        }
        return Optional.empty();
    }

    public void updateProgress(double d) {
        Platform.runLater(() -> {
            percentComplete().set(d);
        });
    }
    
    @Override
    public String toHtml(int level) {
        StringBuilder sb = new StringBuilder();
        sb.append("<tr>");
        reportRow().forEach(value -> sb.append("<td>").append(String.valueOf(value.getValue())).append("</td>"));

        sb.append("<td><pre>");
        if (responseMessage != null) {
            responseMessage.stream().map(str -> str.replaceAll("<", "&lt;") + "\n").forEach(sb::append);
        }
        sb.append("</pre></td>");
        sb.append("</tr>");
        return sb.toString();
    }
}