/*
 * Copyright (C) by Courtanet, All Rights Reserved.
 */
package io.doov.core.dsl.runtime;

import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;

import io.doov.core.CodeLookup;
import io.doov.core.CodeValuable;
import io.doov.core.FieldId;
import io.doov.core.FieldInfo;
import io.doov.core.dsl.DslField;
import io.doov.core.dsl.impl.DefaultCondition;

/**
 * Runtime implementation for {@link FieldInfo} and {@link DslField}.
 * {@link Function#apply(Object)} method gets the value of the field from the model.
 * {@link BiConsumer#accept(Object, Object)} sets the value of the field to the model.
 *
 * @param <B> model entry type
 * @param <R> field type
 */
public class RuntimeField<B, R> implements DslField<R>, FieldInfo, Function<B, R>, BiConsumer<B, R> {

    private final List<PathMethod<Object, Object>> chain;
    private final PathMethod<Object, R> lastLink;
    private final FieldId id;
    private final String readable;
    private FieldId[] siblings;
    private Class<R> type;
    private final Class<?>[] genericTypes;
    private boolean isCodeLookup;
    private boolean isCodeValuable;
    private boolean isTransient;

    public RuntimeField(List<PathMethod<Object, Object>> chain,
                    PathMethod<Object, R> lastLink,
                    FieldId id,
                    String readable,
                    FieldId[] siblings,
                    Class<R> type,
                    Class<?>[] genericTypes,
                    boolean isTransient) {
        this.chain = chain;
        this.lastLink = lastLink;
        this.id = id;
        this.readable = readable;
        this.siblings = siblings;
        this.type = type;
        this.genericTypes = genericTypes;
        this.isCodeLookup = CodeLookup.class.isAssignableFrom(type);
        this.isCodeValuable = CodeValuable.class.isAssignableFrom(type);
        this.isTransient = isTransient;
    }

    @Override
    public FieldId id() {
        return id;
    }

    @Override
    public String readable() {
        return readable;
    }

    @Override
    public DefaultCondition<R> getDefaultCondition() {
        return new DefaultCondition<>(this);
    }

    @Override
    public R apply(B b) {
        return get(b);
    }

    public R get(B model) {
        if (model == null) {
            return null;
        }
        Object next = model;
        for (PathMethod<Object, Object> m : chain) {
            next = m.get(next);
            if (next == null) {
                return null;
            }
        }
        return lastLink.get(next);
    }

    @Override
    public void accept(B b, R r) {
        set(b, r);
    }

    public void set(B model, R value) {
        if (model == null && value == null) {
            return;
        }
        Object next = model;
        for (PathMethod<Object, Object> method : chain) {
            Object o = method.get(next);
            if (o == null) {
                if (value != null) {
                    next = method.create(next);
                } else {
                    return;
                }
            } else {
                next = o;
            }
        }
        lastLink.set(next, value);
    }

    @SuppressWarnings("unchecked")
    public RuntimeField<B, R> register(List<RuntimeField<B, Object>> registry) {
        registry.add((RuntimeField<B, Object>) this);
        return this;
    }

    @Override
    public FieldId[] siblings() {
        return siblings;
    }

    @Override
    public Class<R> type() {
        return type;
    }

    @Override
    public Class<?>[] genericTypes() {
        return genericTypes;
    }

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

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

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

}

@@ -2747,25 +2747,8 @@
 l ==
- null && value ==
  nul
@@ -2793,32 +2793,36 @@
      Object next
+Node
  = model;%0A      
@@ -2892,17 +2892,28 @@
  Object 
-o
+methodReturn
  = metho
@@ -2914,32 +2914,36 @@
  method.get(next
+Node
 );%0A            i
@@ -2945,17 +2945,28 @@
     if (
-o
+methodReturn
  == null
@@ -2997,32 +2997,67 @@
 value != null) %7B
+ // create the path to insert value
 %0A               
@@ -3061,24 +3061,28 @@
         next
+Node
  = method.cr
@@ -3086,24 +3086,28 @@
 .create(next
+Node
 );%0A         
@@ -3113,32 +3113,74 @@
         %7D else %7B
+ // Don't create the path for a null value
 %0A               
@@ -3230,16 +3230,36 @@
 %7D else %7B
+ // Continue on path
 %0A       
@@ -3275,12 +3275,27 @@
 next
- = o
+Node = methodReturn
 ;%0A  
@@ -3341,16 +3341,20 @@
 set(next
+Node
 , value)

Improve insertion algorithm strentgh and readibility