/*
* AndFHEM - Open Source Android application to control a FHEM home automation
* server.
*
* Copyright (c) 2011, Matthias Klass or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU GENERAL PUBLIC LICENSE, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU GENERAL PUBLIC LICENSE
* for more details.
*
* You should have received a copy of the GNU GENERAL PUBLIC LICENSE
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package li.klass.fhem.domain.core;
import android.content.Context;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import org.jetbrains.annotations.NotNull;
import org.joda.time.DateTime;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import li.klass.fhem.domain.genericview.OverviewViewSettings;
import li.klass.fhem.domain.genericview.OverviewViewSettingsCache;
import li.klass.fhem.domain.genericview.ShowField;
import li.klass.fhem.domain.setlist.SetList;
import li.klass.fhem.resources.ResourceIdMapper;
import li.klass.fhem.service.room.xmllist.DeviceNode;
import li.klass.fhem.service.room.xmllist.XmlListDevice;
import li.klass.fhem.util.DateFormatUtil;
import static com.google.common.base.MoreObjects.firstNonNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static java.util.Arrays.asList;
import static li.klass.fhem.service.room.xmllist.DeviceNode.DeviceNodeType;
import static li.klass.fhem.service.room.xmllist.DeviceNode.DeviceNodeType.STATE;
public abstract class FhemDevice<T extends FhemDevice<T>> extends HookedDevice<T> implements Comparable<T> {
public static final Function<FhemDevice, XmlListDevice> TO_XMLLIST_DEVICE = new Function<FhemDevice, XmlListDevice>() {
@Override
public XmlListDevice apply(FhemDevice input) {
return input == null ? null : input.getXmlListDevice();
}
};
protected List<String> webCmd = newArrayList();
@ShowField(description = ResourceIdMapper.definition, showAfter = "roomConcatenated")
protected String definition;
protected Map<String, String> eventMapReverse = newHashMap();
protected Map<String, String> eventMap = newHashMap();
protected SetList setList = new SetList();
@ShowField(description = ResourceIdMapper.measured, showAfter = "definition")
private String measured;
private long lastMeasureTime = -1;
private DeviceFunctionality deviceFunctionality;
private OverviewViewSettings overviewViewSettingsCache;
public final OverviewViewSettings getOverviewViewSettingsCache() {
return overviewViewSettingsCache;
}
protected OverviewViewSettings getExplicitOverviewSettings() {
return null;
}
@XmllistAttribute("WEBCMD")
public void setWebcmd(String value) {
webCmd = newArrayList(value.split(":"));
}
@XmllistAttribute("DEF")
public void setDefinition(String value) {
definition = value;
}
@XmllistAttribute("EVENTMAP")
public void setEventmap(String value) {
parseEventMap(value);
}
public void afterDeviceXMLRead(Context context) {
this.definition = getDefinition();
if (deviceConfiguration.isPresent()) {
deviceFunctionality = deviceConfiguration.get().getDefaultGroup();
}
//Optimization to prevent the expensive calls to Annotations in getView()
overviewViewSettingsCache = getExplicitOverviewSettings();
if (overviewViewSettingsCache == null) {
OverviewViewSettings annotation = getClass().getAnnotation(OverviewViewSettings.class);
if (annotation != null) {
overviewViewSettingsCache = new OverviewViewSettingsCache(annotation);
}
}
}
public void afterAllXMLRead() {
}
@Override
public DeviceFunctionality getDeviceGroup() {
return deviceFunctionality == null ? DeviceFunctionality.UNKNOWN : deviceFunctionality;
}
private void parseEventMap(String content) {
eventMap = newHashMap();
eventMapReverse = newHashMap();
if (content.startsWith("/")) {
parseSlashesEventMap(content);
} else {
parseSpacesEventMap(content);
}
}
private void parseSpacesEventMap(String content) {
String[] events = content.split(" ");
for (String event : events) {
String[] eventParts = event.split(":");
if (eventParts.length < 2) continue;
putEventToEventMap(eventParts[0], eventParts[1]);
}
}
private void parseSlashesEventMap(String content) {
String[] events = content.split("/");
for (int i = 1; i < events.length; i++) {
String event = events[i];
String[] eventParts = event.split(":");
String key = eventParts[0];
String value = eventParts.length > 1 ? eventParts[1] : eventParts[0];
putEventToEventMap(key, value);
}
}
protected void putEventToEventMap(String key, String value) {
eventMap.put(key, value);
eventMapReverse.put(value, key);
}
@ShowField(description = ResourceIdMapper.deviceName, showAfter = ShowField.FIRST)
public String getAliasOrName() {
String alias = getAlias();
if (alias != null && alias.length() != 0) {
return alias;
}
return getName();
}
public String getName() {
return getXmlListDevice().getName();
}
public List<String> getRooms() {
DeviceNode room = getXmlListDevice().getAttributes().get("room");
if (room == null) {
return newArrayList("Unsorted");
}
return Arrays.asList(getRoomConcatenated().split(","));
}
public void setRooms(List<String> rooms) {
setRoomConcatenated(Joiner.on(",").join(rooms));
}
@ShowField(description = ResourceIdMapper.rooms, showAfter = "aliasOrName")
public String getRoomConcatenated() {
DeviceNode room = getXmlListDevice().getAttributes().get("room");
if (room == null) {
return "Unsorted";
}
return room.getValue();
}
public void setRoomConcatenated(String roomsConcatenated) {
getXmlListDevice().getAttributes().put("room", new DeviceNode(DeviceNodeType.ATTR, "room", roomsConcatenated, (DateTime) null));
}
/**
* Checks whether a device is in a given room.
*
* @param room room to check
* @return true if the device is in the room
*/
public boolean isInRoom(String room) {
return getRooms().contains(room);
}
public String getMeasured() {
return measured;
}
@XmllistAttribute("MEASURED")
public void setMeasured(String measuredIn) {
this.measured = DateFormatUtil.formatTime(measuredIn);
this.lastMeasureTime = DateFormatUtil.toMilliSeconds(measuredIn);
}
public void setMeasured(DateTime measuredIn) {
measuredIn = measuredIn != null ? measuredIn : DateTime.now();
this.measured = DateFormatUtil.formatTime(measuredIn);
this.lastMeasureTime = measuredIn.getMillis();
}
public long getLastMeasureTime() {
return lastMeasureTime;
}
/**
* Called for each device node in the <i>xmllist</i>.
*
* @param type contains the current tag name (i.e. State, ATTR or INT)
* @param key name of the key (i.e. ROOM)
* @param value value of the tag
* @param node additional tag node
*/
public void onChildItemRead(DeviceNodeType type, String key, String value, DeviceNode node) {
if (key.endsWith("_TIME") && !key.startsWith("WEEK") && useTimeAndWeekAttributesForMeasureTime()) {
setMeasured(value);
}
if (node.getType() == STATE && "STATE".equalsIgnoreCase(node.getKey()) && measured == null) {
setMeasured(node.getMeasured());
}
}
@XmllistAttribute("SETS")
public void setSetList(String value) {
String setsText = value.replaceAll("\\*", "");
if (isNullOrEmpty(setsText)) return;
setList.parse(setsText);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FhemDevice other = (FhemDevice) o;
String name = getName();
return (other.getName() == null && name == null) || (name != null && name.equals(other.getName()));
}
@Override
public int hashCode() {
String name = getName();
return name != null ? name.hashCode() : 0;
}
@Override
public final int compareTo(@NotNull T other) {
String comparableAttribute = firstNonNull(sortBy, getAliasOrName());
String otherComparableAttribute = firstNonNull(other.sortBy, other.getAliasOrName());
return comparableAttribute.compareTo(otherComparableAttribute);
}
@ShowField(description = ResourceIdMapper.state, showAfter = "measured")
public String getState() {
XmlListDevice xmlListDevice = getXmlListDevice();
Optional<String> state = xmlListDevice.getInternal("STATE");
return state.or(xmlListDevice.getState("state")).or("");
}
public void setState(String state) {
if (eventMap.containsKey(state)) {
state = eventMap.get(state);
}
XmlListDevice device = getXmlListDevice();
device.setState("state", state);
if (device.getInternals().containsKey("STATE")) {
device.setInternal("STATE", state);
}
if (device.getHeader().containsKey("state")) {
device.setHeader("state", state);
}
}
public String getInternalState() {
String state = getState();
if (eventMapReverse == null || !eventMapReverse.containsKey(state)) return state;
return eventMapReverse.get(state);
}
public String getAlias() {
return getXmlListDevice().getAttribute("alias").orNull();
}
public String getDefinition() {
return definition;
}
public String getEventMapStateFor(String state) {
if (eventMap.containsKey(state)) {
return eventMap.get(state);
}
return state;
}
public String getReverseEventMapStateFor(String state) {
return eventMapReverse.containsKey(state) ? eventMapReverse.get(state) : state;
}
public Map<String, String> getEventMap() {
return eventMap;
}
public SetList getSetList() {
return setList;
}
/**
* Generate an array of available target states, but respect any set event maps.
*
* @return array of available target states
*/
public String[] getAvailableTargetStatesEventMapTexts() {
if (setList == null) return new String[]{};
List<String> sortedKeys = setList.getSortedKeys();
List<String> eventMapKeys = newArrayList();
for (String key : sortedKeys) {
String eventMapKey = eventMapReverse.containsKey(key) ? eventMapReverse.get(key) : key;
eventMapKeys.add(eventMapKey);
}
return eventMapKeys.toArray(new String[eventMapKeys.size()]);
}
public String formatTargetState(String targetState) {
if (eventMapReverse != null && eventMapReverse.containsKey(targetState)) {
return eventMapReverse.get(targetState);
}
return targetState;
}
public String formatStateTextToSet(String stateToSet) {
return stateToSet;
}
public List<String> getWebCmd() {
return webCmd;
}
public String getWidgetName() {
return firstNonNull(widgetName, getAliasOrName());
}
public List<String> getInternalDeviceGroupOrGroupAttributes(Context context) {
List<String> groups = newArrayList();
DeviceNode groupAttribute = getXmlListDevice().getAttributes().get("group");
if (groupAttribute != null) {
groups.addAll(asList(groupAttribute.getValue().split(",")));
} else {
groups.add(getDeviceGroup().getCaptionText(context));
}
return groups;
}
protected boolean useTimeAndWeekAttributesForMeasureTime() {
return true;
}
@Override
public String toString() {
String name = getName();
String alias = getAlias();
return "Device{" +
", name='" + name + '\'' +
", alias='" + alias + '\'' +
", measured='" + measured + '\'' +
", definition='" + definition + '\'' +
", eventMapReverse=" + eventMapReverse +
", eventMap=" + eventMap +
", setList=" + setList.toString() +
'}';
}
}