/*
 * "Copyright (c) 2009 Cork Institute of Technology, Ireland
 * All rights reserved."
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without written agreement is
 * hereby granted, provided that the above copyright notice, the following
 * two paragraphs and the author appear in all copies of this software.
 *
 * IN NO EVENT SHALL THE CORK INSTITUTE OF TECHNOLOGY BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE CORK INSTITUTE
 * OF TECHNOLOGY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE CORK INSTITUTE OF TECHNOLOGY SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS."
 */
package avrora.sim.radio;

import java.util.*;
import avrora.sim.state.Complex;
import avrora.sim.radio.Topology;

/**
 * The <code>LossyModel</code> definition.
 *
 * @author Rodolfo de Paz
 */
public class LossyModel implements Medium.Arbitrator {

    protected static final double Sensitivity = -95;
    protected int TimeBefore = 0;
    protected final Map<Object,Topology.Position> positions;
    protected final double lambda = Math.exp(-5D/6D);
    protected final double u = Math.sqrt((1-Math.pow(lambda,2D)));
    protected double Csf,Sf;
    protected boolean first = true;

    public LossyModel() {
        positions = new HashMap<Object, Topology.Position>();
    }

    public boolean lockTransmission(Medium.Receiver receiver, Medium.Transmission trans,int Milliseconds) {
        double PowerRec = computeReceivedPower(trans,receiver,Milliseconds);
        int Pn = getNoise(Milliseconds);
        if (trans.f == receiver.frequency){
            if (PowerRec > Sensitivity && PowerRec > Pn) return true;
            else return false;
        }else return false;
    }

    public char mergeTransmissions(Medium.Receiver receiver, List<Medium.Transmission> it, long bit,int Milliseconds) {
        assert it.size() > 0;
        boolean one = false;
        int value = 0;
        for (Medium.Transmission next : it) {
            if (lockTransmission(receiver, next, Milliseconds)) {
                if (one) {
                    int nval = 0xff & next.getByteAtTime(bit);
                    value |= (nval << 8) ^ (value << 8); // compute corrupted bits
                    value |= nval;
                } else {
                    one = true;
                    value = 0xff & next.getByteAtTime(bit);
                }
            }
        }
        assert one;
        return (char)value;
    }

    private static Random rn = new Random();

    public static double getGaussian(double mean, double std){
        return mean + std*rn.nextGaussian();
    }

    private double Rayleigh() {
        Complex c = new Complex(getGaussian(0,1),getGaussian(0,1));
        return Complex.abs(c);
    }

    private double Shadowing(double mean,double std,int Milliseconds){
       //Correlated shadowing computation updating values every 1second
        if (first) {
            first=false;
            return Sf = getGaussian(mean,std);
        } else {
            if (Milliseconds < 1000) {
                return Csf = Sf;
            } else {
                if ((Milliseconds - TimeBefore) > 1000) {
                    TimeBefore = Milliseconds;
                    return Csf = lambda * Csf + u * getGaussian(mean,std);
                } else {
                    return Csf;
                }
            }
        }
    }

    public void setPosition(Radio radio, Topology.Position pos) {
        positions.put(radio.getTransmitter(), pos);
        positions.put(radio.getReceiver(), pos);
    }

    public int getNoise(int index){
        if (noise.sizeNoise() == 1) return noise.getNoise(0);
        else {
                index = index % noise.sizeNoise();
        }
        return noise.getNoise(index);

    }

    protected double distance(Medium.Transmitter t, Medium.Receiver r) {
        //Distance computed from mote 3D coordinates of topology file
        double dist = 0;
        Topology.Position a = (Topology.Position)positions.get(t);
        Topology.Position b = (Topology.Position)positions.get(r);
        if ( a != null && b != null) {
            double dx = a.x - b.x;
            double dy = a.y - b.y;
            double dz = a.z - b.z;
            dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
        }
        return dist;
    }

    protected double densityObstacles(Medium.Transmitter t, Medium.Receiver r) {
        /*Density of obstacles computed as the maximum rho between transmitter and
        receiver motes inserted in topology file*/
        double rho = 0D;
        Topology.Position a = (Topology.Position)positions.get(t);
        Topology.Position b = (Topology.Position)positions.get(r);
        if ( a != null && b != null) {
            rho = Math.max(a.rho,b.rho);
        }
        return rho;
    }

    /**
     * The <code>computeReceivedPower</code> method
     * @param t transmission
     * @param receiver
     * @param Millisecons
     * @return received power (dBm)
     */
    public double computeReceivedPower(Medium.Transmission t, Medium.Receiver receiver, int Milliseconds) {
        //Indoor channel model with consideration of movement of people/obstacles
        //people density -number of people or obstacles over an occupied area- (m-2)
        double p = densityObstacles(t.origin, receiver);
        //length of ray over area with moving people(m)
        double d = distance(t.origin, receiver);
        //Time sharing between bad and good state
        double A = Math.pow((1-p),0.2*d);
        //Log-distance Pathloss model (d0,n)
        double n = 3;
        /*Reference pathloss Lo calculated with Free space formula
        double PathLoss = 20*Math.log10((4*Math.PI*d0*t.f*1E6)/(299792458));*/
        //Reference pathloss from measurements for d0=1m
        double PathLoss = 55;
        if (d > 1) PathLoss = PathLoss + 10*n*Math.log10(d);
        //Rayleigh fading
        double L_Rayleigh = Rayleigh();
        //Ricean fading
        int k = 6;//Rician k factor
        double L_Rician =(L_Rayleigh/(Math.sqrt(k))+1);
        //Shadowing
        double std = (Math.log10(55*d*p+1)/Math.log10(7)) + 0.5;
        double mean = Math.pow(3*d*p,0.7);
        double s = Shadowing(mean,std,Milliseconds);
        //double s = getGaussian(mean,std);
        double Lsf = Math.pow(10,s/20);
        //Fading computation following Lutz's model
        double Fading = 10*Math.log10(A * L_Rician + (1-A) * (Lsf*L_Rayleigh));
        return (t.Pt - Fading - PathLoss);
    }
}
