package com.atlassian.stash.internal.web.soy.functions;

import com.atlassian.soy.renderer.SanitizationType;
import com.atlassian.soy.renderer.SanitizedString;
import com.atlassian.soy.renderer.SoyServerFunction;
import com.atlassian.stash.internal.plugin.web.fragments.ClientWebFragmentType;
import com.atlassian.stash.internal.web.fragments.*;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.CharMatcher;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.StringWriter;
import java.util.List;
import java.util.Set;

import static com.atlassian.stash.internal.web.soy.functions.SoyArgumentUtils.argCountFromTo;
import static com.atlassian.stash.internal.web.soy.functions.SoyArgumentUtils.checkType;
import static com.google.common.base.Preconditions.checkArgument;

/**
 * Require server-side web fragments into a client-side context, and log the deprecation.
 */
public class RenderServerFragmentsForClientFunction implements SoyServerFunction<SanitizedString> {

    private static final Logger log = LoggerFactory.getLogger(RenderServerFragmentsForClientFunction.class);
    private static final Set<Integer> VALID_ARG_SIZES = argCountFromTo(2, 2);

    @VisibleForTesting
    static final CharMatcher JS_SEPARATORS = CharMatcher.anyOf("\u2028\u2029");

    private final JsonFactory jsonFactory;
    private final WebFragmentSupport webFragmentSupport;

    public RenderServerFragmentsForClientFunction(WebFragmentSupport webFragmentSupport) {
        this.webFragmentSupport = webFragmentSupport;
        jsonFactory = new ObjectMapper().getJsonFactory();
    }

    @Override
    public String getName() {
        return "renderServerFragmentsForClient";
    }

    @Override
    public SanitizedString apply(Object... args) {
        checkArgument(VALID_ARG_SIZES.contains(args.length), "invalid number of arguments: %s", args.length);

        String location = checkType(args, String.class, 0);
        List<WebFragmentData> fragments = checkType(args, List.class, 1);

        if (fragments.size() == 0) {
            return new SanitizedString("");
        }

        StringBuilder sb = new StringBuilder();
        sb.append("<script>\n");

        for (WebFragmentData fragment : fragments) {
            StringWriter writer = new StringWriter();
            String addDescriptorMethod = null;
            try (JsonGenerator generator = jsonFactory.createJsonGenerator(writer)) {
                String formattedLocation = fragment.getParent() != null ?
                        webFragmentSupport.formatLocation(location, fragment.getParent()) :
                        location;

                if (fragment instanceof WebItemData) {
                    addDescriptorMethod = ClientWebFragmentType.ITEM.getAddDescriptorJsMethod();
                    writeDescriptor(generator, (WebItemData) fragment, formattedLocation);
                } else if (fragment instanceof WebSectionData) {
                    addDescriptorMethod = ClientWebFragmentType.SECTION.getAddDescriptorJsMethod();
                    writeDescriptor(generator, (WebSectionData) fragment, formattedLocation);
                } else if (fragment instanceof WebPanelData) {
                    addDescriptorMethod = ClientWebFragmentType.PANEL.getAddDescriptorJsMethod();
                    writeDescriptor(generator, (WebPanelData) fragment, formattedLocation);
                } else {
                    log.warn("Unexpected WebFragmentData type while rendering server fragments to JS.");
                    continue;
                }

            } catch (IOException e) {
                log.error("Exception while rendering server fragments to JS:", e);
            }
            if (addDescriptorMethod != null) {
                sb.append(addDescriptorMethod).append("(").append(writer.toString()).append(");\n");
            }
        }
        sb.append("</script>");
        return new SanitizedString(sb.toString(), SanitizationType.HTML);
    }

    private String escapeJs(String s) {
        return JS_SEPARATORS.replaceFrom(s, "\\n").replaceAll("/", "\\/");
    }

    private void stringField(JsonGenerator json, String name, String value) throws IOException {
        json.writeStringField(name, escapeJs(value));
    }

    private void optionalStringField(JsonGenerator json, String name, String value) throws IOException {
        if (value != null) {
            stringField(json, name, value);
        }
    }

    private void writeDefaultProps(JsonGenerator generator, WebFragmentData fragment, String location) throws IOException {
        stringField(generator, "completeModuleKey", fragment.getCompleteKey());
        stringField(generator, "key", fragment.getKey());
        stringField(generator, "moduleKey", fragment.getModuleKey());
        generator.writeObjectField("params", fragment.getParams());
        stringField(generator, "pluginKey", fragment.getPluginKey());
        stringField(generator, "location", location);
        generator.writeNumberField("weight", fragment.getWeight());
    }

    private void writeDescriptor(JsonGenerator generator, WebItemData item, String location) throws IOException {
        generator.writeStartObject();
        writeDefaultProps(generator, item, location);
        optionalStringField(generator, "url", item.getUrl());
        optionalStringField(generator, "linkText", item.getLinkText());
        optionalStringField(generator, "linkId", item.getLinkId());
        optionalStringField(generator, "styleClass", item.getStyleClass());
        optionalStringField(generator, "tooltip", item.getTooltip());
        generator.writeEndObject();
    }

    private void writeDescriptor(JsonGenerator generator, WebSectionData section, String location) throws IOException {
        generator.writeStartObject();
        writeDefaultProps(generator, section, location);
        optionalStringField(generator, "labelText", section.getLabelText());
        generator.writeEndObject();
    }

    private void writeDescriptor(JsonGenerator generator, WebPanelData panel, String location) throws IOException {
        generator.writeStartObject();
        writeDefaultProps(generator, panel, location);
        optionalStringField(generator, "html", panel.getHtml());
        generator.writeEndObject();
    }

    @Override
    public Set<Integer> validArgSizes() {
        return VALID_ARG_SIZES;
    }
}
