package net.heartshield.data;

import android.icu.util.Measure;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;

import net.heartshield.util.JsonUtil;

import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.TimeZone;

/**
 * Data model for a single measurement.
 * Includes waveform data and metadata (time, device, user age/gender/risk factors/annotations)
 *
 * For server sync, a Measurement is uniquely identified globally by MeasurementId (app_id, start_time).
 *
 * @author David Madl (git@abanbytes.eu)
 * @date 2016-04-06
 */
public class Measurement {
    // DTO: all public members are serialized to JSON (both for local persistence and to transmit to remote server)

    /** start time of the measurement in UTC seconds since the Unix epoch (Unix timestamp) */
    public final long startTime;

    /** globally unique identifier of the client app */
    public final String appId;

    public MeasurementMeta meta;
    public AppMeta app = null;
    public UserMeta user = null;
    public MeasurementSeries series = null;
    public DoctorDetails doctorDetails = null;
    public MeasurementResult result = null;

    /** whether this Measurement has been changed, and should be transmitted to the server. */
    @NoRemote
    public boolean dirty = true;

    public Measurement(String appId, long startTime, TimeZone timeZone) {
        this.startTime = startTime;
        this.appId = appId;
        this.meta = new MeasurementMeta(timeZone);
    }

    public Measurement(String appId, long unixTimestamp) {
        this(appId, unixTimestamp, TimeZone.getDefault());
    }

    public Measurement(String appId) {
        this(appId, System.currentTimeMillis() / 1000, TimeZone.getDefault());
    }

    public MeasurementId getId() {
        return new MeasurementId(appId, startTime);
    }

    public MeasurementSummary getSummary() {
        if(result != null && result.status == MeasurementResult.Status.OK) {
            return new MeasurementSummary(startTime, appId, MeasurementSummary.Status.OK, getRiskLevel(), (int) (100.0 * result.pred), (int) result.getHeartRate(), series.audioSynced, dirty);
        } else if(result != null && result.status == MeasurementResult.Status.UNCLASSIFIED) {
            return new MeasurementSummary(startTime, appId, MeasurementSummary.Status.UNCLASSIFIED, getRiskLevel(), 0, 0, series.audioSynced, dirty);
        } else {
            boolean audioSynced = series != null && series.audioSynced;
            return new MeasurementSummary(startTime, appId, MeasurementSummary.Status.OFFLINE, getRiskLevel(), 0, 0, audioSynced, dirty);
        }
    }

    public int getRiskLevel() {
        if(result == null)
            return 0;
        return result.getRiskLevel();
    }

    /** @return whether this Measurement has been transmitted before, and hence has a result */
    public boolean hasResult() { return result != null; }

    /** @return false if this Measurement needs to be transferred to the server. */
    public boolean isSynced() {
        return result != null && series.audioSynced;
    }

    public String serialize(JsonUtil.Target target) {
        return JsonUtil.getGson(target).toJson(this);
    }

    public static Measurement deserialize(String json) {
        return JsonUtil.getGson().fromJson(json, Measurement.class);
    }

    @Override
    public boolean equals(Object obj) {
        if(obj == null || !(obj instanceof Measurement))
            return false;
        Measurement other = (Measurement) obj;
        return (this.startTime == other.startTime && this.appId.equals(other.appId));
    }
}
