package net.woodyfolsom.msproj.ann2;

import java.io.InputStream;
import java.io.OutputStream;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
public class MultiLayerPerceptron extends FeedforwardNetwork {
	private boolean biased;
	private Layer[] layers;

	public MultiLayerPerceptron() {
		this(false, 1, 1);
	}

	public MultiLayerPerceptron(boolean biased, int... layerSizes) {
		super(biased);

		int numLayers = layerSizes.length;

		if (numLayers < 2) {
			throw new IllegalArgumentException("# of layers must be >= 2");
		}

		this.layers = new Layer[numLayers];

		for (int layerIndex = 0; layerIndex < numLayers; layerIndex++) {
			int layerSize = layerSizes[layerIndex];

			if (layerSize < 1) {
				throw new IllegalArgumentException("Layer size must be >= 1");
			}

			Layer newLayer = createNewLayer(layerIndex, layerSize);

			if (layerIndex > 0) {
				Layer prevLayer = layers[layerIndex - 1];
					for (int j = 0; j < newLayer.size(); j++) {
						if (biased) {
							createBiasConnection(newLayer.getNeuronId(j),0.0);
						}
						for (int i = 0; i < prevLayer.size(); i++) {
								addConnection(new Connection(prevLayer.getNeuronId(i),
							newLayer.getNeuronId(j), 0.0));
					}
				}
			}
		}
	}

	private Layer createNewLayer(int layerIndex, int layerSize) {
		Layer layer = new Layer(layerSize);
		layers[layerIndex] = layer;
		for (int n = 0; n < layerSize; n++) {
			Neuron neuron = createNeuron(layerIndex == 0);
			layer.setNeuronId(n, neuron.getId());
		}
		return layer;
	}

	@XmlElement
	public Layer[] getLayers() {
		return layers;
	}

	@Override
	protected double[] getOutput() {
		Layer outputLayer = layers[layers.length - 1];
		double output[] = new double[outputLayer.size()];
		for (int n = 0; n < outputLayer.size(); n++) {
			output[n] = getNeuron(outputLayer.getNeuronId(n)).getOutput();
		}
		return output;
	}
	
	@Override
	public Neuron[] getOutputNeurons() {
		Layer outputLayer = layers[layers.length - 1];
		Neuron[] outputNeurons = new Neuron[outputLayer.size()];
		for (int i = 0; i < outputLayer.size(); i++) {
			outputNeurons[i] = getNeuron(outputLayer.getNeuronId(i));
		}
		return outputNeurons;
	}

	@Override
	protected void setInput(double[] input) {
		Layer inputLayer = layers[0];
		for (int n = 0; n < inputLayer.size(); n++) {
			getNeuron(inputLayer.getNeuronId(n)).setInput(input[n]);
		}
	}

	public void setLayers(Layer[] layers) {
		this.layers = layers;
	}

	@Override
	public boolean load(InputStream is) {
		try {
			JAXBContext jc = JAXBContext
					.newInstance(MultiLayerPerceptron.class);

			// unmarshal from foo.xml
			Unmarshaller u = jc.createUnmarshaller();
			MultiLayerPerceptron mlp = (MultiLayerPerceptron) u.unmarshal(is);

			super.setActivationFunction(mlp.getActivationFunction());
			super.setConnections(mlp.getConnections());
			super.setNeurons(mlp.getNeurons());
			this.biased = mlp.biased;
			this.layers = mlp.layers;

			return true;
		} catch (JAXBException je) {
			je.printStackTrace();
			return false;
		}
	}

	@Override
	public boolean save(OutputStream os) {
		try {
			JAXBContext jc = JAXBContext
					.newInstance(MultiLayerPerceptron.class);

			Marshaller m = jc.createMarshaller();
			m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
			m.marshal(this, os);
			m.marshal(this, System.out);
			return true;
		} catch (JAXBException je) {
			je.printStackTrace();
			return false;
		}
	}
}