#include <avr/io.h>
#include <stdlib.h>
#include "firefly.h"

// neural network structure
struct neural_network
{
  unsigned int inputs, outputs, children, life, id, wait;
  unsigned int status;
  struct _hidden_layer
  {
    int weights[NN_INPUTS];
    int activated;
  } hidden_layer[NN_HIDDEN];
  struct _output_layer
  {
    int weights[NN_HIDDEN];
    int activated;
  } output_layer[NN_OUTPUTS];
};

// define firefly list
struct neural_network firefly[FF_COUNT];

// define functions
void firefly_call(struct neural_network *network);
void firefly_respond(struct neural_network *network);
void firefly_wait(struct neural_network *network);
void firefly_birthday(struct neural_network *network);
void neural_network_setup(struct neural_network *network);
void neural_network_randomize(struct neural_network *network);
void neural_network_hybridize(struct neural_network *network);
void neural_network_calculate(struct neural_network *network, unsigned int inputs);

// setup fireflies
void fireflies_setup()
{
  unsigned int i;

  // setup neural networks
  for(i = 0; i < FF_COUNT; i++)
  {
    firefly[i].id = i;
    neural_network_setup(firefly + i);
    neural_network_randomize(firefly + i);
  }
}

// update all fireflies
void fireflies_update()
{
  unsigned int i;

  // update fireflies
  for(i = 0; i < FF_COUNT; i++)
  {
    // finished waiting
    if(firefly[i].status & _BV(FF_WAITING) && firefly[i].status & _BV(FF_DONE))
    {
      firefly_call(firefly + i);
    }
    // finished calling
    else if(firefly[i].status & _BV(FF_CALLING) && firefly[i].status & _BV(FF_DONE))
    {
      firefly_respond(firefly + i);
    }
    // finished responding
    else if(firefly[i].status & _BV(FF_RESPONDING) && firefly[i].status & _BV(FF_DONE))
    {
      firefly_wait(firefly + i);
    }
  }

  // have a birthday party for each firefly!
  for(i = 0; i < FF_COUNT; i++)
  {
    // is our firefly still waiting?
    if(firefly[i].wait == 0)
    {
      firefly[i].status |= _BV(FF_DONE);
      firefly_birthday(firefly + i);
    }
    else
    {
      firefly[i].wait--;
    }
  }
}

// get the brightness of a firefly
unsigned int fireflies_brightness(unsigned int firefly)
{
  return 0; // TODO
}

// Tell the network to call
void firefly_call(struct neural_network *network)
{
  neural_network_calculate(network, network->status & _BV(FF_SEX));
  network->wait = rand() % 30 + 20;
  network->status &= ~(_BV(FF_WAITING) | _BV(FF_DONE));
  network->status |= _BV(FF_CALLING);
}

// Tell other fireflies to respond to the call
void firefly_respond(struct neural_network *network)
{
  unsigned int i;
  for(i = 0; i < FF_COUNT; i++)
  {
    // call out to all waiting fireflies
    if(firefly + i != network && firefly[i].status & _BV(FF_WAITING))
    {
      neural_network_calculate(network + i,
        (firefly[i].status & _BV(FF_SEX)) +
        ((i + 1) << 1) +
        ((network->outputs & 0x38) << 1));
      if((firefly[i].outputs & 7) > 0 && (firefly[i].outputs & 7) <= 6)
      {
        network->children++;
        firefly[i].children++;
        firefly[i].wait = rand() % 30 + 20;
        firefly[i].status &= ~(_BV(FF_READY) | _BV(FF_DONE));
        firefly[i].status |= _BV(FF_RESPONDING);
      }
    }
  }
  firefly_wait(network);
}

// Tell the firefly to wait
void firefly_wait(struct neural_network *network)
{
  network->wait = rand() % (256 - 10) + 10;
  network->status &= ~(_BV(FF_CALLING) | _BV(FF_RESPONDING) | _BV(FF_DONE));
  network->status |= _BV(FF_WAITING);
}

// Age the firefly
void firefly_birthday(struct neural_network *network)
{
  // is our firefly dead?
  if(network->life == 0)
  {
    neural_network_setup(network);
    neural_network_hybridize(network);
  }
  else
  {
    network->life--;
  }
}

// setup a random neural network
void neural_network_setup(struct neural_network *network)
{
  // setup network
  network->inputs = 0;
  network->outputs = 0;
  network->children = 0;
  network->life = rand() % 56 + 200;
  network->status = rand() & _BV(FF_SEX);
  firefly_wait(network);
}

// setup a random neural network
void neural_network_randomize(struct neural_network *network)
{
  unsigned int i, j;

  // randomize weights in hidden layer
  for(i = 0; i < NN_HIDDEN; i++)
    for(j = 0; j < NN_INPUTS; j++)
      network->hidden_layer[i].weights[j] = rand();

  // randomize weights in output layer
  for(i = 0; i < NN_OUTPUTS; i++)
    for(j = 0; j < NN_INPUTS; j++)
      network->output_layer[i].weights[j] = rand();
}

// hybridize a neural network
void neural_network_hybridize(struct neural_network *network)
{
}

// update a neural network
void neural_network_calculate(struct neural_network *network, unsigned int inputs)
{
  unsigned int i, j;

  // update inputs
  network->inputs = inputs;

  // calculate hidden layer
  for(i = 0; i < NN_HIDDEN; i++)
  {
    network->hidden_layer[i].activated = 0;
    for(j = 0; j < NN_INPUTS; j++)
      network->hidden_layer[i].activated += NN_ACTIVATION((network->inputs >> j) & 1) * network->hidden_layer[i].weights[j];
  }

  // calculate output layer
  for(i = 0; i < NN_OUTPUTS; i++)
  {
    network->output_layer[i].activated = 0;
    for(j = 0; j < NN_HIDDEN; j++)
      network->output_layer[i].activated += (network->hidden_layer[j].activated >= NN_THRESHOLD) * network->output_layer[i].weights[j];
  }

  // set output
  network->outputs = 0;
  for(i = 0; i < NN_OUTPUTS; i++)
    network->outputs += (network->output_layer[i].activated >= NN_THRESHOLD) << i;
}
