package o2mc.io.dimmldependency;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

import o2mc.io.dimmldependency.Datastreams.DataContainer;
import o2mc.io.dimmldependency.Datastreams.Datastream;

/**
 * Created by nickyromeijn on 02-06-16.
 * App tagging class for dispatching key->value pairs to set endpoint
 * Methods from this class can be used from throughout your android app
 */
public class Tracker {
    private String trackingId;
    private String alias = "";
    private String identity = "";
    private String locationString = "";
    private JSONObject locationObject = null;
    private boolean timerHasStarted = false;
    private HashMap<String, ArrayList<JSONObject>> funnel = new HashMap<>();
    private final Datastream ds;
    private Timer timer = new Timer();

    public Tracker(Datastream datastream) {
        ds = datastream;
        ds.setIdentity("");
        try {
            UUID uuid =  UUID.randomUUID();
            addToFunnel("alias", new JSONObject().put("alias", uuid).put("identity", this.identity));
            alias = uuid.toString();
            ds.setAlias(alias);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private void addToFunnel(String key, JSONObject props) {
        if (funnel.get(key) == null) funnel.put(key, new ArrayList<JSONObject>());
        funnel.get(key).add(props);
        Log.e("Added to funnel", key+": "+props.toString());

    }

    private void updateFunnel(String key, int index, JSONObject props) {
        funnel.get(key).set(index, props);
        Log.e("Updated in funnel", key + ": " + props.toString());
    }


    public void track(String eventName) {
        JSONObject obj = new JSONObject();
        try {
            obj.put("event", eventName);
            obj.put("alias", alias);
            obj.put("identity", identity);
            obj.put("location", this.getLocation());
            //obj.put("time", getTimestamp());
            addToFunnel(eventName, obj);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void trackWithProperties(String eventName, String propertiesAsJson) {
        JSONObject obj = new JSONObject();
        try {
            JSONObject propertiesAsJsonObject = new JSONObject(propertiesAsJson);
            obj.put("event", eventName);
            obj.put("alias", alias);
            obj.put("identity", identity);
            obj.put("location", this.getLocation());
            //obj.put("time", getTimestamp());
            obj.put("properties", propertiesAsJsonObject);
            addToFunnel(eventName, obj);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void createAlias(String alias) {
        this.alias = alias;
        ds.setAlias(alias);
        JSONObject obj = new JSONObject();
        try {
            obj.put("event", "alias");
            obj.put("identity", identity);
            obj.put("location", this.getLocation());
            //obj.put("time", getTimestamp());
            obj.put("properties", alias);
            addToFunnel("alias", obj);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void identify(String identifier) {
        try {
            ds.setIdentity(identifier);
            identity = identifier;
            addToFunnel("identity", new JSONObject().put("identity", identifier).put("alias", this.alias).put("location", this.getLocation()));
//            updateFunnel("identity", 0, new JSONObject().put("identity", identifier).put("alias", this.alias));
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private String timedEvent = "";
    private JSONObject timedEventProperties = new JSONObject();
    private String startTime = "";

    public void timeEventStart(String eventName) {
        startTime = getTimestamp();
        timedEvent = eventName;
    }

    private String stopTime = "";

    public void timeEventStop(String eventName) {
        if (eventName.equals(timedEvent)) {
            stopTime = getTimestamp();
            try {
                addToFunnel(timedEvent, new JSONObject().put("timeStart", startTime).put("timeStop", stopTime).put("identity",identity).put("alias",alias).put("location", this.getLocation()));
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

    public void timeEventStart(String eventName, String propertiesAsJson) {
        startTime = getTimestamp();
        timedEvent = eventName;
        try {
            timedEventProperties = new JSONObject(propertiesAsJson);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void timeEventStop(String eventName, String propertiesAsJson) {
        if (eventName.equals(timedEvent)) {
            stopTime = getTimestamp();
            try {
                JSONObject propertiesAsJsonObject = new JSONObject(propertiesAsJson);
                JSONObject obj = new JSONObject().put("stopProperties", propertiesAsJsonObject).put("location", this.getLocation());
                obj.put("alias", alias);
                obj.put("identity", identity);
                obj.put("startProperties", timedEventProperties);
                addToFunnel(timedEvent, obj.put("timeStart", startTime).put("timeStop", stopTime));
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }
    }

    public void trackCharge(double amount, String propertiesAsJson) {

    }

    public void trackLocation(String location){
        if(isJSONValid(location)){
            try {
                locationObject = new JSONObject(location);
                locationString = "";
            } catch (JSONException e) {
                e.printStackTrace();
            }
        } else {
            locationObject = null;
            locationString = location;
        }
    }

    public void reset() {
        ds.getDatastreamsHandler().getDispatcher().callback();
        funnel.clear();
    }

    public void setEndpoint(String endpoint) {
        ds.getSettings().setEndpoint(endpoint);
    }

    public void setDispatchInterval(int interval) {
        try {
            if(timerHasStarted) {
                timer.cancel();
                timer.purge();
            }
            timer.schedule(new Dispatcher(), interval * 1000, interval * 1000);
        } catch(IllegalStateException e){
            Log.e("DISPATCHTIMER", e.getMessage());
        }
    }

    private String getTimestamp() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); // Quoted "Z" to indicate UTC, no timezone offset
        return df.format(new Date());
    }

    class Dispatcher extends TimerTask {
        public void run() {
            Iterator it = funnel.entrySet().iterator();
            while (it.hasNext()) {

                Map.Entry pair = (Map.Entry) it.next();

                for (JSONObject obj : funnel.get(pair.getKey())) {
                    DataContainer c = new DataContainer();
                    c.setEventType((String) pair.getKey());
                    c.setValue(obj);
                    c.setTimestamp();
                    ds.getDatastreamsHandler().getDispatcher().dispatch(c);
                }
//                System.out.println(pair.getKey() + " = " + pair.getValue());
                it.remove(); // avoids a ConcurrentModificationException
            }
            ds.getDatastreamsHandler().getDispatcher().dispatchNow(ds.getGeneralInfo());
            funnel.clear();
        }
    }

    private Object getLocation(){
        if(locationObject != null) return locationObject;
        if(locationString.equals("")) return locationString;
        return null;
    }

    public void setTrackingId(String trackingId) {
        this.trackingId = trackingId;
    }

    public boolean isJSONValid(String test) {
        try {
            new JSONObject(test);
        } catch (JSONException ex) {
            // edited, to include @Arthur's comment
            // e.g. in case JSONArray is valid as well...
            try {
                new JSONArray(test);
            } catch (JSONException ex1) {
                return false;
            }
        }
        return true;
    }
}
