001package org.intellimate.izou.sdk.frameworks.presence.provider.template;
002
003import org.intellimate.izou.events.EventModel;
004import org.intellimate.izou.sdk.Context;
005import org.intellimate.izou.sdk.events.EventsController;
006import org.intellimate.izou.sdk.frameworks.presence.provider.Presence;
007import org.intellimate.izou.sdk.frameworks.presence.provider.PresenceHelper;
008import org.intellimate.izou.sdk.frameworks.presence.provider.PresenceIndicatorLevel;
009import org.intellimate.izou.sdk.frameworks.presence.provider.PresenceResourceProvider;
010import org.intellimate.izou.sdk.frameworks.presence.resources.PresenceResource;
011
012import java.util.ArrayList;
013import java.util.Optional;
014import java.util.concurrent.CompletableFuture;
015import java.util.concurrent.atomic.AtomicBoolean;
016
017/**
018 * the base template class for addons able to conclude about constant presence.
019 * <p>
020 * If the addon is based on encounters with the user and not able to conclude constant presence from the information,
021 * it should pass not extend this class, if not (for example if it monitors wifi) it can. They can just fire the
022 * Presence-Event on encountering.
023 * It is expected that addons extending this class are pretty sure that the user is around and not a random person.
024 * </p>
025 * @author LeanderK
026 * @version 1.0
027 */
028public abstract class PresenceConstant extends EventsController implements PresenceHelper, PresenceResourceProvider {
029    private boolean present = false;
030    private  boolean globalPresent = false;
031    private boolean globalStrictPresent = false;
032    private final boolean strict;
033    private final PresenceIndicatorLevel level;
034    /**
035     * if the PresenceIndicatorLevel is under Weak, it will control events if it is still (one of) the most Vague
036     */
037    private AtomicBoolean mostVague = new AtomicBoolean(true);
038
039    /**
040     * @param context the context
041     * @param ID      the ID
042     * @param strict  whether it is strict (very high probability that the user is around)
043     * @param level   the level
044     *
045     */
046    public PresenceConstant(Context context, String ID, boolean strict, PresenceIndicatorLevel level) {
047        super(context, ID);
048        this.strict = strict;
049        this.level = level;
050    }
051
052    /**
053     * Controls whether the fired Event should be dispatched to all the listeners. This method should execute quickly
054     *
055     * @param eventModel the Event
056     * @return true if it should dispatch, false if not
057     */
058    @Override
059    public boolean controlEvents(EventModel eventModel) {
060        if (level.compareTo(PresenceIndicatorLevel.WEAK) >= 0) {
061            return present;
062        } else //noinspection SimplifiableIfStatement
063            if (level.compareTo(PresenceIndicatorLevel.WEAK) < 0 && mostVague.get()) {
064            return present;
065        } else {
066            return true;
067        }
068    }
069
070    /**
071     * true if the addon can guarantee that the user is around, false if not
072     *
073     * @return true if it can guarantee it, false if not
074     */
075    @Override
076    public boolean isStrict() {
077        return strict;
078    }
079
080    /**
081     * whether it is known that the user caused the presence
082     *
083     * @return true if known, false if not
084     */
085    @Override
086    public boolean isKnown() {
087        return true;
088    }
089
090    /**
091     * gets the PresenceIndicatorLevel of the addon (mainly used for communication between presence-providing addons)
092     *
093     * @return the Level
094     */
095    @Override
096    public PresenceIndicatorLevel getLevel() {
097        return level;
098    }
099
100    /**
101     * returns true if the user might be/is present
102     *
103     * @return true if present
104     */
105    @Override
106    public boolean isPresent() {
107        return present;
108    }
109
110    /**
111     * when this method is called the present-status was changed
112     *
113     * @param present true if present, false if not
114     */
115    @Override
116    public void setGlobalPresent(boolean present) {
117        globalPresent = present;
118    }
119
120    /**
121     * returns true if the user is first encountered in the current mode
122     */
123    @Override
124    public boolean isFirstEncountering() {
125        if (strict && !globalStrictPresent) {
126            return true;
127        } else if (!globalPresent) {
128            return true;
129        }
130        return false;
131    }
132
133    /**
134     * when this method is called, the strict-present status was changed
135     *
136     * @param present true if present, false if not
137     */
138    @Override
139    public void setGlobalStrictPresent(boolean present) {
140        globalStrictPresent = present;
141    }
142
143    /**
144     * sets the presence
145     * @param present true for presence, false if not
146     */
147    public void setPresence(boolean present) {
148        if (this.present == present)
149            return;
150        this.present = present;
151        updateVague();
152        if (present) {
153            firePresence(true);
154        } else {
155            fireLeaving();
156        }
157    }
158
159    /**
160     * updates the boolean whether it is the mode vague
161     */
162    private void updateVague() {
163        generateResource(PresenceResource.ID)
164            .orElse(CompletableFuture.completedFuture(new ArrayList<>()))
165            .thenAccept(list -> mostVague.set(list.stream()
166                            .map(Presence::importPresence)
167                            .filter(Optional::isPresent)
168                            .map(Optional::get)
169                            .map(Presence::getLevel)
170                            .noneMatch(level -> level.compareTo(getLevel()) > 0))
171            );
172    }
173}