package com.yahoo.labs.samoa.learners.classifiers.trees;

/*
 * #%L
 * SAMOA
 * %%
 * Copyright (C) 2013 Yahoo! Inc.
 * %%
 * 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.
 * #L%
 */

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.Serializer;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import com.yahoo.labs.samoa.core.ContentEvent;

/**
 * Attribute Content Event represents the instances that split vertically
 * based on their attribute
 * @author Arinto Murdopo
 *
 */
public final class AttributeContentEvent implements ContentEvent {

	private static final long serialVersionUID = 6652815649846676832L;

	private final long learningNodeId;
	private final int obsIndex;
	private final double attrVal;
	private final int classVal;
	private final double weight;
	private final transient String key;
	private final boolean isNominal;
	
	public AttributeContentEvent(){
		learningNodeId = -1;
		obsIndex = -1;
		attrVal = 0.0;
		classVal = -1;
		weight = 0.0;
		key = "";
		isNominal = true;
	}
		
	private AttributeContentEvent(Builder builder){
		this.learningNodeId = builder.learningNodeId;
		this.obsIndex = builder.obsIndex;
		this.attrVal = builder.attrVal;
		this.classVal = builder.classVal;
		this.weight = builder.weight;
		this.isNominal = builder.isNominal;
		this.key = builder.key;
	}
	
	@Override
	public String getKey() {
		return this.key;
	}
	
	@Override
	public void setKey(String str) {
		//do nothing, maybe useful when we want to reuse the object for serialization/deserialization purpose
	}

	@Override
	public boolean isLastEvent() {
		return false;
	}
	
	long getLearningNodeId(){
		return this.learningNodeId;
	}
	
	int getObsIndex(){
		return this.obsIndex;
	}
	
	int getClassVal(){
		return this.classVal;
	}
	
	double getAttrVal(){
		return this.attrVal;
	}
	
	double getWeight(){
		return this.weight;
	}
	
	boolean isNominal(){
		return this.isNominal;
	}
	
	static final class Builder{
		
		//required parameters
		private final long learningNodeId;
		private final int obsIndex;
		private final String key;
		
		//optional parameters
		private double attrVal = 0.0;
		private int classVal = 0;
		private double weight = 0.0;
		private boolean isNominal = false;
		
		Builder(long id, int obsIndex, String key){
			this.learningNodeId = id;
			this.obsIndex = obsIndex;
			this.key = key;
		}
		
		private Builder(long id, int obsIndex){
			this.learningNodeId = id;
			this.obsIndex = obsIndex;
			this.key = "";
		}
		
		Builder attrValue(double val){
			this.attrVal = val;
			return this;
		}
		
		Builder classValue(int val){
			this.classVal = val;
			return this;
		}
		
		Builder weight(double val){
			this.weight = val;
			return this;
		}
		
		Builder isNominal(boolean val){
			this.isNominal = val;
			return this;
		}
		
		AttributeContentEvent build(){
			return new AttributeContentEvent(this);
		}		
	}
	
	/**
	 * The Kryo serializer class for AttributeContentEvent when executing on top of Storm. 
	 * This class allow us to change the precision of the statistics.
	 * @author Arinto Murdopo
	 *
	 */
	public static final class AttributeCESerializer extends Serializer<AttributeContentEvent>{

		private static double PRECISION = 1000000.0;
		@Override
		public void write(Kryo kryo, Output output, AttributeContentEvent event) {		
			output.writeLong(event.learningNodeId, true);
			output.writeInt(event.obsIndex, true);
			output.writeDouble(event.attrVal, PRECISION, true);
			output.writeInt(event.classVal, true);
			output.writeDouble(event.weight, PRECISION, true);
			output.writeBoolean(event.isNominal);
		}

		@Override
		public AttributeContentEvent read(Kryo kryo, Input input,
				Class<AttributeContentEvent> type) {			
			AttributeContentEvent ace 
				= new AttributeContentEvent.Builder(input.readLong(true), input.readInt(true))
											.attrValue(input.readDouble(PRECISION, true))
											.classValue(input.readInt(true))
											.weight(input.readDouble(PRECISION, true))
											.isNominal(input.readBoolean())
											.build();
			return ace;
		}
	}
	
	/**
	 * The Kryo serializer class for AttributeContentEvent when executing on top of Storm 
	 * with full precision of the statistics.
	 * @author Arinto Murdopo
	 *
	 */
	public static final class AttributeCEFullPrecSerializer extends Serializer<AttributeContentEvent>{

		@Override
		public void write(Kryo kryo, Output output, AttributeContentEvent event) {
			output.writeLong(event.learningNodeId, true);
			output.writeInt(event.obsIndex, true);
			output.writeDouble(event.attrVal);
			output.writeInt(event.classVal, true);
			output.writeDouble(event.weight);
			output.writeBoolean(event.isNominal);
		}

		@Override
		public AttributeContentEvent read(Kryo kryo, Input input,
				Class<AttributeContentEvent> type) {
			AttributeContentEvent ace 
				= new AttributeContentEvent.Builder(input.readLong(true), input.readInt(true))
										.attrValue(input.readDouble())
										.classValue(input.readInt(true))
										.weight(input.readDouble())
										.isNominal(input.readBoolean())
										.build();
			return ace;
		}
		
	}
}