/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.camel.component.mustache;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;

import com.github.mustachejava.DefaultMustacheFactory;
import com.github.mustachejava.Mustache;
import com.github.mustachejava.MustacheFactory;
import org.apache.camel.Component;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.Message;
import org.apache.camel.component.ResourceEndpoint;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.spi.UriParam;
import org.apache.camel.util.ExchangeHelper;

import static org.apache.camel.component.mustache.MustacheConstants.MUSTACHE_ENDPOINT_URI_PREFIX;
import static org.apache.camel.component.mustache.MustacheConstants.MUSTACHE_RESOURCE_URI;
import static org.apache.camel.component.mustache.MustacheConstants.MUSTACHE_TEMPLATE;

/**
 * Transforms the message using a Mustache template.
 */
@UriEndpoint(scheme = "mustache", title = "Mustache", syntax = "mustache:resourceUri", producerOnly = true, label = "transformation")
public class MustacheEndpoint extends ResourceEndpoint {

    private MustacheFactory mustacheFactory;
    private Mustache mustache;
    @UriParam
    private String encoding;
    @UriParam(defaultValue = "{{")
    private String startDelimiter;
    @UriParam(defaultValue = "}}")
    private String endDelimiter;

    public MustacheEndpoint() {
    }

    public MustacheEndpoint(String endpointUri, Component component, String resourceUri) {
        super(endpointUri, component, resourceUri);
    }

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public ExchangePattern getExchangePattern() {
        return ExchangePattern.InOut;
    }

    @Override
    protected String createEndpointUri() {
        return MUSTACHE_ENDPOINT_URI_PREFIX + getResourceUri();
    }

    @Override
    public void clearContentCache() {
        this.mustache = null;
        super.clearContentCache();
    }

    @Override
    protected void onExchange(Exchange exchange) throws Exception {
        String newResourceUri = exchange.getIn().getHeader(MUSTACHE_RESOURCE_URI, String.class);
        if (newResourceUri == null) {
            // Get Mustache
            String newTemplate = exchange.getIn().getHeader(MUSTACHE_TEMPLATE, String.class);
            Mustache newMustache;
            if (newTemplate == null) {
                newMustache = getOrCreateMustache();
            } else {
                newMustache = createMustache(new StringReader(newTemplate), "mustache:temp#" + newTemplate.hashCode());
                exchange.getIn().removeHeader(MUSTACHE_TEMPLATE);
            }

            // Execute Mustache
            Map<String, Object> variableMap = ExchangeHelper.createVariableMap(exchange);
            StringWriter writer = new StringWriter();
            newMustache.execute(writer, variableMap);
            writer.flush();

            // Fill out message
            Message out = exchange.getOut();
            out.setBody(writer.toString());
            out.setHeaders(exchange.getIn().getHeaders());
            out.setAttachments(exchange.getIn().getAttachments());
        } else {
            exchange.getIn().removeHeader(MustacheConstants.MUSTACHE_RESOURCE_URI);
            MustacheEndpoint newEndpoint = getCamelContext().getEndpoint(MUSTACHE_ENDPOINT_URI_PREFIX + newResourceUri, MustacheEndpoint.class);
            newEndpoint.onExchange(exchange);
        }
    }

    /**
     * Read and compile a Mustache template
     *
     * @param resourceReader Reader used to get template
     * @param resourceUri    Template Id
     * @return Template
     */
    private Mustache createMustache(Reader resourceReader, String resourceUri) throws IOException {
        ClassLoader oldcl = Thread.currentThread().getContextClassLoader();
        try {
            ClassLoader apcl = getCamelContext().getApplicationContextClassLoader();
            if (apcl != null) {
                Thread.currentThread().setContextClassLoader(apcl);
            }
            Mustache newMustache;
            if (startDelimiter != null && endDelimiter != null && mustacheFactory instanceof DefaultMustacheFactory) {
                DefaultMustacheFactory defaultMustacheFactory = (DefaultMustacheFactory) mustacheFactory;
                newMustache = defaultMustacheFactory.compile(resourceReader, resourceUri, startDelimiter, endDelimiter);
            } else {
                newMustache = mustacheFactory.compile(resourceReader, resourceUri);
            }
            return newMustache;
        } finally {
            resourceReader.close();
            if (oldcl != null) {
                Thread.currentThread().setContextClassLoader(oldcl);
            }
        }
    }

    private Mustache getOrCreateMustache() throws IOException {
        if (mustache == null) {
            mustache = createMustache(getResourceAsReader(), getResourceUri());
        }
        return mustache;
    }

    @Override
    public String getResourceUri() {
        // do not have leading slash as mustache cannot find the resource, as that entails classpath root
        String uri = super.getResourceUri();
        if (uri != null && (uri.startsWith("/") || uri.startsWith("\\"))) {
            return uri.substring(1);
        } else {
            return uri;
        }
    }

    public MustacheFactory getMustacheFactory() {
        return mustacheFactory;
    }

    /**
     * To use a custom {@link MustacheFactory}
     */
    public void setMustacheFactory(MustacheFactory mustacheFactory) {
        this.mustacheFactory = mustacheFactory;
    }

    public String getEncoding() {
        return encoding;
    }

    /**
     * Character encoding of the resource content.
     */
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    private Reader getResourceAsReader() throws IOException {
        return encoding == null
                ? new InputStreamReader(getResourceAsInputStream())
                : new InputStreamReader(getResourceAsInputStream(), encoding);
    }

    public String getStartDelimiter() {
        return startDelimiter;
    }

    /**
     * Characters used to mark template code beginning.
     */
    public void setStartDelimiter(String startDelimiter) {
        this.startDelimiter = startDelimiter;
    }

    public String getEndDelimiter() {
        return endDelimiter;
    }

    /**
     * Characters used to mark template code end.
     */
    public void setEndDelimiter(String endDelimiter) {
        this.endDelimiter = endDelimiter;
    }

}
