package de.tud.kom.p2psim.impl.topology.views.wifi.phy.propagation.loss;

import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;

/**
 * A model suited for connections between antennas at heights of 0.5-3m. Defined
 * in:
 * <p>
 * Konstantinou, K., Kang, S., & Tzaras, C. (2007). A measurement-based model for
 * mobile-to-mobile UMTS links. IEEE Vehicular Technology Conference, 529–533.
 * http://doi.org/10.1109/VETECS.2007.120
 * <p>
 * The model defines path loss for line of sight (LOS) and non light of sight (NLOS)
 * propagation.
 * <pre>
 * PLOS = 4.62 + 20 * log10(4π/λ) - 2.24ht - 4.9hr + 29.6 * log10(d)
 * PNLOS = 20 * log10(4π/λ) - 2 *hr + 40 * log10(d) + C
 *
 * C = 0 if dense urban (HB > 18m), -4 else (HB < 12m)
 * </pre>
 * <p>
 * with λ the wavelength, HB average building height, ht and hr the height of
 * the transmitting and receiving antenna above ground, d the distance.
 * <p>
 * The average path loss is estimated as <code>PL = α PLLOS + (1-α)PLNLOS</code>, where
 * α is the probability that there is a LOS connection between transmitter and
 * receiver.
 *
 * @author Tim Feuerbach
 */
public class SurreyPropagationLossModel extends LogDistancePropagationLossModel {
    private double C = 0;
    private double alpha = 0.098601797632;
    private double transmitterHeight = 1.6;
    private double receiverHeight = 1.6;

    /**
     * Distance below which the model will always return the TX power.
     */
    private double minDistance = 0.5d;

    @Override
    public double getRxPowerDbm(double txPowerDbm, Location a, Location b) {
        return getRxPowerDbm(txPowerDbm, a.distanceTo(b));
    }

    @Override
    public double getRxPowerDbm(double txPowerDbm, double distance) {
        if (distance < minDistance) return txPowerDbm;
        double pLOS = 4.62 + getReferenceLoss() - 2.24 * transmitterHeight - 4.9 * receiverHeight + 29.6 *
                Math.log10(distance);
        double pNLOS = getReferenceLoss() - 2 * receiverHeight + 40 * Math.log10(distance) + C;
        double loss = alpha * pLOS + (1 - alpha) * pNLOS;
        return txPowerDbm - Math.max(0, loss);
    }

    @Override
    public double getDistance(double txPowerDbm, double rxPowerDbm) {
        double p = Math.abs(rxPowerDbm - txPowerDbm);
        // TODO use less obscure formulation, this one was obtained from Wolfram Alpha by solving for d
        return Math.exp((-285379588546205536L * alpha * C - 827600806783996032L * alpha *
                receiverHeight - 639250278343500416L * alpha * transmitterHeight + 1318453699083469568L
                * alpha + 285379588546205536L * C - 285379588546205536L * p - 570759177092411072L *
                receiverHeight + 285379588546205536L * getReferenceLoss()) / (1288963317755749376L *
                alpha - 4957551222137499648L));

    }

    public void setIsUrban(boolean isUrban) {
        C = (isUrban) ? 0 : -4;
    }

    public void setLosProbability(double losProbability) {
        this.alpha = losProbability;
    }

    public void setTransmitterHeight(double transmitterHeight) {
        this.transmitterHeight = transmitterHeight;
    }

    public void setReceiverHeight(double receiverHeight) {
        this.receiverHeight = receiverHeight;
    }
}
