/**
 * Copyright 2010 Neuroph Project http://neuroph.sourceforge.net
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.neuroph.contrib;

import org.neuroph.core.Layer;
import org.neuroph.core.NeuralNetwork;
import org.neuroph.core.Neuron;
import org.neuroph.nnet.learning.UnsupervisedHebbianLearning;
import org.neuroph.util.LayerFactory;
import org.neuroph.util.NeuralNetworkFactory;
import org.neuroph.util.NeuralNetworkType;
import org.neuroph.util.NeuronProperties;
import org.neuroph.util.TransferFunctionType;
import org.neuroph.util.plugins.LabelsPlugin;

/**
 * E-commerce recommender neural network based on hebbian learning.
 * Still under development, mostly hard-coded for now
 * @author Zoran Sevarac <sevarac@gmail.com>
 */
public class RecommenderNetwork extends NeuralNetwork {
	private static final long serialVersionUID = 1L;
	
	int inputLayerIdx = 0,
		typeLayerIdx = 1,
		brandLayerIdx = 2,
		priceLayerIdx = 3,
		promoLayerIdx = 4,
		outputLayerIdx = 5;

	public RecommenderNetwork() {
        for(int i = 0; i < 6; i++)
            this.addLayer(new Layer());
		//this.createDemoNetwork();
	}	
	
	public void createDemoNetwork() {
		int productsCount = 20;
		int typesCount = 3;
		int brandsCount = 3;
		int priceCount = 3;
		int promoCount = 3;
				
		this.setNetworkType(NeuralNetworkType.RECOMMENDER);
		this.getLayers().clear();
		// init neuron settings for this type of network
		NeuronProperties neuronProperties = new NeuronProperties();
		neuronProperties.setProperty("transferFunction", TransferFunctionType.RAMP);
		// for sigmoid and tanh transfer functions
		neuronProperties.setProperty("transferFunction.slope", new Double(1)); 
		
		// create input layer		
		Layer inputLayer = LayerFactory.createLayer(productsCount,
				neuronProperties);
		this.addLayer(inputLayer);
		createProductLabels(inputLayer);

		
		// create product types layer		
		Layer typeLayer = LayerFactory.createLayer(typesCount,
				neuronProperties);
		createTypeLabels(typeLayer);
		this.addLayer(typeLayer);
		
		
		// create brands layer		
		Layer brandLayer = LayerFactory.createLayer(brandsCount,
				neuronProperties);
		createBrandLabels(brandLayer);
		this.addLayer(brandLayer);	
		
		
		// create price layer		
		Layer priceLayer = LayerFactory.createLayer(priceCount, neuronProperties);
		createPriceLabels(priceLayer);
		this.addLayer(priceLayer);	
		
		// create price layer		
		Layer promoLayer = LayerFactory.createLayer(promoCount, neuronProperties);
		createPromoLabels(promoLayer);
		this.addLayer(promoLayer);		
				
		// create output layer
		Layer outputLayer = LayerFactory.createLayer(productsCount,
				neuronProperties);
		this.addLayer(outputLayer);
		createProductLabels(outputLayer);
		
		createTypeConnections();
		createBrandConnections();
		createPriceConnections();
		createPromoConnections();

        
		// create reccurent self connections in output layer
		for(Neuron neuron : this.getLayerAt(outputLayerIdx).getNeurons()) {
			neuron.addInputConnection(neuron, 1);
		}
		
		// set input and output cells for this network
		NeuralNetworkFactory.setDefaultIO(this);	
		
		// dont learn the self connections
		// moze cak i posle svakog prolaza da se primenjuje hebbianovo pravilo a ne samo nakon kupovine
		// napravi vise varijanti
		// ako kupuje onda moze da se primenjje winner takes all hebbian learning
		this.setLearningRule(new UnsupervisedHebbianLearning(this));
	}
	
	private void createTypeConnections() {
		// input connections for the first product type
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(i);
			Neuron toNeuron = this.getLayerAt(typeLayerIdx).getNeuronAt(0);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// input connections for the second product type
		for(int i = 7; i < 14; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(i);
			Neuron toNeuron = this.getLayerAt(typeLayerIdx).getNeuronAt(1);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// input connections for the third product type
		for(int i = 14; i < 20; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(i);
			Neuron toNeuron = this.getLayerAt(typeLayerIdx).getNeuronAt(2);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}		
		
		
		// output connections for the first product type
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(typeLayerIdx).getNeuronAt(0);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(i);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// output connections for the second product type
		for(int i = 7; i < 14; i++) {
			Neuron fromNeuron = this.getLayerAt(typeLayerIdx).getNeuronAt(1);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(i);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// output connections for the third product type
		for(int i = 14; i < 20; i++) {
			Neuron fromNeuron = this.getLayerAt(typeLayerIdx).getNeuronAt(2);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(i);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}			
	}
	
	private void createBrandConnections() {
		int samsung[] = {0, 1, 7, 8, 9, 14, 15};
		int lg[] = {2, 3, 10, 11, 16, 17};
		int sony[] = {4, 5, 6, 12, 13, 18, 19};
		
		// create samsung input sonnections
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(samsung[i]);
			Neuron toNeuron = this.getLayerAt(brandLayerIdx).getNeuronAt(0);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// create input connections for LG
		for(int i = 0; i < 6; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(lg[i]);
			Neuron toNeuron = this.getLayerAt(brandLayerIdx).getNeuronAt(1);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// create input connections for sony
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(sony[i]);
			Neuron toNeuron = this.getLayerAt(brandLayerIdx).getNeuronAt(2);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}		
		
		
		// output connections for the first brand
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(brandLayerIdx).getNeuronAt(0);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(samsung[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// output connections for the second brand
		for(int i = 0; i < 6; i++) {
			Neuron fromNeuron = this.getLayerAt(brandLayerIdx).getNeuronAt(1);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(lg[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// output connections for the third brand
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(brandLayerIdx).getNeuronAt(2);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(sony[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}			
	}	
	
	private void createPriceConnections() {
		int low[] = {0, 2, 4, 7, 10, 16, 18};
		int mid[] = {3, 5, 8, 11, 12, 14, 19};
		int high[] = {1, 6, 9, 13, 15, 17};		
		
		// input connections for the first price class
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(low[i]);
			Neuron toNeuron = this.getLayerAt(priceLayerIdx).getNeuronAt(0);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// input connections for the second price class
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(mid[i]);
			Neuron toNeuron = this.getLayerAt(priceLayerIdx).getNeuronAt(1);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// input connections for the third price class
		for(int i = 0; i < 6; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(high[i]);
			Neuron toNeuron = this.getLayerAt(priceLayerIdx).getNeuronAt(2);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}		
		
		
		// output connections for the first price class
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(priceLayerIdx).getNeuronAt(0);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(low[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// output connections for the second price class
		for(int i = 0; i < 7; i++) {
			Neuron fromNeuron = this.getLayerAt(priceLayerIdx).getNeuronAt(1);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(mid[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// output connections for the third price class
		for(int i = 0; i < 6; i++) {
			Neuron fromNeuron = this.getLayerAt(priceLayerIdx).getNeuronAt(2);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(high[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}			
	}		
	
	private void createPromoConnections() {
		
		int sales[] = {0, 10, 19};
		int new_products[] = {6, 9};
		int bestsellers[] = {3, 12, 14};		
		
		// input connections for the first promo type
		for(int i = 0; i < sales.length; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(sales[i]);
			Neuron toNeuron = this.getLayerAt(promoLayerIdx).getNeuronAt(0);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// input connections for the second promo type
		for(int i = 0; i < new_products.length; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(new_products[i]);
			Neuron toNeuron = this.getLayerAt(promoLayerIdx).getNeuronAt(1);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// input connections for the third promo type
		for(int i = 0; i < bestsellers.length; i++) {
			Neuron fromNeuron = this.getLayerAt(inputLayerIdx).getNeuronAt(bestsellers[i]);
			Neuron toNeuron = this.getLayerAt(promoLayerIdx).getNeuronAt(2);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}		

		// output connections for the first promo type
		for(int i = 0; i < sales.length; i++) {
			Neuron fromNeuron = this.getLayerAt(promoLayerIdx).getNeuronAt(0);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(sales[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// output connections for the second promo type
		for(int i = 0; i < new_products.length; i++) {
			Neuron fromNeuron = this.getLayerAt(promoLayerIdx).getNeuronAt(1);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(new_products[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}
		
		// output connections for the third promo type
		for(int i = 0; i < bestsellers.length; i++) {
			Neuron fromNeuron = this.getLayerAt(promoLayerIdx).getNeuronAt(2);
			Neuron toNeuron = this.getLayerAt(outputLayerIdx).getNeuronAt(bestsellers[i]);
			this.createConnection(fromNeuron, toNeuron, 0.1);
		}			
	}	
	
	private void createProductLabels(Layer layer) {
		LabelsPlugin labelsPlugin = (LabelsPlugin)getPlugin("LabelsPlugin");
		
		labelsPlugin.setLabel(layer.getNeuronAt(0), "Samsung LCD TV LE-32A330");
		labelsPlugin.setLabel(layer.getNeuronAt(1), "Samsung LCD TV LE-32A558");
		labelsPlugin.setLabel(layer.getNeuronAt(2), "LG LCD TV 32LG2000");	
		labelsPlugin.setLabel(layer.getNeuronAt(3), "LG LCD TV 32LG5010");
		labelsPlugin.setLabel(layer.getNeuronAt(4), "Sony LCD TV KDL-32L4000K");
		labelsPlugin.setLabel(layer.getNeuronAt(5), "Sony LCD TV KDL-32S4000");
		labelsPlugin.setLabel(layer.getNeuronAt(6), "Sony LCD TV KDL-32W4000K");
		labelsPlugin.setLabel(layer.getNeuronAt(7), "Samsung Digital Camera S760");
		labelsPlugin.setLabel(layer.getNeuronAt(8), "Samsung Digital Camera L100");
		labelsPlugin.setLabel(layer.getNeuronAt(9), "Samsung Digital Camera S850");
		labelsPlugin.setLabel(layer.getNeuronAt(10), "LG Digital Camera DMCLS80E");
		labelsPlugin.setLabel(layer.getNeuronAt(11), "LG Digital Camera DMCLZ8E");
		labelsPlugin.setLabel(layer.getNeuronAt(12), "Sony Digital Camera DSCW120S");
		labelsPlugin.setLabel(layer.getNeuronAt(13), "Sony Digital Camera DSCW130S");
		labelsPlugin.setLabel(layer.getNeuronAt(14), "Samsung Mobile Phone E251");
		labelsPlugin.setLabel(layer.getNeuronAt(15), "Samsung Mobile Phone U600");
		labelsPlugin.setLabel(layer.getNeuronAt(16), "Sony Mobile Phone KP100");
		labelsPlugin.setLabel(layer.getNeuronAt(17), "Sony Mobile Phone KE850");
		labelsPlugin.setLabel(layer.getNeuronAt(18), "LG Mobile Phone K330");
		labelsPlugin.setLabel(layer.getNeuronAt(19), "LG Mobile Phone K660");
	}	
	
	private void createTypeLabels(Layer layer) {
		LabelsPlugin labelsPlugin = (LabelsPlugin)getPlugin("LabelsPlugin");
		
		labelsPlugin.setLabel(layer.getNeuronAt(0), "LCD TV");
		labelsPlugin.setLabel(layer.getNeuronAt(1), "Digital Camera");
		labelsPlugin.setLabel(layer.getNeuronAt(2), "Mobile Phone");	
	}
	
	private void createBrandLabels(Layer layer) {
		LabelsPlugin labelsPlugin = (LabelsPlugin)getPlugin("LabelsPlugin");
		
		labelsPlugin.setLabel(layer.getNeuronAt(0), "Samsung");
		labelsPlugin.setLabel(layer.getNeuronAt(1), "LG");
		labelsPlugin.setLabel(layer.getNeuronAt(2), "Sony");	
	}	
	
	private void createPriceLabels(Layer layer) {
		LabelsPlugin labelsPlugin = (LabelsPlugin)getPlugin("LabelsPlugin");
		
		labelsPlugin.setLabel(layer.getNeuronAt(0), "Low Price");
		labelsPlugin.setLabel(layer.getNeuronAt(1), "Mid Price");
		labelsPlugin.setLabel(layer.getNeuronAt(2), "High Price");	
	}	
	
	private void createPromoLabels(Layer layer) {
		LabelsPlugin labelsPlugin = (LabelsPlugin)getPlugin("LabelsPlugin");
		
		labelsPlugin.setLabel(layer.getNeuronAt(0), "Sales");
		labelsPlugin.setLabel(layer.getNeuronAt(1), "New");
		labelsPlugin.setLabel(layer.getNeuronAt(2), "Bestseller");	
	}	
	
	
}
