/*
 * The initial contribution was derived from the reference implementation
 * developed by Sun in consultation with the Groovy community. The reference
 * implementation had the following license header:
 *
 *  Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 *  Use is subject to license terms.
 *
 *  Redistribution and use in source and binary forms, with or without modification, are
 *  permitted provided that the following conditions are met: Redistributions of source code
 *  must retain the above copyright notice, this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright notice, this list of
 *  conditions and the following disclaimer in the documentation and/or other materials
 *  provided with the distribution. Neither the name of the Sun Microsystems nor the names of
 *  is contributors may be used to endorse or promote products derived from this software
 *  without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 *  OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 *  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 *  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 *
 * Subsequent modifications by the Groovy community have been done under the Apache License v2:
 *
 *  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.codehaus.groovy.jsr223;

import groovy.lang.GroovySystem;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * A factory class conforming to JSR-223 which is used to instantiate
 * Groovy <code>ScriptEngines</code> and also exposes metadata describing
 * Groovy's engine class.
 *
 * Adapted from original by Mike Grogan and A. Sundararajan
 */
public class GroovyScriptEngineFactory implements ScriptEngineFactory {

    private static final String VERSION = "2.0";

    private static final String SHORT_NAME = "groovy";

    private static final String LANGUAGE_NAME = "Groovy";

    /**
     * Returns the display name of this script engine implementation.
     *
     * @return the engine name reported through the JSR-223 API
     */
    @Override
    public String getEngineName() {
        return "Groovy Scripting Engine";
    }

    /**
     * Note that the scripting.dev.java.net engine had this backwards.
     * The engine version refers to this engine implementation.
     * Whereas language version refers to the groovy implementation
     * (which is obtained from the runtime).
     */
    @Override
    public String getEngineVersion() {
        return VERSION;
    }

    /**
     * This is also different from scripting.dev.java.net which used an
     * initial lowercase.  But these are proper names and should be capitalized.
     */
    @Override
    public String getLanguageName() {
        return LANGUAGE_NAME;
    }

    /**
     * Returns the version of the Groovy language available at runtime.
     *
     * @return the runtime Groovy version
     */
    @Override
    public String getLanguageVersion() {
        return GroovySystem.getVersion();
    }

    /**
     * Returns the file extensions recognized by this engine.
     *
     * @return an immutable list of supported script file extensions
     */
    @Override
    public List<String> getExtensions() {
        return EXTENSIONS;
    }

    /**
     * Returns the MIME types associated with Groovy scripts.
     *
     * @return an immutable list of supported MIME types
     */
    @Override
    public List<String> getMimeTypes() {
        return MIME_TYPES;
    }

    /**
     * Returns the short and display names accepted when locating this engine.
     *
     * @return an immutable list of supported engine names
     */
    @Override
    public List<String> getNames() {
        return NAMES;
    }

    /**
     * Resolves a standard JSR-223 metadata key for this engine.
     *
     * @param key the metadata key to resolve
     * @return the metadata value associated with {@code key}
     * @throws IllegalArgumentException if {@code key} is not supported by this factory
     */
    @Override
    public Object getParameter(String key) {

        if (ScriptEngine.NAME.equals(key)) {
            return SHORT_NAME;
        } else if (ScriptEngine.ENGINE.equals(key)) {
            return getEngineName();
        } else if (ScriptEngine.ENGINE_VERSION.equals(key)) {
            return VERSION;
        } else if (ScriptEngine.LANGUAGE.equals(key)) {
            return LANGUAGE_NAME;
        } else if (ScriptEngine.LANGUAGE_VERSION.equals(key)) {
            return GroovySystem.getVersion();
        } else if ("THREADING".equals(key)) {
            return "MULTITHREADED";
        } else {
            throw new IllegalArgumentException("Invalid key");
        }

    }

    /**
     * Creates a new Groovy script engine backed by this factory.
     *
     * @return a fresh {@link GroovyScriptEngineImpl} instance
     */
    @Override
    public ScriptEngine getScriptEngine() {
        return new GroovyScriptEngineImpl(this);
    }

    /**
     * Formats a Groovy method call using the supplied receiver, method name, and arguments.
     *
     * @param obj the expression identifying the receiver object
     * @param method the method name to call
     * @param args the argument expressions to include in the invocation
     * @return a Groovy expression that invokes {@code method} on {@code obj}
     */
    @Override
    public String getMethodCallSyntax(String obj, String method,
                                      String... args) {

        StringBuilder ret = new StringBuilder(obj + "." + method + "(");
        int len = args.length;
        if (len == 0) {
            ret.append(")");
            return ret.toString();
        }

        for (int i = 0; i < len; i++) {
            ret.append(args[i]);
            if (i != len - 1) {
                ret.append(",");
            } else {
                ret.append(")");
            }
        }
        return ret.toString();
    }

    /**
     * Produces a Groovy statement that prints the supplied text.
     * <p>
     * The text is data, so it is rendered as a string literal with every character escaped
     * that would otherwise change what the statement means: the quote and backslash that
     * would end or re-escape the literal, the dollar that would make it an interpolating
     * {@link groovy.lang.GString}, and the line terminators that would end the line. Other
     * control characters are legal inside a Groovy string literal and are emitted as they
     * are.
     *
     * @param toDisplay the text to render
     * @return a {@code println} statement which displays the text verbatim
     */
    @Override
    public String getOutputStatement(String toDisplay) {
        StringBuilder buf = new StringBuilder();
        buf.append("println(\"");
        int len = toDisplay.length();
        for (int i = 0; i < len; i++) {
            char ch = toDisplay.charAt(i);
            switch (ch) {
                case '"':
                    buf.append("\\\"");
                    break;
                case '\\':
                    buf.append("\\\\");
                    break;
                case '$':
                    buf.append("\\$");
                    break;
                case '\n':
                    buf.append("\\n");
                    break;
                case '\r':
                    buf.append("\\r");
                    break;
                default:
                    buf.append(ch);
                    break;
            }
        }
        buf.append("\")");
        return buf.toString();
    }

    /**
     * Joins multiple Groovy statements into a single program.
     *
     * @param statements the statements to concatenate in source order
     * @return a newline-delimited Groovy program
     */
    @Override
    public String getProgram(String... statements) {
        StringBuilder ret = new StringBuilder();
        for (String statement : statements) {
            ret.append(statement).append('\n');
        }
        return ret.toString();
    }

    private static final List<String> NAMES;
    private static final List<String> EXTENSIONS;
    private static final List<String> MIME_TYPES;

    static {
        List<String> n;
        NAMES = List.of(SHORT_NAME, LANGUAGE_NAME);

        n = new ArrayList<String>(1);
        n.add("groovy");
        EXTENSIONS = Collections.unmodifiableList(n);

        n = new ArrayList<String>(1);
        n.add("application/x-groovy");
        MIME_TYPES = Collections.unmodifiableList(n);
    }
}
