/*
 * Copyright (c) 2010, 2013, 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.nashorn.internal.tools.nasgen;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import jdk.internal.org.objectweb.asm.Type;
import jdk.nashorn.internal.objects.annotations.Constructor;
import jdk.nashorn.internal.objects.annotations.Function;
import jdk.nashorn.internal.objects.annotations.Getter;
import jdk.nashorn.internal.objects.annotations.Property;
import jdk.nashorn.internal.objects.annotations.ScriptClass;
import jdk.nashorn.internal.objects.annotations.Setter;
import jdk.nashorn.internal.objects.annotations.SpecializedFunction;
import jdk.nashorn.internal.objects.annotations.SpecializedFunction.LinkLogic;
import jdk.nashorn.internal.objects.annotations.Where;
import jdk.nashorn.internal.tools.nasgen.MemberInfo.Kind;

/**
 * All annotation information from a class that is annotated with
 * the annotation com.sun.oracle.objects.annotations.ScriptClass.
 *
 */
public final class ScriptClassInfo {
    // descriptors for various annotations
    static final String SCRIPT_CLASS_ANNO_DESC  = Type.getDescriptor(ScriptClass.class);
    static final String CONSTRUCTOR_ANNO_DESC   = Type.getDescriptor(Constructor.class);
    static final String FUNCTION_ANNO_DESC      = Type.getDescriptor(Function.class);
    static final String GETTER_ANNO_DESC        = Type.getDescriptor(Getter.class);
    static final String SETTER_ANNO_DESC        = Type.getDescriptor(Setter.class);
    static final String PROPERTY_ANNO_DESC      = Type.getDescriptor(Property.class);
    static final String WHERE_ENUM_DESC         = Type.getDescriptor(Where.class);
    static final String LINK_LOGIC_DESC         = Type.getDescriptor(LinkLogic.class);
    static final String SPECIALIZED_FUNCTION    = Type.getDescriptor(SpecializedFunction.class);

    static final Map<String, Kind> annotations = new HashMap<>();

    static {
        annotations.put(SCRIPT_CLASS_ANNO_DESC, Kind.SCRIPT_CLASS);
        annotations.put(FUNCTION_ANNO_DESC, Kind.FUNCTION);
        annotations.put(CONSTRUCTOR_ANNO_DESC, Kind.CONSTRUCTOR);
        annotations.put(GETTER_ANNO_DESC, Kind.GETTER);
        annotations.put(SETTER_ANNO_DESC, Kind.SETTER);
        annotations.put(PROPERTY_ANNO_DESC, Kind.PROPERTY);
        annotations.put(SPECIALIZED_FUNCTION, Kind.SPECIALIZED_FUNCTION);
    }

    // name of the script class
    private String name;
    // member info for script properties
    private List<MemberInfo> members = Collections.emptyList();
    // java class name that is annotated with @ScriptClass
    private String javaName;

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(final String name) {
        this.name = name;
    }

    /**
     * @return the members
     */
    public List<MemberInfo> getMembers() {
        return Collections.unmodifiableList(members);
    }

    /**
     * @param members the members to set
     */
    public void setMembers(final List<MemberInfo> members) {
        this.members = members;
    }

    MemberInfo getConstructor() {
        for (final MemberInfo memInfo : members) {
            if (memInfo.getKind() == Kind.CONSTRUCTOR) {
                return memInfo;
            }
        }
        return null;
    }

    List<MemberInfo> getSpecializedConstructors() {
        final List<MemberInfo> res = new LinkedList<>();
        for (final MemberInfo memInfo : members) {
            if (memInfo.isSpecializedConstructor()) {
                assert memInfo.getKind() == Kind.SPECIALIZED_FUNCTION;
                res.add(memInfo);
            }
        }
        return Collections.unmodifiableList(res);
    }

    boolean isConstructorNeeded() {
        // Constructor class generation is needed if we one or
        // more constructor properties are defined or @Constructor
        // is defined in the class.
        for (final MemberInfo memInfo : members) {
            if (memInfo.getKind() == Kind.CONSTRUCTOR ||
                memInfo.getWhere() == Where.CONSTRUCTOR) {
                return true;
            }
        }
        return false;
    }

    boolean isPrototypeNeeded() {
        // Prototype class generation is needed if we have at least one
        // prototype property or @Constructor defined in the class.
        for (final MemberInfo memInfo : members) {
            if (memInfo.getWhere() == Where.PROTOTYPE || memInfo.isConstructor()) {
                return true;
            }
        }
        return false;
    }

    int getPrototypeMemberCount() {
        int count = 0;
        for (final MemberInfo memInfo : members) {
            switch (memInfo.getKind()) {
                case SETTER:
                case SPECIALIZED_FUNCTION:
                    // SETTER was counted when GETTER was encountered.
                    // SPECIALIZED_FUNCTION was counted as FUNCTION already.
                    continue;
            }

            if (memInfo.getWhere() == Where.PROTOTYPE) {
                count++;
            }
        }
        return count;
    }

    int getConstructorMemberCount() {
        int count = 0;
        for (final MemberInfo memInfo : members) {
            switch (memInfo.getKind()) {
                case CONSTRUCTOR:
                case SETTER:
                case SPECIALIZED_FUNCTION:
                    // SETTER was counted when GETTER was encountered.
                    // Constructor and constructor SpecializedFunctions
                    // are not added as members and so not counted.
                    continue;
            }

            if (memInfo.getWhere() == Where.CONSTRUCTOR) {
                count++;
            }
        }
        return count;
    }

    int getInstancePropertyCount() {
        int count = 0;
        for (final MemberInfo memInfo : members) {
            switch (memInfo.getKind()) {
                case SETTER:
                case SPECIALIZED_FUNCTION:
                    // SETTER was counted when GETTER was encountered.
                    // SPECIALIZED_FUNCTION was counted as FUNCTION already.
                    continue;
            }

            if (memInfo.getWhere() == Where.INSTANCE) {
                count++;
            }
        }
        return count;
    }

    MemberInfo find(final String findJavaName, final String findJavaDesc, final int findAccess) {
        for (final MemberInfo memInfo : members) {
            if (memInfo.getJavaName().equals(findJavaName) &&
                memInfo.getJavaDesc().equals(findJavaDesc) &&
                memInfo.getJavaAccess() == findAccess) {
                return memInfo;
            }
        }
        return null;
    }

    List<MemberInfo> findSpecializations(final String methodName) {
        final List<MemberInfo> res = new LinkedList<>();
        for (final MemberInfo memInfo : members) {
            if (memInfo.getName().equals(methodName) &&
                memInfo.getKind() == Kind.SPECIALIZED_FUNCTION) {
                res.add(memInfo);
            }
        }
        return Collections.unmodifiableList(res);
    }

    MemberInfo findSetter(final MemberInfo getter) {
        assert getter.getKind() == Kind.GETTER : "getter expected";
        final String getterName = getter.getName();
        final Where getterWhere = getter.getWhere();
        for (final MemberInfo memInfo : members) {
            if (memInfo.getKind() == Kind.SETTER &&
                getterName.equals(memInfo.getName()) &&
                getterWhere == memInfo.getWhere()) {
                return memInfo;
            }
        }
        return null;
    }

    /**
     * @return the javaName
     */
    public String getJavaName() {
        return javaName;
    }

    /**
     * @param javaName the javaName to set
     */
    void setJavaName(final String javaName) {
        this.javaName = javaName;
    }

    String getConstructorClassName() {
        return getJavaName() + StringConstants.CONSTRUCTOR_SUFFIX;
    }

    String getPrototypeClassName() {
        return getJavaName() + StringConstants.PROTOTYPE_SUFFIX;
    }

    void verify() {
        boolean constructorSeen = false;
        for (final MemberInfo memInfo : getMembers()) {
            if (memInfo.isConstructor()) {
                if (constructorSeen) {
                    error("more than @Constructor method");
                }
                constructorSeen = true;
            }
            try {
                memInfo.verify();
            } catch (final Exception e) {
                error(e.getMessage());
            }
        }
    }

    private void error(final String msg) throws RuntimeException {
        throw new RuntimeException(javaName + " : " + msg);
    }
}
