package org.cafebabepy.runtime.module;

import org.cafebabepy.annotation.DefineCafeBabePyFunction;
import org.cafebabepy.runtime.*;
import org.cafebabepy.runtime.object.PyFunctionObject;

import java.lang.reflect.Method;
import java.util.*;

import static org.cafebabepy.util.ProtocolNames.__call__;

/**
 * Created by yotchang4s on 2017/05/30.
 */
abstract class AbstractAbstractCafeBabePyAny extends AbstractPyObject {

    private enum InitializeStage {
        NONE, PRE, POST
    }

    private volatile List<PyObject> bases;

    private InitializeStage initialize;

    AbstractAbstractCafeBabePyAny(Python runtime) {
        super(runtime);

        this.initialize = InitializeStage.NONE;
    }

    @Override
    public void preInitialize() {
        if (this.initialize != InitializeStage.NONE) {
            return;
        }

        this.initialize = InitializeStage.PRE;
        defineClass();
    }

    @Override
    public void postInitialize() {
        if (this.initialize != InitializeStage.PRE) {
            return;
        }

        this.initialize = InitializeStage.POST;
        defineClassMembers();
    }

    @Override
    public List<PyObject> getBases() {
        if (this.bases == null) {
            synchronized (this) {
                if (this.bases == null) {
                    String[] baseNames = getBaseNames();
                    List<PyObject> bases = new ArrayList<>(baseNames.length);

                    for (String baseName : getBaseNames()) {
                        PyObject base = this.runtime.type(baseName).orElseThrow(() ->
                                new CafeBabePyException(
                                        "type '" + getName() + "' parent '" + baseName + "' is not found")
                        );

                        bases.add(base);
                    }

                    this.bases = Collections.unmodifiableList(Collections.synchronizedList(bases));
                }
            }
        }

        return this.bases;
    }

    abstract String[] getBaseNames();

    public abstract void defineClass();

    private void defineClassMembers() {
        Class<?> clazz = getClass();

        PyObjectScope objectScope = this.runtime.type("builtins.object")
                .orElseThrow(() -> new CafeBabePyException("'builtins.object' is not found"))
                .getScope();

        for (Class<?> c = clazz.getSuperclass(); c != Object.class; c = c.getSuperclass()) {
            defineClassMember(c, objectScope);
        }

        defineClassMember(clazz, getScope());
    }

    private void defineClassMember(Class<?> clazz, PyObjectScope scope) {
        // Check duplicate
        Set<String> defineClassMemberNamesSet = new HashSet<>();

        for (Method method : clazz.getMethods()) {
            // Same class method only
            if (clazz != method.getDeclaringClass()) {
                continue;
            }

            DefineCafeBabePyFunction defineCafeBabePyFunction = method.getAnnotation(DefineCafeBabePyFunction.class);
            if (defineCafeBabePyFunction == null) {
                continue;
            }

            if (defineClassMemberNamesSet.contains(defineCafeBabePyFunction.name())) {
                throw new CafeBabePyException(
                        "Duplicate '" + defineCafeBabePyFunction.name() + "' function");
            }

            PyFunctionJavaObject f = new PyFunctionJavaObject(
                    getRuntime(),
                    this,
                    defineCafeBabePyFunction.name(),
                    method);

            if (__call__.equals(f.getName())) {
                f.getScope().put(__call__, f);
            }

            scope.put(f.getName(), f);

            defineClassMemberNamesSet.add(defineCafeBabePyFunction.name());
        }
    }

    @Override
    public boolean isNone() {
        return false;
    }
}

@@ -172,16 +172,20 @@
 Function
+Java
 Object;%0A
@@ -2494,21 +2494,8 @@
 er(c
-, objectScope
 );%0A 
@@ -2539,20 +2539,8 @@
 lazz
-, getScope()
 );%0A 
@@ -2598,29 +2598,8 @@
 lazz
-, PyObjectScope scope
 ) %7B%0A
@@ -3653,16 +3653,21 @@
         
+this.
 scope.pu

Fix define Python function from Java class