package de.factoryfx.factory;

import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import de.factoryfx.data.Data;

@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonTypeInfo(use=JsonTypeInfo.Id.MINIMAL_CLASS, include=JsonTypeInfo.As.PROPERTY, property="@class")
/**
 *  L liveobject created from this factory
 *  V runtime visitor
 */
public abstract class FactoryBase<L,V> extends Data {

    public abstract LiveCycleController<L,V> createLifecycleController();
    LiveCycleController<L, V> lifecycleController;
    private LiveCycleController<L,V> getLifecycleController(){
        if (lifecycleController==null){
            lifecycleController = createLifecycleController();
        }
        return lifecycleController;
    }

    private String id;

    public FactoryBase() {

    }

    @Override
    public String getId() {
        if (id == null) {
            id = UUID.randomUUID().toString();
        }
        return id;
    }

    public void setId(String value) {
        id = value;
    }
    public void setId(Object value) {
        id = (String)value;
    }



    private L createdLiveObject;
    public L instance() {
        if (!changedDeep() && createdLiveObject !=null) {//TODO is the createdLiveObject==null correct? is is used if new Factory is transitive added (Limitation of the mergerdiff)
            return createdLiveObject;
        } else{
            LiveCycleController<L, V> lifecycleController = getLifecycleController();
            if (lifecycleController==null){
                throw new IllegalStateException("lifecycleController is null for:"+getClass());
            }

            if (createdLiveObject==null){
                createdLiveObject = lifecycleController.create();
            } else {
                L previousLiveObject = this.createdLiveObject;
                this.createdLiveObject = lifecycleController.reCreate(previousLiveObject);
                lifecycleController.destroy(previousLiveObject);//TODO check if this is the wrong destroy order (parent are destroyed before the children)
            }
            changed=false;
            return createdLiveObject;
        }
    }

//    protected abstract L createImp(Optional<L> previousLiveObject, LifecycleNotifier<V> lifecycle);

//    public void collectLiveObjects(Map<String,LiveObject> liveObjects){
//
//        this.visitChildFactoriesFlat(factory -> cast(factory).collectLiveObjects(liveObjects));
//
//        if (createdLiveObject!=null){
//            liveObjects.put(getId(),createdLiveObject);
//        }
//    }

    @JsonIgnore
    //intented for test only
    protected Optional<L> getCreatedLiveObject(){
        return Optional.ofNullable(createdLiveObject);
    }

    @JsonIgnore
    private boolean changed=true;

    /**set from Merger used to determine which live Objects needs update*/
    public void markChanged() {
        changed=true;
    }

    public void unMarkChanged() {
        changed=false;
    }

    @JsonIgnore
    private boolean changedDeep;
    public boolean changedDeep(){
        if (changed){
            return true;
        }
        changedDeep=false;
        this.internal().visitChildFactoriesFlat(factoryBase -> changedDeep = changedDeep || cast(factoryBase).changedDeep());
        return changedDeep;
    }

    private LoopProtector loopProtector = new LoopProtector();
    public void loopDetector(){
        loopProtector.enter();
        try {
            this.internal().visitChildFactoriesFlat(factory -> cast(factory).loopDetector());
        } finally {
            loopProtector.exit();
        }
    }

    //TODO this works as long als tree elements extends FactoryBase, how to enforce that?
    @SuppressWarnings("unchecked")
    private FactoryBase<?,V> cast(Data data){
        return (FactoryBase<?,V>)data;
    }

    @SuppressWarnings("unchecked")
    public Set<FactoryBase<?,V>> collectChildFactoriesDeep(){
        return super.internal().collectChildrenDeep().stream().map(this::cast).collect(Collectors.toSet());
    }

    public void start(){
        getLifecycleController().start(createdLiveObject);
    }

    public void destroy(){
        getLifecycleController().destroy(createdLiveObject);
    }

    public void runtimeQuery(V visitor){
        getLifecycleController().runtimeQuery(visitor,createdLiveObject);
    }

}

@@ -3766,28 +3766,58 @@
 ryBase).
-changedDeep(
+map(FactoryBase::changedDeep).orElse(false
 ));%0D%0A   
@@ -4072,16 +4072,39 @@
 actory).
+ifPresent(FactoryBase::
 loopDete
@@ -4107,17 +4107,16 @@
 Detector
-(
 ));%0D%0A   
@@ -4327,16 +4327,25 @@
 private 
+Optional%3C
 FactoryB
@@ -4352,16 +4352,17 @@
 ase%3C?,V%3E
+%3E
  cast(Da
@@ -4384,37 +4384,130 @@
     
-return (FactoryBase%3C?,V%3E)data
+if (data instanceof FactoryBase)%0D%0A            return Optional.of((FactoryBase%3C?,V%3E)data);%0D%0A        return Optional.empty()
 ;%0D%0A 
@@ -4692,16 +4692,63 @@
 ::cast).
+filter(Optional::isPresent).map(Optional::get).
 collect(

fix downcast