package com.naaturel.ANN.domain.model.neuron;
import com.naaturel.ANN.domain.abstraction.ActivationFunction;
import com.naaturel.ANN.domain.abstraction.Model;

import java.util.List;
import java.util.function.Consumer;

public class Neuron implements Model {

    protected List<Synapse> synapses;
    protected Bias bias;
    protected ActivationFunction activationFunction;
    protected Float output;

    public Neuron(List<Synapse> synapses, Bias bias, ActivationFunction func){
        this.synapses = synapses;
        this.bias = bias;
        this.activationFunction = func;
        this.output = 0F;
    }

    public void updateBias(Weight weight) {
        this.bias.setWeight(weight.getValue());
    }

    public void updateWeight(int index, Weight weight) {
        this.synapses.get(index).setWeight(weight.getValue());
    }

    protected void setInputs(List<Input> inputs){
        for(int i = 0;  i < inputs.size() && i < synapses.size(); i++){
            Synapse syn = this.synapses.get(i);
            syn.setInput(inputs.get(i));
        }
    }

    public ActivationFunction getActivationFunction(){
        return this.activationFunction;
    }

    public float getOutput(){
        return this.output;
    }

    public float calculateWeightedSum() {
        float res = 0;
        res += this.bias.getWeight() * this.bias.getInput();
        for(Synapse syn : this.synapses){
            res += syn.getWeight() * syn.getInput();
        }
        return res;
    }

    @Override
    public int synCount() {
        return this.synapses.size()+1; //take the bias into account
    }

    @Override
    public int neuronCount() {
        return 1;
    }

    @Override
    public List<Float> predict(List<Input> inputs) {
        this.setInputs(inputs);
        this.output = activationFunction.accept(this);
        return List.of(output);
    }

    @Override
    public void forEachNeuron(Consumer<Neuron> consumer) {
        consumer.accept(this);
    }

    @Override
    public void forEachSynapse(Consumer<Synapse> consumer) {
        consumer.accept(this.bias);
        this.synapses.forEach(consumer);
    }

    @Override
    public void forEachOutputNeurons(Consumer<Neuron> consumer) {
        consumer.accept(this);
    }

    @Override
    public void forEachNeuronConnectedTo(Neuron n, Consumer<Neuron> consumer) {
        throw new UnsupportedOperationException("Neurons have no connection with themselves");
    }
}
