# -*- coding: utf-8 -*-

from __future__ import absolute_import

from spark.node import Input, Output
from spark.simulation import Simulation, SimulationError
from .neuron import RbfNeuron
from .accumulator import Accumulator


class RbfSimulation(Simulation):
    '''
    A radial basis function simulation.
    '''

    def __init__(self, **kwargs):
        '''
        Create the simulation.
        '''

        # store settings
        self.network = kwargs.get('network')
        self.learnrate = kwargs.get('learnrate', 1.0)

        # setup the simulation
        self.epochs = 0
        self.nodes = self.sort_nodes(self.network)

    def step(self, inputs, expected):
        '''
        Run one step of the simulation.
        '''

        net = self.network

        # retrieve input values
        data = {}
        for n in net.inputs:
            data[n] = inputs.get(n, net[n].default)
            if data[n] is None:
                raise ValueError("Missing value for input {0}!".format(n))

        # next epoch
        self.epochs += 1

        # evaluate the network
        potentials = {}
        for n in self.nodes:
            # output
            if isinstance(net[n], Output):
                edge = net.get_inputs(n)[0]
                data[n] = data[edge[0]] * net.weights[edge]

            # accumulator
            if isinstance(net[n], Accumulator):
                weights = [net.weights[e] for e in net.get_inputs(n)]
                values = [data[e[0]] for e in net.get_inputs(n)]

                potentials[n] = net[n].potential(weights, values)
                data[n] = potentials[n]

            # neuron
            if isinstance(net[n], RbfNeuron):
                weights = [net.weights[e] for e in net.get_inputs(n)]
                values = [data[e[0]] for e in net.get_inputs(n)]

                potentials[n] = net[n].potential(weights, values)
                data[n] = net[n].transfer(potentials[n])

        # can we train the network
        if expected is not None:
            # update the weights
            for n in self.nodes:
                # skip everything but accumulator nodes
                if not isinstance(net[n], Accumulator):
                    continue

                # retrieve the expected value
                _, output = net.get_outputs(n)[0]

                # update the input weights
                error = expected[output] - potentials[n]
                for e in net.get_inputs(n):
                    net.weights[e] += self.learnrate * data[e[0]] * error

        # return the output values
        return dict((n, data[n]) for n in net.outputs)
