// Provide classes for simulating a mouse moving in a maze
//
// Copyright 2007 John L Baker. All rights reserved.
//
// This software is provided AS IS under the terms of the Open Source
// MIT License. See http://www.opensource.org/licenses/mit-license.php.
//
// File: mouse_baker_2003.cpp
//
// Release:		1.0.0
// Author:		John Baker
// Updated:		14 July 2006
//
// Description:
//
// To simulate the effects of NMDA knockout in the CA3 portion of the hippocampus,
// it is necessary to generate inputs similar in form to that which might be present
// for a mouse moving in a maze, typically a Morris water maze or its dry equivalent.
//
// The maze subject is nominally a mouse with motion parameters similar to those
// reported by Nakazawa. Only random swimming (or walking) is provided. Goal
// seeking behavior is not included in the simulation,
//
// This mouse model is basically the way a hippocampus specialist would think
// of a mouse, i.e. a hippocampus with some trivial behavior attached. No real
// attempt at even low fidelity simulation of mouse behavior is intended.


#include "mouse_baker_2003.h"

using namespace std;
using namespace BNSF;
using namespace BAKER_2003;



// --------------------------------------------------------------------
// Mouse class body
// --------------------------------------------------------------------



// Default random number seed values. These values are
// from a random units table in CRC Standard Mathematical 
// Tables 13th edition (1964 - a vintage year), which uses 
// values from a table prepared by the Bureau of Transport
// Economics and Statistics of the Interstate Commerce 
// Commission, Wash. DC. Using values from this source at 
// least avoids the issue of picking good and bad seed values
// in some preferential fashion. Doubles are used to simplify 
// combining pairs of 5 digit values into a single seed and 
// avoiding the C convention for integer constants in which 
// 010=8 because the constant is taken as octal (really).
// There is some possibility that these seeds could result in
// overlaps in the random streams, but that seems unlikely.

double Mouse::_defaultSeeds[8][6] = {

//	network       PC locations  movements     PC spikes     IN spikes     synapses
	0.1048015011, 0.0153602011, 0.8164791646, 0.6917914194, 0.6259036207, 0.2096999570, 
	0.9129190700, 0.2236846573, 0.2559585393, 0.3099589198, 0.2798253402, 0.9396534095, 
	0.5266619174, 0.3961599505, 0.2413048360, 0.2252797265, 0.7639364809, 0.1517924830, 
	0.4934032081, 0.3068019655, 0.6334858629, 0.4216793093, 0.0624361680, 0.0785616376, 
	0.3944053537, 0.7134157004, 0.0084974917, 0.9775816379, 0.3757039975, 0.8183716656,
	0.0612191782, 0.6046881305, 0.4968460672, 0.1411006927, 0.0126354613, 0.7792106907, 
	0.1100842751, 0.2775653498, 0.1860270659, 0.9065515053, 0.2191681825, 0.4439442880,
	0.9956272905, 0.5642069994, 0.9887231016, 0.7119418738, 0.4401348840, 0.6321321069};

// Constructors and destructor
Mouse::Mouse(
	Maze*				m,
	MovementType		moveType,
	double*				seedValues,
	bool				buildNet, 
	bool				doInit) 
: MazeSubject(m)
{
	using namespace UOM;

	// Do basic initialization of variables
	_ECPlaceCells = NULL;
	_DGPlaceCells = NULL;
	_CA3PlaceCells = NULL;
	_CA3AxoAxonicCells = NULL;
	_CA3BasketCells = NULL;
	_CA3BistratifiedCells = NULL;
	_CA3OLMCells = NULL;

	_PP = NULL;
	_MF = NULL;
	_AC = NULL;
	_INaxon = NULL;
	_INsoma = NULL;
	_INproximal = NULL;
	_INdistal = NULL;

	_targetCell = NULL;

	_buildNetwork = buildNet;
	_ownsTargetCell = false;

	// Set a default speed appropriate for a mouse
	_speed = 10*cm/sec;

	// Save the initial movement policy
	switch (moveType) {

	case wallFollower:
		// Set movement to follow walls such that the
		// target place field center is in the middle
		// of a 5-cm spatial bin.
		movementPolicy(new WallFollower(this, 7.5*cm) );
		break;

	case randomWalk:
		// Set random walk to more or less cover a 50-cm
		// diameter circle in a few minutes.
		movementPolicy(new RandomWalk(this, 4/sec, 2*sec));
		break;

	default:
		FatalError("(Mouse::Mouse) Invalid movement type");
	}

	// Set seeds for random number generator
	setSeedValues(seedValues==NULL ? _defaultSeeds[0] : seedValues);

	// Finish initialization if permitted
	if (doInit) {
		initialize();
	}
}

Mouse::~Mouse()
{
	// Delete any allocated storage
	delete _ECPlaceCells;
	delete _DGPlaceCells;
	delete _CA3PlaceCells;
	delete _CA3AxoAxonicCells;
	delete _CA3BasketCells;
	delete _CA3BistratifiedCells;
	delete _CA3OLMCells;

	delete _PP;
	delete _MF;
	delete _AC;
	delete _INaxon;
	delete _INsoma;
	delete _INproximal;
	delete _INdistal;

	// Since the movement policy is created here it is deleted.
	// Also, must set to NULL to prevent superclass from using ptr.
	delete _movementPolicy;
	_movementPolicy = NULL;

	// Delete the target cell if not supplied
	// externally.
	if (_ownsTargetCell) {
		delete _targetCell;
	}
}

// Set the maze for this subject and set location to its origin
void Mouse::maze(Maze* m)
{

	// Let superclass do the assignment
	MazeSubject::maze(m);

	// If the maze has been set, initialize location
	if (m!=NULL) {
		locX( m->originX() );
		locY( m->originY() );
	}
}

// Set the novelty region here and in affected layers
void Mouse::noveltyRegion(SpatialRegion* novelArea)
{
	_noveltyRegion = novelArea;

	// For now, assume EC is not affected by novelty.
	// Otherwise, pass novelty region to all layers.
	
	DGPlaceCells()->inactiveRegion(novelArea);
	CA3PlaceCells()->inactiveRegion(novelArea);
	
	CA3AxoAxonicCells()->inactiveRegion(novelArea);
	CA3BasketCells()->inactiveRegion(novelArea);
	CA3BistratifiedCells()->inactiveRegion(novelArea);
	CA3OLMCells()->inactiveRegion(novelArea);
}

// Set the target cell to a non-default value
void Mouse::targetCell(CA3PyramidalCell* cell)
{
	// Make sure target cell not changed once network is in place
	if (CA3PlaceCells() != NULL ) {
		FatalError("(Mouse::targetCell) Target cell cannot be changed after initialization.");
	}
	
	// Delete earlier cell if created here
	if (_ownsTargetCell) {
		delete _targetCell;
	}

	// Save the new cell and set ownership flag
	_targetCell = cell;				// save cell provided
	_ownsTargetCell = cell==NULL;	// does not own cell if supplied here
}

// Initialize the instance
void Mouse::initialize()
{
	// Set the starting point as the origin of the maze, if available
	if (maze() != NULL) {
		locX( maze()->originX() );
		locY( maze()->originY() );
	}

	// Start with a random heading
	randomizeHeading();

	// Create the necessary components
	if (_targetCell==NULL) {
		allocateTargetCell();
	}
	allocateCellLayers();
	setLayerActivities();
	if ( buildNetwork() ) {
		connectNetwork();
	}
	
	// Inform cell layers about any novelty region
	noveltyRegion( noveltyRegion() );
}

// Set randomizer seed values (array of 6 doubles)
void Mouse::setSeedValues(double* seedValues)
{
	networkRandomizer() ->setSeed(seedValues[0]);
	locationRandomizer()->setSeed(seedValues[1]);
	movementRandomizer()->setSeed(seedValues[2]);
	pcSpikeRandomizer() ->setSeed(seedValues[3]);
	inSpikeRandomizer() ->setSeed(seedValues[4]);
	synapticRandomizer()->setSeed(seedValues[5]);
}

// Allocate the target cell using a default cell definition
void Mouse::allocateTargetCell()
{
	_targetCell = new L56aPyramidalCell;
	_ownsTargetCell = true;
	_targetCell->model()->uniformRandom(synapticRandomizer());
	_targetCell->numericIdentifier(0);
}

// Allocate cell layers (EC, DG, CA3)
void Mouse::allocateCellLayers()
{
	using namespace UOM;

	UniformRandom*	pcspk = pcSpikeRandomizer();		// for PC Poisson cells
	UniformRandom*	inspk = inSpikeRandomizer();		// for IN Poisson cells

	// Create place cell layers ------------- #cells 1st Id randgen
	_ECPlaceCells		  = new ECLayer (this,  2000, 10000, pcspk);
	_DGPlaceCells		  = new DGLayer (this,  4000, 20000, pcspk);
	_CA3PlaceCells		  = new CA3Layer(this, 12000, 30000, pcspk);

	// Create interneuron cell layers ----------------- #cells 1st Id randgen
	_CA3AxoAxonicCells	  = new CA3AxoAxonicLayer   (this, 100, 50000, inspk);
	_CA3BasketCells		  = new CA3BasketLayer      (this, 100, 51000, inspk);
	_CA3BistratifiedCells = new CA3BistratifiedLayer(this, 500, 52000, inspk);
	_CA3OLMCells		  = new CA3OLMLayer         (this, 100, 53000, inspk);
}

// Set activity fractions for affected networks
void Mouse::setLayerActivities()
{
	_DGPlaceCells		  ->setFractionActive(0.05f);
	_CA3PlaceCells		  ->setFractionActive(0.18f);
}

// Connect the layers with the target
void Mouse::connectNetwork()
{

	UniformRandom*	urnet = networkRandomizer();

	// Initialize connection policy objects
	_PP = new ECPerforantPath(_ECPlaceCells, urnet);
	_AC = new CA3AssocCollaterals(_CA3PlaceCells, urnet);
	_MF = new DGMossyFibers(_DGPlaceCells, urnet);

	// Make the various connections for place cells.
	// Sequential order is used to force one connection per
	// afferent neuron and to allow identification of which
	// cells in the layer are actually afferents. This also
	// means that each cell has only one connection with
	// the target cell as long as sufficient cells were
	// initially allocated in the place cell layer.
	_PP->connectWith(targetCell(), ConnectionPolicy::sequentialOrder);
	_MF->connectWith(targetCell(), ConnectionPolicy::sequentialOrder);
	_AC->connectWith(targetCell(), ConnectionPolicy::sequentialOrder);

	// Now create interneuron connections using network randomizer.
	// Random order is used to permit multiple connections
	// per afferent neuron as is found experimentally.
	_INaxon		= new CA3AxonicInhibition	(_CA3AxoAxonicCells,	urnet);
	_INsoma		= new CA3SomaticInhibition	(_CA3BasketCells,		urnet);
	_INproximal	= new CA3ProximalInhibition	(_CA3BistratifiedCells,	urnet);
	_INdistal	= new CA3DistalInhibition	(_CA3OLMCells,			urnet);

	_INaxon		->connectWith(targetCell(), ConnectionPolicy::randomOrder);
	_INsoma		->connectWith(targetCell(), ConnectionPolicy::randomOrder);
	_INproximal	->connectWith(targetCell(), ConnectionPolicy::randomOrder);
	_INdistal	->connectWith(targetCell(), ConnectionPolicy::randomOrder);
}

// Print synpase creation statistics
void Mouse::printSynapseCounts()
{
	cerr<<endl<<"PP"<<endl;
	_PP->printSynapseCounts();

	cerr<<endl<<"MF"<<endl;
	_MF->printSynapseCounts();

	cerr<<endl<<"AC"<<endl;
	_AC->printSynapseCounts();

	cerr<<endl<<"INaxon"<<endl;
	_INaxon->printSynapseCounts();

	cerr<<endl<<"INsoma"<<endl;
	_INsoma->printSynapseCounts();

	cerr<<endl<<"INproximal"<<endl;
	_INproximal->printSynapseCounts();

	cerr<<endl<<"INdistal"<<endl;
	_INdistal->printSynapseCounts();

	cerr<<endl;
}

// When adding this to a controller, add the place cell layers also
void Mouse::addToController(Controller* cont)
{
	// Let superclasses handle adding this object
	MazeSubject::addToController(cont);

	// Add the place cell layers to the controller
	ECPlaceCells()			->addToController(cont);
	DGPlaceCells()			->addToController(cont);
	CA3PlaceCells()			->addToController(cont);
	CA3AxoAxonicCells()		->addToController(cont);
	CA3BasketCells()		->addToController(cont);
	CA3BistratifiedCells()	->addToController(cont);
	CA3OLMCells()			->addToController(cont);

	// If the network was built during initialization,
	// add the target cell to the controller. Otherwise not
	// since the whole idea of not building the network
	// is to avoid the time to simulate the target cell.
	if (buildNetwork() ) {
		targetCell()->addToController(cont);
	}
}