package org.altbeacon.beacon.service;

import android.os.SystemClock;

import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.logging.LogManager;

import java.io.Serializable;
import java.lang.reflect.Constructor;

public class RangedBeacon implements Serializable {

    private static final String TAG = "RangedBeacon";
    public static final long DEFAULT_MAX_TRACKING_AGE = 5000; /* 5 Seconds */
    public static long maxTrackingAge = DEFAULT_MAX_TRACKING_AGE; /* 5 Seconds */
    //kept here for backward compatibility
    public static final long DEFAULT_SAMPLE_EXPIRATION_MILLISECONDS = 20000; /* 20 seconds */
    private static long sampleExpirationMilliseconds = DEFAULT_SAMPLE_EXPIRATION_MILLISECONDS;
    private boolean mTracked = true;
    protected long lastTrackedTimeMillis = 0;
    Beacon mBeacon;
    protected transient RssiFilter mFilter = null;
    private int packetCount = 0;
    private long firstCycleDetectionTimestamp = 0;
    private long lastCycleDetectionTimestamp = 0;

    public RangedBeacon(Beacon beacon) {
        updateBeacon(beacon);
    }

    public void updateBeacon(Beacon beacon) {
        packetCount += 1;
        mBeacon = beacon;
        if(firstCycleDetectionTimestamp == 0) {
            firstCycleDetectionTimestamp = beacon.getFirstCycleDetectionTimestamp();
        }
        lastCycleDetectionTimestamp = beacon.getLastCycleDetectionTimestamp();
        addMeasurement(mBeacon.getRssi());
    }

    public boolean isTracked() {
        return mTracked;
    }

    public void setTracked(boolean tracked) {
        mTracked = tracked;
    }

    public Beacon getBeacon() {
        return mBeacon;
    }

    // Done at the end of each cycle before data are sent to the client
    public void commitMeasurements() {
         if (!getFilter().noMeasurementsAvailable()) {
             double runningAverage = getFilter().calculateRssi();
             mBeacon.setRunningAverageRssi(runningAverage);
             mBeacon.setRssiMeasurementCount(getFilter().getMeasurementCount());
             LogManager.d(TAG, "calculated new runningAverageRssi: %s", runningAverage);
        }
        else {
            LogManager.d(TAG, "No measurements available to calculate running average");
        }
        mBeacon.setPacketCount(packetCount);
        mBeacon.setFirstCycleDetectionTimestamp(firstCycleDetectionTimestamp);
        mBeacon.setLastCycleDetectionTimestamp(lastCycleDetectionTimestamp);
        packetCount = 0;
        firstCycleDetectionTimestamp = 0L;
        lastCycleDetectionTimestamp = 0L;
    }

    public void addMeasurement(Integer rssi) {
        // Filter out unreasonable values per
        // http://stackoverflow.com/questions/30118991/rssi-returned-by-altbeacon-library-127-messes-up-distance
        if (rssi != 127) {
            mTracked = true;
            lastTrackedTimeMillis = SystemClock.elapsedRealtime();
            getFilter().addMeasurement(rssi);
        }
    }

    //kept here for backward compatibility
    public static void setSampleExpirationMilliseconds(long milliseconds) {
        sampleExpirationMilliseconds = milliseconds;
        RunningAverageRssiFilter.setSampleExpirationMilliseconds(sampleExpirationMilliseconds);
    }

    public static void setMaxTrackinAge(int maxTrackinAge) {
        RangedBeacon.maxTrackingAge = maxTrackinAge;
    }

    public boolean noMeasurementsAvailable() {
        return getFilter().noMeasurementsAvailable();
    }

    public long getTrackingAge() {
        return SystemClock.elapsedRealtime() - lastTrackedTimeMillis;
    }

    public boolean isExpired() {
        return getTrackingAge() > maxTrackingAge;
    }

    private RssiFilter getFilter() {
        if (mFilter == null) {
            //set RSSI filter
            try {
            Constructor cons = BeaconManager.getRssiFilterImplClass().getConstructors()[0];
                mFilter = (RssiFilter)cons.newInstance();
            } catch (Exception e) {
                LogManager.e(TAG, "Could not construct RssiFilterImplClass %s", BeaconManager.getRssiFilterImplClass().getName());
            }
        }
        return mFilter;
    }

}