/**
 * This file is part of the Meeds project (https://meeds.io/).
 *
 * Copyright (C) 2020 - 2025 Meeds Association contact@meeds.io
 *
 * This program 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 3 of the License, or (at your option) any later version.
 * This program 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.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */
package org.exoplatform.groovyscript;

import java.io.IOException;
import java.io.Writer;
import java.util.Locale;
import java.util.Map;

import org.codehaus.groovy.runtime.InvokerHelper;
import org.exoplatform.commons.utils.OutputStreamPrinter;

import groovy.lang.Binding;

/**
 * A wrapper for a Groovy script and its meta data.
 *
 */
public class GroovyScript {

    /** . */
    private final String templateId;

    /** . */
    private final String groovyText;

    /** . */
    private final Class<?> scriptClass;

    /** . */
    private final Map<Integer, TextItem> lineTable;

    public GroovyScript(String templateId, String groovyText, Class<?> scriptClass, Map<Integer, TextItem> lineTable) {
        this.templateId = templateId;
        this.groovyText = groovyText;
        this.scriptClass = scriptClass;
        this.lineTable = lineTable;
    }

    public String getTemplateId() {
        return templateId;
    }

    public String getGroovyText() {
        return groovyText;
    }

    public Class<?> getScriptClass() {
        return scriptClass;
    }

    /**
     * Renders the script with the provided context and locale to the specified writer.
     *
     * @param context the context
     * @param writer the writer
     * @param locale the locale
     * @throws IOException
     * @throws TemplateRuntimeException
     */
    public void render(Map context, Writer writer, Locale locale) throws IOException, TemplateRuntimeException {
        Binding binding = context != null ? new Binding(context) : new Binding();

        //
        GroovyPrinter printer;
        if (writer instanceof OutputStreamPrinter) {
            printer = new OutputStreamWriterGroovyPrinter((OutputStreamPrinter) writer);
        } else {
            printer = new WriterGroovyPrinter(writer);
        }

        //
        printer.setLocale(locale);

        //
        BaseScript script = (BaseScript) InvokerHelper.createScript(scriptClass, binding);
        script.printer = printer;

        //
        try {
            script.run();
        } catch (Exception e) {
            if (e instanceof IOException) {
                throw (IOException) e;
            } else {
                throw buildRuntimeException(e);
            }
        } catch (Throwable e) {
            if (e instanceof Error) {
                throw ((Error) e);
            }
            throw buildRuntimeException(e);
        }

        //
        script.flush();
    }

    private TemplateRuntimeException buildRuntimeException(Throwable t) {
        StackTraceElement[] trace = t.getStackTrace();

        //
        TextItem firstItem = null;

        // Try to find the groovy script lines
        for (int i = 0; i < trace.length; i++) {
            StackTraceElement element = trace[i];
            if (element.getClassName().equals(scriptClass.getName())) {
                int lineNumber = element.getLineNumber();
                TextItem item = lineTable.get(lineNumber);
                int templateLineNumber;
                if (item != null) {
                    templateLineNumber = item.getPosition().getLine();
                    if (firstItem == null) {
                        firstItem = item;
                    }
                } else {
                    templateLineNumber = -1;
                }
                element = new StackTraceElement(element.getClassName(), element.getMethodName(), element.getFileName(),
                        templateLineNumber);
                trace[i] = element;
            }
        }

        //
        t.setStackTrace(trace);

        //
        return new TemplateRuntimeException(templateId, firstItem, t);
    }
}
