/*
 * 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.topology.views.wifi.phy.propagation.loss;

import java.util.HashMap;
import java.util.Random;

import de.tud.kom.p2psim.api.topology.views.wifi.phy.PropagationLossModel;
import de.tud.kom.p2psim.impl.topology.placement.RandomPositionDistributionCircle;
import de.tudarmstadt.maki.simonstrator.api.Randoms;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;

/**
 * 
 * 
 * The formula is:
 * <p>
 * rx = tx - Pr0 - 10 * n * log (d/d0)
 * <p>
 * Pr0: rx power at reference distance d0 (dBm) <br>
 * d0: reference distance: 1.0 (m) <br>
 * d: distance (m) <br>
 * n: the path loss exponent <br>
 * tx: tx power (dBm) <br>
 * rx: rx power (dBm)
 * 
 * 
 * This class based on NS3 (src/propagation/model/propagation-loss-model.cc) by
 * Mathieu Lacage <mathieu.lacage@sophia.inria.fr> and further extended by
 * Christoph Muenker. and Benjamin Becker in 2021
 * 
 * @version 1.0, 11.11.2021
 */
public class LogDistancePropagationLossModelNormalDistribution
		extends PropagationLossModel {

	/**
	 * Exponent of the path loss propagation Model. In "Wireless Communications
	 * - Principles and Practices" from Theodore S. Rappaport different
	 * exponents are introduced. To model the propagation loss of an urban area
	 * cellular radio, a value between 2.7 and 3.5 should be chosen.
	 */
	private double exponent = 3.2;

	/**
	 * The reference distance of the referenceLoss
	 */
	private double referenceDistance = 1;

	private Random random = Randoms
			.getRandom(LogDistancePropagationLossModelNormalDistribution.class);

	/**
	 * Frequency in Hz<br>
	 * Default 2,4 GHz
	 */
	private long frequency = 2400000000l;

	/**
	 * The wavelength
	 */
	private double lambda = ((double) lightspeed) / frequency;

	/**
	 * The reference Loss
	 */

	private double referenceLoss = 20
			* Math.log10(4 * Math.PI * referenceDistance / lambda);

	private HashMap<Location, HashMap<Location, Double>> pathLosses = new HashMap<>();

	private HashMap<Double, Double> pathLossesWithShadowing = new HashMap<>();

	long nextSave = -1;

	static double avg = 0;

	static int count = 0;

	static double sum = 0, sumh = 0;

	@Override
	public double getRxPowerDbm(double txPowerDbm, Location a, Location b) {

		double distance = a.distanceTo(b);
		if (distance <= referenceDistance) {
			return txPowerDbm;
		}
		double rxc;
		// reset after 100 (nanoseconds)
		if ((Time.getCurrentTime()) > nextSave) {
			pathLosses = new HashMap<>();
			nextSave = (Time.getCurrentTime() / 100L) * 100L + 100L;
		}

		HashMap<Location, Double> lossAndFadings;
		if (!pathLosses.containsKey(a)) {
			lossAndFadings = new HashMap<>();
			pathLosses.put(a, lossAndFadings);
		} else {
			lossAndFadings = pathLosses.get(a);
		}
		if (lossAndFadings.containsKey(b)) {
			rxc = lossAndFadings.get(b);
		} else {
			// random seed, based on sender/receiver position
			long positionBasedRandomSeed = (((((((((((((((long) a
					.getLatitudeOrY() % 776531419) * (long) b.getLatitudeOrY()
					% 776531419) * (long) a.getLongitudeOrX() % 776531419)
					* (long) b.getLongitudeOrX() % 776531419)
					* (long) a.getLatitudeOrY() % 776531419)
					* (long) b.getLatitudeOrY() % 776531419)
					* (long) a.getLongitudeOrX() % 776531419)
					* (long) b.getLongitudeOrX() % 776531419)
					* (long) a.getLatitudeOrY() % 776531419)
					* (long) b.getLatitudeOrY() % 776531419)
					* (long) a.getLongitudeOrX() % 776531419)
					* (long) b.getLongitudeOrX() % 776531419)
					* (long) a.getLatitudeOrY() % 776531419)
					* (long) a.getLongitudeOrX() % 776531419);

			// random seed, based on time
			long timeBasedRandomSeed = (((((nextSave * nextSave % 512927377)
					* nextSave % 512927377) * nextSave % 512927377) * nextSave
					% 512927377) * nextSave % 512927377);
			double pathLossWithShadowing;
			if (pathLossesWithShadowing.containsKey(distance)) {
				pathLossWithShadowing = pathLossesWithShadowing.get(distance);
			} else {
				// random which is constant for a pair of nodes; links are
				// symmetric
				Random r1 = new Random(positionBasedRandomSeed);
				Random r = new Random(positionBasedRandomSeed / 2 + r1.nextLong() / 2);
				// SHADOW FADING
				double shadowFading = r.nextGaussian() * 5;
				// LOG-DISTANCE PATH LOSS
				double pathLoss = 10 * exponent
						* Math.log10(distance / referenceDistance);
				pathLossWithShadowing = pathLoss + shadowFading;
				pathLossesWithShadowing.put(distance, pathLossWithShadowing);
			}
			// random which is constant for a pair of nodes, at a given time;
			// links are symmetric
			Random r1 = new Random(timeBasedRandomSeed);
			Random r = new Random((positionBasedRandomSeed / 3
					+ r1.nextLong() / 3 + r1.nextLong() / 3));
			// RICEAN FADING
			// constants: K, mean, sigma
			double K = 0;
			double mean = Math.sqrt(K / (2 * (K + 1)));
			double sigma = Math.sqrt(1 / (2 * (K + 1)));
			// gaussian randoms
			double x = mean + r.nextGaussian() * sigma;
			double y = mean + r.nextGaussian() * sigma;
			// h
			double h = Math.sqrt(x * x + y * y);
			double riceanFading = Math.log10(h);

			// total loss of the link
			rxc = -referenceLoss - pathLossWithShadowing - riceanFading;
			lossAndFadings.put(b, rxc);
		}
		return txPowerDbm + rxc;
	}

	/**
	 * Calculates watt to dBm.
	 * 
	 * @param w
	 *            The watt, which should be calculated to dBm.
	 * @return The given watt as dBm.
	 */
	public static double wToDbm(double w) {
		double mw = w * 1000.0;
		return Math.log10(mw) * 10.0;
	}

	public static double dbmToW(double dbm) {
		double mw = Math.pow(10.0, dbm / 10.0);
		return mw / 1000.0;
	}

	@Override
	public void setFrequency(long frequency) {
		this.frequency = frequency;
		updatePreComputation();
	}

	public void setReferenceDistance(double referenceDistance) {
		this.referenceDistance = referenceDistance;
		updatePreComputation();
	}

	public void setExponent(double exponent) {
		this.exponent = exponent;
	}

	public double getExponent() {
		return exponent;
	}

	protected double getReferenceLoss() {
		return referenceLoss;
	}

	/**
	 * The lamba and the referenceLoss is depending of the frequency and the
	 * referenceDistance. If one of this values changed, then should be updated
	 * the pre computed values.
	 */
	private void updatePreComputation() {
		this.lambda = ((double) lightspeed) / frequency;
		this.referenceLoss = 20
				* Math.log10(4 * Math.PI * referenceDistance / lambda);
	}
}
