package nl.utwente.viskell.ui.components;

import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import nl.utwente.ewi.caes.tactilefx.control.TactilePane;
import nl.utwente.viskell.haskell.expr.Annotated;
import nl.utwente.viskell.haskell.expr.Binder;
import nl.utwente.viskell.haskell.expr.Expression;
import nl.utwente.viskell.haskell.expr.Lambda;
import nl.utwente.viskell.haskell.expr.LocalVar;
import nl.utwente.viskell.haskell.type.FunType;
import nl.utwente.viskell.haskell.type.Type;
import nl.utwente.viskell.ui.CustomUIPane;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * A definition block is a block that represents a named lambda. It can be used to build lambda abstractions.
 */
public class DefinitionBlock extends Block implements ComponentLoader {

    // TODO make this an independent class if other blocks (case?) need this too 
    /** An internal output anchor for an argument binder. */
    public static class BinderAnchor extends OutputAnchor {
        /** The variable binder corresponding to this input argument */
        private final Binder binder;

        public BinderAnchor(DefinitionBlock parent, Binder binder) {
            super(parent);
            this.binder = binder;
        }

        @Override
        public Expression getExpr() {
            return new LocalVar(this.binder);
        }
        
        @Override
        public void handleConnectionChanges() {
            // do not propagate upwards to the outer block
        }

    }

    // TODO make this an independent class if other blocks (case?) need this too
    /** An internal input anchor for a local result. */
    public static class ResultAnchor extends InputAnchor {
        /** The optional type of the result of the function (the last part of the signature). */
        private final Optional<Type> resType;

        public ResultAnchor(DefinitionBlock parent, Optional<Type> resType) {
            super(parent);
            this.resType = resType;
        }
        
        @Override
        public Expression getExpr() {
            if (this.resType.isPresent()) {
                return new Annotated(super.getExpr(), this.resType.get());
            }
           
            return super.getExpr();
        }
    }
    
    @FXML private Pane argSpace;
    @FXML private Pane resSpace;
    @FXML private Pane funSpace;

    @FXML private Label signature;

    private List<BinderAnchor> args;

    /** The result anchor (first bottom anchor) */
    private ResultAnchor res;

    /** The function anchor (second bottom anchor) */
    private OutputAnchor fun;


    /**
     * Constructs a DefinitionBlock that is an untyped lambda of n arguments.
     * @param pane the parent ui pane.
     * @param arity the number of arguments of this lambda.
     */
    public DefinitionBlock(CustomUIPane pane, int arity) {
        super(pane);
        this.loadFXML("DefinitionBlock");

        this.signature.setText("");
        this.signature.setVisible(false);
        this.args = new ArrayList<>();
        for (int i = 0; i < arity; i++) {
            this.args.add(new BinderAnchor(this, new Binder("x_" + i)));
        }
        this.res = new ResultAnchor(this, Optional.empty());
        
        this.setupAnchors();
    }
            
    public DefinitionBlock(CustomUIPane pane, String name, Type type) {
        super(pane);
        this.loadFXML("DefinitionBlock");

        this.args = new ArrayList<>();
        this.signature.setText(name + " :: " + type.prettyPrint());
        // Collect argument types and result type
        Type t = type;
        int i = 0;
        while (t instanceof FunType) {
            FunType ft = (FunType) t;
            args.add(new BinderAnchor(this, new Binder(name + "_x" + i, ft.getArgument())));
            i++;
            t = ft.getResult();
        }
        this.res = new ResultAnchor(this, Optional.of(t));

        this.setupAnchors();
    }

    private void setupAnchors() {
        this.argSpace.getChildren().addAll(this.args);
        this.resSpace.getChildren().add(this.res);
        this.fun = new OutputAnchor(this);
        this.funSpace.getChildren().add(this.fun);
        TactilePane.setGoToForegroundOnContact(this, false);
    }

    @Override
    public Optional <OutputAnchor> getOutputAnchor() {
        return Optional.of(this.fun);
    }

    @Override
    public void handleConnectionChanges() {
        if (!this.exprIsDirty) {
            // also propagate into the internals
            this.res.getOppositeAnchor().ifPresent(a -> a.handleConnectionChanges());
        }
        
        super.handleConnectionChanges();
    }
    
    @Override
    public final void updateExpr() {
        List<Binder> binders = this.args.stream().map(arg -> arg.binder).collect(Collectors.toList());
        this.expr = new Lambda(binders, this.res.getExpr());
    }

    @Override
    public void invalidateVisualState() {
        // TODO maybe something with internal blocks?
    }
}

@@ -1444,153 +1444,8 @@
   %7D%0A
-        %0A        @Override%0A        public void handleConnectionChanges() %7B%0A            // do not propagate upwards to the outer block%0A        %7D%0A%0A
     
@@ -4889,33 +4889,56 @@
  // 
-TODO maybe something wi
+also update the internal blocks connected to 
 th
+e
  int
@@ -4947,15 +4947,110 @@
 nal 
-blocks?
+anchor %0A        this.res.getOppositeAnchor().ifPresent(a -%3E a.getBlock().staleVisuals.setValue(true));
 %0A   

Fix of fix of updating in lambda blocks