package info.nightscout.androidaps.plugins.general.automation.triggers;


import android.view.ViewGroup;
import android.widget.LinearLayout;

import androidx.annotation.StringRes;
import androidx.fragment.app.FragmentManager;

import com.google.common.base.Optional;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.logging.L;
import info.nightscout.androidaps.plugins.general.automation.dialogs.TriggerListAdapter;
import info.nightscout.androidaps.utils.JsonHelper;

public class TriggerConnector extends Trigger {
    private static Logger log = LoggerFactory.getLogger(L.AUTOMATION);

    public enum Type {
        AND,
        OR,
        XOR;

        public boolean apply(boolean a, boolean b) {
            switch (this) {
                case AND:
                    return a && b;
                case OR:
                    return a || b;
                case XOR:
                    return a ^ b;
            }
            return false;
        }

        public @StringRes
        int getStringRes() {
            switch (this) {
                case OR:
                    return R.string.or;
                case XOR:
                    return R.string.xor;

                default:
                case AND:
                    return R.string.and;
            }
        }

        public static List<String> labels() {
            List<String> list = new ArrayList<>();
            for (Type t : values()) {
                list.add(MainApp.gs(t.getStringRes()));
            }
            return list;
        }
    }

    public static void fillIconSet(TriggerConnector connector, HashSet<Integer> set) {
        for (Trigger t : connector.list) {
            if (t instanceof TriggerConnector) {
                fillIconSet((TriggerConnector) t, set);
            } else {
                Optional<Integer> icon = t.icon();
                if (icon.isPresent()) {
                    set.add(icon.get());
                }
            }
        }
    }

    protected List<Trigger> list = new ArrayList<>();
    private Type connectorType;

    public TriggerConnector() {
        connectorType = Type.AND;
    }

    public TriggerConnector(Type connectorType) {
        this.connectorType = connectorType;
    }

    public void changeConnectorType(Type type) {
        this.connectorType = type;
    }

    public Type getConnectorType() {
        return connectorType;
    }

    public synchronized void add(Trigger t) {
        list.add(t);
        t.connector = this;
    }

    public synchronized void add(int pos, Trigger t) {
        list.add(pos, t);
        t.connector = this;
    }

    public synchronized boolean remove(Trigger t) {
        return list.remove(t);
    }

    public int size() {
        return list.size();
    }

    public Trigger get(int i) {
        return list.get(i);
    }

    public int pos(Trigger trigger) {
        for (int i = 0; i < list.size(); ++i) {
            if (list.get(i) == trigger) return i;
        }
        return -1;
    }

    @Override
    public synchronized boolean shouldRun() {
        boolean result = true;

        // check first trigger
        if (list.size() > 0)
            result = list.get(0).shouldRun();

        // check all others
        for (int i = 1; i < list.size(); ++i) {
            result = connectorType.apply(result, list.get(i).shouldRun());
        }
        if (result)
            if (L.isEnabled(L.AUTOMATION))
                log.debug("Ready for execution: " + friendlyDescription().replace("\n", " "));

        return result;
    }

    @Override
    public synchronized String toJSON() {
        JSONObject o = new JSONObject();
        try {
            o.put("type", TriggerConnector.class.getName());
            JSONObject data = new JSONObject();
            data.put("connectorType", connectorType.toString());
            JSONArray array = new JSONArray();
            for (Trigger t : list) {
                array.put(t.toJSON());
            }
            data.put("triggerList", array);
            o.put("data", data);
        } catch (JSONException e) {
            log.error("Unhandled exception", e);
        }
        return o.toString();
    }

    @Override
    Trigger fromJSON(String data) {
        try {
            JSONObject d = new JSONObject(data);
            connectorType = Type.valueOf(JsonHelper.safeGetString(d, "connectorType"));
            JSONArray array = d.getJSONArray("triggerList");
            list.clear();
            for (int i = 0; i < array.length(); i++) {
                Trigger newItem = instantiate(new JSONObject(array.getString(i)));
                add(newItem);
            }
        } catch (JSONException e) {
            log.error("Unhandled exception", e);
        }
        return this;
    }

    @Override
    public int friendlyName() {
        return connectorType.getStringRes();
    }

    @Override
    public String friendlyDescription() {
        int counter = 0;
        StringBuilder result = new StringBuilder();
        for (Trigger t : list) {
            if (counter++ > 0) result.append("\n").append(MainApp.gs(friendlyName())).append("\n");
            result.append(t.friendlyDescription());
        }
        return result.toString();
    }

    @Override
    public Optional<Integer> icon() {
        return Optional.absent();
    }

    @Override
    public void executed(long time) {
        for (int i = 0; i < list.size(); ++i) {
            list.get(i).executed(time);
        }
    }

    @Override
    public Trigger duplicate() {
        return null;
    }

    private TriggerListAdapter adapter;

    public void rebuildView(FragmentManager fragmentManager) {
        if (adapter != null)
            adapter.rebuild(fragmentManager);
    }

    @Override
    public void generateDialog(LinearLayout root, FragmentManager fragmentManager) {
        final int padding = MainApp.dpToPx(5);

        root.setPadding(padding, padding, padding, padding);
        root.setBackgroundResource(R.drawable.border_automation_unit);

        LinearLayout triggerListLayout = new LinearLayout(root.getContext());
        triggerListLayout.setOrientation(LinearLayout.VERTICAL);
        triggerListLayout.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        root.addView(triggerListLayout);

        adapter = new TriggerListAdapter(fragmentManager, root.getContext(), triggerListLayout, this);
    }

    public TriggerConnector simplify() {
        // simplify children
        for (int i = 0; i < size(); ++i) {
            if (get(i) instanceof TriggerConnector) {
                TriggerConnector t = (TriggerConnector) get(i);
                t.simplify();
            }
        }

        // drop connector with only 1 element
        if (size() == 1 && get(0) instanceof TriggerConnector) {
            TriggerConnector c = (TriggerConnector) get(0);
            remove(c);
            changeConnectorType(c.getConnectorType());
            for (Trigger t : c.list) {
                add(t);
            }
            c.list.clear();
            return simplify();
        }

        // merge connectors
        if (connector != null && (connector.getConnectorType().equals(connectorType) || size() == 1)) {
            final int pos = connector.pos(this);
            connector.remove(this);
            // move triggers of child connector into parent connector
            for (int i = size() - 1; i >= 0; --i) {
                connector.add(pos, get(i));
            }
            list.clear();
            return connector.simplify();
        }

        return this;
    }
}