/*
 * Copyright (c) 2011, 2012, 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 com.apple.internal.jobjc.generator.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.apple.internal.jobjc.generator.RestrictedKeywords;
import com.apple.internal.jobjc.generator.classes.JObjCClassClassFile;
import com.apple.internal.jobjc.generator.classes.JObjCClassFile;
import com.apple.internal.jobjc.generator.classes.OutputFile;
import com.apple.jobjc.MacOSXFramework;
import com.apple.jobjc.SuperClassExtractor;
import com.apple.jobjc.UnsafeRuntimeAccess;
import com.apple.jobjc.NSClass.NSClassNotFoundException;
import com.apple.jobjc.SEL;

public class Clazz extends Element<Framework> implements OutputFileGenerator {
    private final Map<String, Method> instanceMethodsByName = new HashMap<String, Method>();
    private final Map<String, Method> classMethodsByName = new HashMap<String, Method>();

    public final List<Method> classMethods;
    public final List<Method> instanceMethods;

    public final List<Clazz> subClassers = new ArrayList<Clazz>(0);
    public Clazz superClass;

    public Clazz(String name, List<Method> classMethods, List<Method> instanceMethods, Clazz superClass, Framework parent){
        super(name, parent);
        this.classMethods = classMethods;
        this.instanceMethods = instanceMethods;
        this.superClass = superClass;
    }

    public Clazz(final Node classNode, final Framework parent) {
        super(classNode, parent);
        this.classMethods = new ArrayList<Method>();
        this.instanceMethods = new ArrayList<Method>();

        final NodeList methodNodes = classNode.getChildNodes();
        for (int i = 0; i < methodNodes.getLength(); i++) {
            final Node node = methodNodes.item(i);
            if (!"method".equals(node.getLocalName())) continue;

            final String selName = Element.getAttr(node, "selector");
            if(selName == null || !SEL.validName(selName)){
                System.err.format("Warning: Discarding method %1$s:%2$s:%3$s"
                        + " -- Invalid selector name. Verify.\n",
                        parent.name, name, selName);
                continue;
            }

            final Method method = new Method(node, parent);
            if (method.isClassMethod) {
                classMethods.add(method);
            } else {
                instanceMethods.add(method);
            }
        }
    }

    public String getPackage() {
        return parent.pkg;
    }

    public String getFullPath(){
        return parent.pkg + "." + name;
    }

    @Override
    public String toString() {
        return super.toString() + " " + classMethods + " " + instanceMethods;
    }

    public boolean doesActuallyExist(){
        try{
            UnsafeRuntimeAccess.getNSClass(parent.load(), name);
        }catch(NSClassNotFoundException x){
            return false;
        }
        return true;
    }

    void resolveSuperClass(final MacOSXFramework nativeFramework, final Map<String, Clazz> allClasses) throws Throwable {
        superClass = SuperClassExtractor.getSuperClassFor(name, nativeFramework, allClasses);
    }

    public void disambiguateMethods() {
        disambiguateMethods(instanceMethods, instanceMethodsByName);
        disambiguateMethods(classMethods, classMethodsByName);
    }

    private void disambiguateMethods(final List<Method> methods, final Map<String, Method> methodMap) {
        final Set<String> existingMethodNames = RestrictedKeywords.getNewRestrictedSet();
        for (final Method method : methods) {
            method.disambiguateNameAndArgs(this, existingMethodNames);
            methodMap.put(method.javaName, method);
        }
    }

    public void generateClasses(final List<OutputFile> generatedClassFiles) {
        generatedClassFiles.add(new JObjCClassClassFile(this));
        generatedClassFiles.add(new JObjCClassFile(this));
    }

    Method getParentMethodMatchingName(final String methodName) {
        if(superClass == null) return null;
        Method m = superClass.getMethodNamed(methodName);
        if(m != null) return m;
        return superClass.getParentMethodMatchingName(methodName);
    }

    private Method getMethodNamed(final String methodName) {
        final Method instanceMethod = instanceMethodsByName.get(methodName);
        if (instanceMethod != null) return instanceMethod;

        final Method classMethod = classMethodsByName.get(methodName);
        if (classMethod != null) return classMethod;

        return null;
    }

    int compareTo(Clazz o) {
      return toString().compareTo(o.toString());
    }
}
