package com.ajjpj.amapper.javabean.propbased.accessors;

import com.ajjpj.amapper.core.compile.ACodeSnippet;
import com.ajjpj.amapper.core.tpe.AQualifier;
import com.ajjpj.amapper.javabean.JavaBeanType;
import com.ajjpj.amapper.util.AMapperReflectionHelper;

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

/**
 * @author arno
 */
public class AMethodPathBasedPropertyAccessor implements APropertyAccessor {
    private final String name;
    private final List<Step> steps;
    private final Method finalGetter;
    private final Method finalSetter;
    private final boolean isFinalStepNullSafe;
    private final boolean isDeferred;
    private final JavaBeanType<?> tpe;
    private final AQualifier sourceQualifier;
    private final AQualifier targetQualifier;

    public AMethodPathBasedPropertyAccessor(String name, List<Step> steps, Method finalGetter, Method finalSetter, boolean isFinalStepNullSafe, boolean isDeferred, JavaBeanType<?> tpe, AQualifier sourceQualifier, AQualifier targetQualifier) {
        this.name = name;
        this.steps = steps;
        this.finalGetter = finalGetter;
        this.finalSetter = finalSetter;
        this.isFinalStepNullSafe = isFinalStepNullSafe;
        this.isDeferred = isDeferred;
        this.tpe = tpe;
        this.sourceQualifier = sourceQualifier;
        this.targetQualifier = targetQualifier;
    }

    @Override public String getName() {
        return name;
    }

    @Override public JavaBeanType<?> getType() {
        return tpe;
    }

    @Override public AQualifier getSourceQualifier() {
        return sourceQualifier;
    }

    @Override public AQualifier getTargetQualifier() {
        return targetQualifier;
    }

    @Override public boolean isDeferred() {
        return isDeferred;
    }

    @Override public boolean isWritable() {
        return finalSetter != null;
    }

    @Override public Object get(Object o) throws Exception {
        Object cur = o;
        for(Step step: steps) {
            if(cur == null && step.isNullSafe()) {
                return null;
            }
            cur = AMapperReflectionHelper.invoke (step.getGetter(), cur);
        }

        if(cur == null && isFinalStepNullSafe) {
            return null;
        }
        return AMapperReflectionHelper.invoke (finalGetter, cur);
    }

    @Override
    public void set(Object o, Object newValue) throws Exception {
        Object cur = o;
        for(Step step: steps) {
            if(cur == null && step.isNullSafe()) {
                return;
            }
            cur = AMapperReflectionHelper.invoke (step.getGetter(), cur);
        }

        if(cur == null && isFinalStepNullSafe) {
            return;
        }
        AMapperReflectionHelper.invoke (finalSetter, cur, newValue);
    }

    private boolean hasNullSafeSegment() {
        for(Step step: steps) {
            if(step.isNullSafe()) {
                return true;
            }
        }
        return isFinalStepNullSafe;
    }

    @Override
    public ACodeSnippet javaCodeForGet(ACodeSnippet parent) throws Exception {
        final StringBuilder code = new StringBuilder();

        if(hasNullSafeSegment()) {
            code.append("((" + finalGetter.getReturnType().getName() + ")(new java.util.concurrent.Callable() {\n");
            code.append("public Object call() {\n");

            String prevVarName = parent.getCode();
            for(Step step: steps) {
                if(step.isNullSafe()) {
                    code.append("if(" + prevVarName + " == null) return null;\n");
                }
                final String varName = ACodeSnippet.uniqueIdentifier();
                code.append("final " + step.getGetter().getReturnType().getName() + " " + varName + " = " + prevVarName + "." + step.getGetter().getName() + "();\n");
                prevVarName = varName;
            }

            if(isFinalStepNullSafe) {
                code.append("if(" + prevVarName + " == null) return null;\n");
            }
            code.append("return " + prevVarName + "." + finalGetter.getName() + "();\n");

            code.append("}\n");
            code.append("}).call())");
        }
        else {
            code.append(parent.getCode());
            for(Step step: steps) {
                code.append("." + step.getGetter().getName() + "()");
            }

            code.append("." + finalGetter.getName() + "()");
        }

        return new ACodeSnippet(code.toString());
    }

    @Override
    public ACodeSnippet javaCodeForSet(ACodeSnippet parent, ACodeSnippet newValue) throws Exception {
        final StringBuilder code = new StringBuilder();

        if(hasNullSafeSegment()) {
            code.append("new java.util.concurrent.Callable() {\n");
            code.append("public Object call() {\n");
            String prevVarName = parent.getCode();
            for(Step step: steps) {
                if(step.isNullSafe()) {
                    code.append("if(" + prevVarName + " == null) return null;\n");
                }
                final String varName = ACodeSnippet.uniqueIdentifier();
                code.append("final " + step.getGetter().getReturnType().getName() + " " + varName + " = " + prevVarName + "." + step.getGetter().getName() + "();\n");
                prevVarName = varName;
            }

            if(isFinalStepNullSafe) {
                code.append("if(" + prevVarName + " == null) return null;\n");
            }
            code.append(prevVarName + "." + finalSetter.getName() + "(" + newValue.getCode() + ");\n");
            code.append("return null;\n");

            code.append("}\n");
            code.append("}.call()");
        }
        else {
            code.append(parent.getCode());
            for(Step step: steps) {
                code.append("." + step.getGetter().getName() + "()");
            }

            code.append("." + finalSetter.getName() + "(" + newValue.getCode() + ")");
        }

        return new ACodeSnippet(code.toString());
    }

    public static class Step {
        private final Method getter;
        private final boolean nullSafe;

        public Step(Method getter, boolean nullSafe) {
            this.getter = getter;
            this.nullSafe = nullSafe;
        }

        public Method getGetter() {
            return getter;
        }

        public boolean isNullSafe () {
            return nullSafe;
        }
    }
}

@@ -6424,11 +6424,125 @@
  %7D%0A    %7D
+%0A%0A    @Override public String toString () %7B%0A        return %22AMethodPathBasedPropertyAccessor%7B%22 + name + '%7D';%0A    %7D
 %0A%7D%0A

debug support