/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package network.aika.neuron.activation;

import network.aika.Thought;
import network.aika.neuron.activation.direction.Direction;
import network.aika.neuron.phase.VisitorPhase;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static network.aika.neuron.activation.direction.Direction.*;

/**
 *
 * @author Lukas Molzberger
 */
public class Visitor {
    private Visitor origin;
    private Activation act; // Just debug code
    public Link link; // Just debug code
    private Transition transition; // Just debug code
    private Visitor previousStep;
    private VisitorPhase phase;
    private int round;

    public enum Transition {  // Just debug code
        ACT,
        LINK
    }

    public Direction downUpDir;
    public Direction startDir;

    private Set<ScopeEntry> scopes;

    public int downSteps = 0;
    public int upSteps = 0;

    private Visitor() {}

    public Visitor(VisitorPhase vp, Activation act, Direction startDir, Direction downUpDir, Transition t, int round) {
        this.phase = vp;
        this.origin = this;
        this.act = act;
        this.transition = t;
        this.downUpDir = downUpDir;
        this.startDir = startDir;
        this.scopes = act.getNeuron().getInitialScopes(startDir);
        this.round = round;
    }

    public Visitor prepareNextStep(Activation currentAct, Link currentLink, Set<ScopeEntry> scopes, Transition t) {
        if(scopes.isEmpty())
            return null;

        Visitor nv = new Visitor();
        nv.phase = phase;
        nv.act = currentAct;
        nv.link = currentLink;
        nv.transition = t;
        nv.previousStep = this;
        nv.origin = origin;
        nv.downUpDir = downUpDir;
        nv.startDir = startDir;
        nv.upSteps = upSteps;
        nv.downSteps = downSteps;
        nv.scopes = scopes;
        nv.round = round;

        return nv;
    }

    public int getRound() {
        return round;
    }

    public Set<ScopeEntry> getScopes() {
        return scopes;
    }

    public VisitorPhase getPhase() {
        return phase;
    }

    public Activation getOriginAct() {
        return origin.act;
    }

    public void incrementPathLength() {
        if (downUpDir == INPUT) {
            this.downSteps++;
        } else {
            this.upSteps++;
        }
    }

    public boolean isClosedCycle() {
        return scopes.stream()
                        .anyMatch(s ->
                                origin.scopes.contains(s)
                        );
    }

    public boolean getSelfRef() {
        return downSteps == 0 || upSteps == 0;
    }

    public int numSteps() {
        return downSteps + upSteps;
    }

    public void tryToLink(Activation act) {
        if (downUpDir != OUTPUT || numSteps() < 1)
            return;

        if (act == origin.act || act.isConflicting())
            return; // <--

        if (scopes
                .stream()
                .anyMatch(s -> s.getScope() == Scope.PP_RELATED_INPUT)
        )
            return; // TODO

        phase.closeCycle(act, this);
    }

    private Thought getThought() {
        return origin.act.getThought();
    }

    public void onEvent(boolean dir) {
        getThought().onVisitorEvent(this, dir);
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();

        if(previousStep != null) {
            sb.append(previousStep + "\n");
        }

        sb.append("Origin:" + origin.act.toShortString() + ", ");

        if(act != null) {
            sb.append("Current:" + act.toShortString() + ", ");
        } else if(link != null) {
            sb.append("Current:" + link.toString() + ", ");
        }

        sb.append("DownUp:" + downUpDir + ", ");
        sb.append("StartDir:" + startDir + ", ");

        sb.append("Scopes:" + scopes + ", ");
        sb.append("DownSteps:" + downSteps + ", ");
        sb.append("UpSteps:" + upSteps + "");
        sb.append("Round:" + round + "");

        return sb.toString();
    }
}

@@ -2761,32 +2761,626 @@
 n round;%0A    %7D%0A%0A
+    public Visitor getOrigin() %7B%0A        return origin;%0A    %7D%0A%0A    public Activation getAct() %7B%0A        return act;%0A    %7D%0A%0A    public Link getLink() %7B%0A        return link;%0A    %7D%0A%0A    public Transition getTransition() %7B%0A        return transition;%0A    %7D%0A%0A    public Visitor getPreviousStep() %7B%0A        return previousStep;%0A    %7D%0A%0A    public Direction getDownUpDir() %7B%0A        return downUpDir;%0A    %7D%0A%0A    public Direction getStartDir() %7B%0A        return startDir;%0A    %7D%0A%0A    public int getDownSteps() %7B%0A        return downSteps;%0A    %7D%0A%0A    public int getUpSteps() %7B%0A        return upSteps;%0A    %7D%0A%0A
     public Set%3CS

work on rounds