/*
 * Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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 General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.jshell;

import jdk.jshell.Snippet.Kind;
import jdk.jshell.Snippet.SubKind;

/**
 * The Key is unique for a given signature.  Used internal to the implementation
 * to group signature matched Snippets.  Snippet kinds without a signature
 * (for example expressions) each have their own UniqueKey.
 * @author Robert Field
 */
abstract class Key {

    /**
     * The unique index for this Key
     */
    private final int index;

    /**
     * The JShell corresponding to this Key
     */
    private final JShell state;

    Key(JShell state) {
        this.index = state.nextKeyIndex();
        this.state = state;
    }

    /**
     * The unique numeric identifier for the snippet.  Snippets which replace or
     * redefine existing snippets take on the same key index as the replaced or
     * redefined snippet.
     * The index values are monotonically increasing integers.
     * @return the Key index
     */
    int index() { return index; }

    /**
     * The kind for the key.  Indicates the subinterface of Key.
     * @return the Kind of the Key
     */
    abstract Kind kind();

    /**
     * For foreign key testing.
     */
    JShell state() { return state; }

    /**
     * Grouping for snippets which persist and influence future code.
     * They are keyed off at least the name.  They may be Modified/Replaced
     * with new input.
     */
    abstract static class PersistentKey extends Key {

        private final String name;

        PersistentKey(JShell state, String name) {
            super(state);
            this.name = name;
        }
        /**
         * Name of the snippet.
         *
         * @return the name of the snippet.
         */
        String name() { return name; }
    }

    /**
     * Grouping for snippets which reference declarations
     */
    abstract static class DeclarationKey extends PersistentKey {

        DeclarationKey(JShell state, String name) {
            super(state, name);
        }
    }

    /**
     * Key for a class definition Keys of TYPE_DECL. Keyed off class name.
     */
    static class TypeDeclKey extends DeclarationKey {

        TypeDeclKey(JShell state, String name) {
            super(state, name);
        }

        @Override
        Kind kind() { return Kind.TYPE_DECL; }

        @Override
        public String toString() { return "ClassKey(" + name() + ")#" + index(); }
    }

    /**
     * Key for a method definition Keys of METHOD. Keyed off method name and
     * parameter types.
     */
    static class MethodKey extends DeclarationKey {

        private final String parameterTypes;

        MethodKey(JShell state, String name, String parameterTypes) {
            super(state, name);
            this.parameterTypes = parameterTypes;
        }

        @Override
        Kind kind() { return Kind.METHOD; }

        /**
         * The parameter types of the method. Because of overloading of methods,
         * the parameter types are part of the key.
         *
         * @return a String representation of the parameter types of the method.
         */
        String parameterTypes() { return parameterTypes; }


        @Override
        public String toString() { return "MethodKey(" + name() +
                "(" + parameterTypes() + "))#" + index(); }
    }

    /**
     * Key for a variable definition Keys of VARIABLE. Keyed off variable
     * name.
     */
    static class VarKey extends DeclarationKey {

        VarKey(JShell state, String name) {
            super(state, name);
        }

        @Override
        public Kind kind() { return Kind.VAR; }

        @Override
        public String toString() { return "VariableKey(" + name() + ")#" + index(); }
    }

    /**
     * Key for an import. Keys of IMPORT. Keyed off import text and whether
     * import is static.
     */
    static class ImportKey extends PersistentKey {

        private final SubKind snippetKind;

        ImportKey(JShell state, String name,SubKind snippetKind) {
            super(state, name);
            this.snippetKind = snippetKind;
        }

        @Override
        public Kind kind() { return Kind.IMPORT; }

        /**
         * Which kind of import.
         *
         * @return the appropriate SubKind.
         */
        SubKind snippetKind() {
            return snippetKind;
        }


        @Override
        public String toString() { return "ImportKey(" + name() + "," +
                snippetKind + ")#" + index(); }
    }

    /**
     * Grouping for snippets which are the key for only one input -- even if the
     * exactly same entry is made again. The referenced snippets are thus
     * unmodifiable.
     */
    abstract static class UniqueKey extends Key {

        UniqueKey(JShell state) {
            super(state);
        }
    }

    /**
     * Key for a statement snippet. Keys of STATEMENT. Uniquely keyed, see
     * UniqueKey.
     */
    static class StatementKey extends UniqueKey {

        StatementKey(JShell state) {
            super(state);
        }


        @Override
        public Kind kind() {
            return Kind.STATEMENT;
        }

        @Override
        public String toString() { return "StatementKey#" + index(); }
    }

    /**
     * Key for an expression. Keys of EXPRESSION. Uniquely keyed, see
     * UniqueKey.
     */
    static class ExpressionKey extends UniqueKey {

        private final String name;
        private final String typeName;

        ExpressionKey(JShell state, String name, String typeName) {
            super(state);
            this.name = name;
            this.typeName = typeName;
        }

        @Override
        public Kind kind() { return Kind.EXPRESSION; }


        /**
         * Variable name which is the value of the expression. Since the
         * expression is either just an identifier which is a variable or it is
         * an assignment to a variable, there is always a variable which is the
         * subject of the expression. All other expression snippets become
         * temporary variables referenced by a VariableKey.
         *
         * @return the name of the variable which is the subject of the
         * expression.
         */
        String name() { return name; }

        /**
         * Type of the expression
         *
         * @return String representation of the type of the expression.
         */
        String typeName() { return typeName; }


        @Override
        public String toString() { return "ExpressionKey(" + name() + ")#" + index(); }
    }

    /**
     * Key for an erroneous snippet. Keys of ERRONEOUS. Uniquely keyed, see
     * UniqueKey.
     */
    static class ErroneousKey extends UniqueKey {

        ErroneousKey(JShell state) {
            super(state);
        }

        @Override
        Kind kind() { return Kind.ERRONEOUS; }

        @Override
        public String toString() { return "ErroneousKey#" + index(); }
    }
}
