/*
 * 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.clerezza.platform.typerendering.scalaserverpages;

import java.util.logging.Level;
import org.apache.clerezza.platform.typerendering.CallbackRenderer;
import org.apache.clerezza.platform.typerendering.TypeRenderlet;
import org.apache.clerezza.commons.rdf.IRI;
import org.apache.clerezza.rdf.utils.GraphNode;
import org.apache.clerezza.scala.scripting.CompilerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.core.MediaType;
import java.io.*;
import java.net.URL;
import java.nio.charset.Charset;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.Map;

/**
 * A TypeRenderlet delegating the actual renderlet to a compiled ScalaServerPage. On every request the
 * ScalaServerPage is checked for changes and recompiled if needed.
 */
public class SspTypeRenderlet implements TypeRenderlet {


    private static final Logger logger = LoggerFactory.getLogger(SspTypeRenderlet.class);
    private static final Charset UTF8 = Charset.forName("UTF-8");
    private static final char[] headerChars
            = getChars(SspTypeRenderlet.class.getResource("typerenderlet-header.txt"));
    private static final String lineSeparator = System.getProperty("line.separator");
    private static final char[] footerChars = (";}" + lineSeparator + "}"
            + lineSeparator + "}" + lineSeparator + "}" + lineSeparator).toCharArray();
    

    private IRI rdfType;
    private String modePattern;
    private MediaType mediaType;
    private URL sspLocation;
    private char[] lastCompiledChars;
    private TypeRenderlet lastCompiledSsp = null;
    private CompilerService scalaCompilerService;

    SspTypeRenderlet(URL sspLocation, IRI rdfType, String modePattern, MediaType mediaType,
                     CompilerService scalaCompilerService) {
        this.sspLocation = sspLocation;
        this.rdfType = rdfType;
        this.modePattern = modePattern;
        this.mediaType = mediaType;
        this.scalaCompilerService = scalaCompilerService;
    }

    @Override
    public IRI getRdfType() {
        return rdfType;
    }

    @Override
    public String getModePattern() {
        return modePattern;
    }

    @Override
    public MediaType getMediaType() {
        return mediaType;
    }

    @Override
    public void render(GraphNode node, GraphNode context,
                       Map<String, Object> sharedRenderingValues, CallbackRenderer callbackRenderer,
                       RequestProperties requestProperties, OutputStream os) throws IOException {
        TypeRenderlet compiledSsp = getCompiledSsp();
        compiledSsp.render(node, context, sharedRenderingValues, callbackRenderer, requestProperties, os);
    }

    private synchronized TypeRenderlet getCompiledSsp()  {
        char[] scriptChars = getScriptChars(sspLocation);
        if (Arrays.equals(scriptChars, lastCompiledChars)) {
            return lastCompiledSsp;
        }
        final char[][] scripts = new char[][]{scriptChars};
        Class renderletClass;
        try {
            //doing as priviledged so that no CompilePermission is needed
            renderletClass = AccessController.doPrivileged(new PrivilegedExceptionAction<Class>() {
                @Override
                public Class run() {
                    return scalaCompilerService.compile(scripts)[0];
                }
            });

        } catch (PrivilegedActionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            /*if (cause instanceof CompileErrorsException) {
                throw (CompileErrorsException) cause;
            }*/
            throw new RuntimeException(e);
        }
        TypeRenderlet compiledSsp;
        try {
            compiledSsp = (TypeRenderlet) renderletClass.newInstance();
        } catch (InstantiationException ex) {
            throw new RuntimeException(ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(ex);
        }
        lastCompiledSsp = compiledSsp;
        lastCompiledChars = scriptChars;
        return compiledSsp;
    }

    private static char[] getScriptChars(URL location) {
        try {
            final CharArrayWriter caos = new CharArrayWriter();
            //Add the scriptHeader to the beginning of the script
            caos.write(headerChars);
            //add the content
            caos.write(getChars(location));
            caos.write(footerChars);
            return caos.toCharArray();
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

    private static char[] getChars(URL location) {
        try {
            final Reader in = new InputStreamReader(location.openStream(), UTF8);
            final CharArrayWriter caos = new CharArrayWriter();
            
            final char[] buffer = new char[1024];
            int charsRead;
            while ((charsRead = in.read(buffer, 0, 1024)) != -1) {
                caos.write(buffer, 0, charsRead);
            }
            logger.debug("getting bytes for: {}", location);
            return caos.toCharArray();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return "SspTypeRenderlet for: " + sspLocation;
    }


}
