/*
 * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
 *
 * This file is part of PeerfactSim.KOM.
 * 
 * PeerfactSim.KOM is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 * 
 * PeerfactSim.KOM is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with PeerfactSim.KOM.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package de.tud.kom.p2psim.impl.util.db.metric;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;

/**
 * 
 * Statistical representation of a series of measurements in the database,
 * intended to capture all relevant metrics for boxplots.
 *
 * @author Bjoern Richerzhagen
 */
@Entity
@Table(name = "measurements_statistics", indexes = {
		@Index(columnList = "id", name = "id") })
public class MeasurementStatistic implements GroupMetricBound {

	/**
	 * The id of this table
	 */
	@Id
	@GeneratedValue
	private int id;

	/**
	 * The simulation time for to this measurement in simulator time, that is,
	 * microseconds.
	 */
	@Column(nullable = true, name = "[time]")
	private long time;
	
	@Column(nullable = false, name = "[describesWholeSimulation]")
	private boolean describesWholeSimulation;

	/**
	 * The simulation time for to this measurement in simulator time, that is,
	 * microseconds.
	 */
	@Column(nullable = true, name = "[observationDuration]")
	private long observationDuration;

	/**
	 * The number of values
	 */
	@Column(nullable = true, name = "[values]")
	private Double values;
	
	@Column(nullable = true, name = "[std]")
	private Double std;

	@Column(nullable = true, name = "[sum]")
	private Double sum;

	@Column(nullable = true, name = "[sum2]")
	private Double sum2;

	/**
	 * The minimum of all values for this measurement
	 */
	@Column(nullable = true, name = "[min]")
	private Double min;

	/**
	 * The maximum of all values for this measurement
	 */
	@Column(nullable = true, name = "[max]")
	private Double max;

	@Column(nullable = true, name = "[mean]")
	private Double mean;

	@Column(nullable = true, name = "[median]")
	private Double median;

	@Column(nullable = true, name = "[perc25]")
	private Double perc25;

	@Column(nullable = true, name = "[perc75]")
	private Double perc75;

	@Column(nullable = true, name = "[perc97]")
	private Double perc97; // 97,7

	@Column(nullable = true, name = "[perc2]")
	private Double perc2; // 2,3

	@Column(nullable = true, name = "[perc95]")
	private Double perc95; // 95

	@Column(nullable = true, name = "[perc5]")
	private Double perc5; // 5
	
	@Column(nullable = true, name = "[locationX]")
	private Integer locationX;
	
	@Column(nullable = true, name = "[locationY]")
	private Integer locationY;
	
	@Column(nullable = true, name = "[isSpatial]")
	private boolean isSpatial;

	/**
	 * Mapping to group metric
	 */
	@ManyToOne(cascade = CascadeType.ALL)
	@JoinColumn(name = "groupMetricId")
	GroupMetric groupMetric;

	/**
	 * Creates a {@link Measurement}-Object using the provided
	 * {@link DescriptiveStatistics} object.
	 *
	 * @param time
	 *            The simulation time for to this measurement as Date
	 * @param stats
	 *            the {@link DescriptiveStatistics} object
	 * @param hostMetric
	 *            The reference to the {@link HostMetric}-Object, which
	 *            describes this metric. Is used for the mapping.
	 * @param observationDuration
	 *            duration of the observation
	 * @param describesWholeSimulation
	 *            true, if this measurement describes the whole simulation
	 */
	public MeasurementStatistic(long time, DescriptiveStatistics stats,
			GroupMetric groupMetric, long observationDuration,
			boolean describesWholeSimulation) {
		this(time, stats, observationDuration, describesWholeSimulation);
		this.groupMetric = groupMetric;
	}

	/**
	 * Creates a {@link Measurement}-Object using the provided
	 * {@link DescriptiveStatistics} object, with spatial data attached.
	 *
	 * @param time
	 *            The simulation time for to this measurement as Date
	 * @param stats
	 *            the {@link DescriptiveStatistics} object
	 * @param hostMetric
	 *            The reference to the {@link HostMetric}-Object, which
	 *            describes this metric. Is used for the mapping.
	 * @param observationDuration
	 *            duration of the observation
	 * @param describesWholeSimulation
	 *            true, if this measurement describes the whole simulation
	 * @param locationX
	 *            x coordinate for spatial sampling
	 * @param locationY
	 *            y coordinate for spatial sampling
	 */
	public MeasurementStatistic(long time, DescriptiveStatistics stats,
			GroupMetric groupMetric, long observationDuration,
			boolean describesWholeSimulation, int locationX, int locationY) {
		this(time, stats, observationDuration, describesWholeSimulation);
		this.groupMetric = groupMetric;
		this.locationX = locationX;
		this.locationY = locationY;
		this.isSpatial = true;
	}

	/**
	 * Internal - write statistics
	 * 
	 * @param time
	 * @param stats
	 * @param observationDuration
	 *            duration covered by this measurement in simulation units
	 */
	private MeasurementStatistic(long time, DescriptiveStatistics stats,
			long observationDuration, boolean describesWholeSimulation) {
		super();
		this.time = time;
		this.observationDuration = observationDuration;
		this.describesWholeSimulation = describesWholeSimulation;
		this.values = checkForSpecialNumbers((double) stats.getN());
		this.sum = checkForSpecialNumbers(stats.getSum());
		this.sum2 = checkForSpecialNumbers(stats.getSumsq());
		this.min = checkForSpecialNumbers(stats.getMin());
		this.max = checkForSpecialNumbers(stats.getMax());
		this.mean = checkForSpecialNumbers(stats.getMean());
		this.median = checkForSpecialNumbers(stats.getPercentile(50));
		this.perc2 = checkForSpecialNumbers(stats.getPercentile(2.3));
		this.perc25 = checkForSpecialNumbers(stats.getPercentile(25));
		this.perc75 = checkForSpecialNumbers(stats.getPercentile(75));
		this.perc97 = checkForSpecialNumbers(stats.getPercentile(97.7));
		this.perc5 = checkForSpecialNumbers(stats.getPercentile(5));
		this.perc95 = checkForSpecialNumbers(stats.getPercentile(95));
		this.std = checkForSpecialNumbers(stats.getStandardDeviation());
		this.isSpatial = false;
	}

	/**
	 * Check for special numbers, like infinity or NaN. If the given value is
	 * equals this numbers then will be return null, otherwise will be returned
	 * the given value.
	 *
	 * @param value
	 *            The value, which should be checked.
	 * @return The value or null, if it is a special number.
	 */
	private Double checkForSpecialNumbers(Double value) {
		if (value == null)
			return value;
		if (value.equals(Double.NEGATIVE_INFINITY)
				|| value.equals(Double.POSITIVE_INFINITY)
				|| value.equals(Double.NaN)) {
			return null;
		} else {
			return value;
		}
	}

	@Override
	public GroupMetric getGroupMetric() {
		return groupMetric;
	}

	@Override
	public void setGroupMetric(GroupMetric metric) {
		this.groupMetric = metric;
	}
}
