package com.eveningoutpost.dexdrip.Models;

/**
 * Created by jamorham on 01/02/2017.
 */


import android.provider.BaseColumns;

import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;
import com.activeandroid.query.Select;
import com.eveningoutpost.dexdrip.BestGlucose;
import com.eveningoutpost.dexdrip.UtilityModels.Constants;
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
import com.google.gson.annotations.Expose;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Table(name = "Accuracy", id = BaseColumns._ID)
public class Accuracy extends PlusModel {
    private static final String TAG = "Accuracy";
    private static boolean patched = false;
    static final String[] schema = {
            "CREATE TABLE Accuracy (_id INTEGER PRIMARY KEY AUTOINCREMENT);",
            "ALTER TABLE Accuracy ADD COLUMN timestamp INTEGER;",
            "ALTER TABLE Accuracy ADD COLUMN bg REAL;",
            "ALTER TABLE Accuracy ADD COLUMN bgtimestamp INTEGER;",
            "ALTER TABLE Accuracy ADD COLUMN bgsource TEXT;",
            "ALTER TABLE Accuracy ADD COLUMN plugin TEXT;",
            "ALTER TABLE Accuracy ADD COLUMN calculated REAL;",
            "ALTER TABLE Accuracy ADD COLUMN lag INTEGER;",
            "ALTER TABLE Accuracy ADD COLUMN difference REAL;",
            "CREATE INDEX index_Accuracy_timestamp on Accuracy(timestamp);",
            "CREATE INDEX index_Accuracy_bgtimestamp on Accuracy(bgtimestamp);"
    };

    @Expose
    @Column(name = "timestamp", index = true)
    public long timestamp;

    @Expose
    @Column(name = "bg")
    public double bg;

    @Expose
    @Column(name = "bgtimestamp", index = true)
    public long bgtimestamp;

    @Expose
    @Column(name = "bgsource")
    public String bgsource;

    @Expose
    @Column(name = "plugin")
    public String plugin;

    @Expose
    @Column(name = "calculated")
    public double calculated;

    @Expose
    @Column(name = "lag")
    public boolean lag;

    @Expose
    @Column(name = "difference")
    public double difference;

    private static final boolean d = false;

    public static Accuracy create(BloodTest bloodTest, BestGlucose.DisplayGlucose dg) {
        if (dg == null) return null;
        final BgReading from_dg = new BgReading();
        from_dg.timestamp = dg.timestamp;
        from_dg.calculated_value = dg.mgdl;
        return create(bloodTest, from_dg, dg.plugin_name);
    }


    public static Accuracy create(BloodTest bloodTest, BgReading bgReading, String plugin) {
        if ((bloodTest == null) || (bgReading == null)) return null;
        patched = fixUpTable(schema, patched);
        if (getForPreciseTimestamp(bgReading.timestamp, Constants.MINUTE_IN_MS, plugin) != null) {
            UserError.Log.d(TAG, "Duplicate accuracy timestamp for: " + JoH.dateTimeText(bgReading.timestamp));
            return null;
        }
        final Accuracy ac = new Accuracy();
        ac.timestamp = bgReading.timestamp;
        ac.bg = bloodTest.mgdl;
        ac.bgtimestamp = bloodTest.timestamp;
        ac.bgsource = bloodTest.source;
        ac.plugin = plugin;
        ac.calculated = bgReading.calculated_value;
        //ac.lag = bgReading.timestamp-bloodTest.timestamp;
        ac.difference = bgReading.calculated_value - bloodTest.mgdl;
        ac.save();
        return ac;
    }

    static Accuracy getForPreciseTimestamp(double timestamp, double precision, String plugin) {
        patched = fixUpTable(schema, patched);
        final Accuracy accuracy = new Select()
                .from(Accuracy.class)
                .where("timestamp <= ?", (timestamp + precision))
                .where("timestamp >= ?", (timestamp - precision))
                .where("plugin = ?", plugin)
                .orderBy("abs(timestamp - " + timestamp + ") asc")
                .executeSingle();
        if (accuracy != null && Math.abs(accuracy.timestamp - timestamp) < precision) {
            return accuracy;
        }
        return null;
    }

    public static List<Accuracy> latestForGraph(int number, long startTime, long endTime) {
        try {
            return new Select()
                    .from(Accuracy.class)
                    .where("timestamp >= " + Math.max(startTime, 0))
                    .where("timestamp <= " + endTime)
                    .orderBy("timestamp desc, _id asc")
                    .limit(number)
                    .execute();
        } catch (android.database.sqlite.SQLiteException e) {
            patched = fixUpTable(schema, patched);
            return new ArrayList<>();
        }
    }

    public static String evaluateAccuracy(long period) {
        // TODO CACHE ?
        final boolean domgdl = Pref.getString("units", "mgdl").equals("mgdl");
        final Map<String, Double> totals = new HashMap<>();
        final Map<String, Double> signed_totals = new HashMap<>();
        final Map<String, Integer> count = new HashMap<>();
        final List<Accuracy> alist = latestForGraph(500, JoH.tsl() - period, JoH.tsl());

        // total up differences
        for (Accuracy entry : alist) {
            if (totals.containsKey(entry.plugin)) {
                totals.put(entry.plugin, totals.get(entry.plugin) + Math.abs(entry.difference));
                signed_totals.put(entry.plugin, signed_totals.get(entry.plugin) + entry.difference);
                count.put(entry.plugin, count.get(entry.plugin) + 1);
            } else {
                totals.put(entry.plugin, Math.abs(entry.difference));
                signed_totals.put(entry.plugin, entry.difference);
                count.put(entry.plugin, 1);
            }
        }
        String result = "";
        int plugin_count = 0;
        for (Map.Entry<String, Double> total : totals.entrySet()) {
            plugin_count++;
            final String plugin = total.getKey();
            final int this_count = count.get(plugin);
            final double this_total = total.getValue();
            // calculate the abs mean, 0 = perfect
            final double this_mean = this_total / this_count;
            final double signed_total = signed_totals.get(plugin);
            final double signed_mean = signed_total / this_count;
            // calculate the bias ratio. 0% means totally unbiased, 100% means all data skewed towards signed mean
            final double signed_ratio = (Math.abs(signed_mean) / this_mean) * 100;

            if (d) UserError.Log.d(TAG, plugin + ": total: " + JoH.qs(this_total) + " count: " + this_count + " avg: " + JoH.qs(this_mean) + " mmol: " + JoH.qs((this_mean) * Constants.MGDL_TO_MMOLL) + " bias: " + JoH.qs(signed_mean) + " " + JoH.qs(signed_ratio, 0) + "%");
            String plugin_result = plugin.substring(0, 1).toLowerCase() + ": " + asString(this_mean, signed_mean, signed_ratio, domgdl);
            UserError.Log.d(TAG, plugin_result);
            if (result.length() > 0) result += " ";
            result += plugin_result;
        }

        return plugin_count == 1 ? result : result.replaceFirst(" mmol", "").replaceFirst(" mgdl", " ");
    }

    private static String asString(double mean, double signed_mean, double signed_ratio, boolean domgdl) {

        String symbol = "err";
        if (signed_ratio < 90) {
            symbol = "\u00B1";   // +- symbol
        } else {
            if (signed_mean < 0) {
                symbol = "\u207B"; // superscript minus
            } else {
                symbol = "\u207A"; // superscript plus
            }
        }
        return symbol + (!domgdl ? JoH.qs(mean * Constants.MGDL_TO_MMOLL, 2) + " mmol" : JoH.qs(mean, 1) + " mgdl");
    }

}