/**
 * Copyright (c) 2014-2015 openHAB UG (haftungsbeschraenkt) and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.scene;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.listener.SceneStatusListener;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.devices.Device;
import org.eclipse.smarthome.binding.digitalstrom.internal.lib.structure.scene.constants.SceneTypes;

/**
 * The {@link InternalScene} represents a digitalSTROM-Scene for the internal model.
 *
 * @author Michael Ochel - Initial contribution
 * @author Matthias Siegele - Initial contribution
 */
public class InternalScene {

    private final Short SCENE_ID;
    private final Short GROUP_ID;
    private final Integer ZONE_ID;
    private String SceneName;
    private final String INTERNAL_SCENE_ID;
    private boolean active = false;
    private String sceneType = SceneTypes.GROUP_SCENE;

    private List<Device> devices = Collections.synchronizedList(new LinkedList<Device>());
    private SceneStatusListener listener = null;

    /**
     * Creates a new {@link InternalScene} with the given parameters. Only the <i>sceneID</i> must not be null. If the
     * <i>sceneName</i> is null, the internal scene id will be set as name in format "[zoneID]-[groupID]-[sceneID]". If
     * the
     * <i>zoneID</i> and/or the <i> groupID</i> is null, the broadcast address 0 will be set.
     *
     * @param zoneID can be null
     * @param groupID can be null
     * @param sceneID must not be null
     * @param sceneName can be null
     */
    public InternalScene(Integer zoneID, Short groupID, Short sceneID, String sceneName) {
        if (sceneID == null) {
            throw new IllegalArgumentException("The parameter sceneID can't be null!");
        }
        this.SCENE_ID = sceneID;
        if (groupID == null) {
            this.GROUP_ID = 0;
        } else {
            this.GROUP_ID = groupID;
        }
        if (zoneID == null) {
            this.ZONE_ID = 0;
        } else {
            this.ZONE_ID = zoneID;
        }
        this.INTERNAL_SCENE_ID = this.ZONE_ID + "-" + this.GROUP_ID + "-" + this.SCENE_ID;
        if (StringUtils.isBlank(sceneName)) {
            this.SceneName = this.INTERNAL_SCENE_ID;
        } else {
            this.SceneName = sceneName;
        }
        if ((sceneName != this.INTERNAL_SCENE_ID) && !sceneName.contains("Apartment-Scene: ")
                && !sceneName.contains("Zone-Scene: Zone:")
                && !(sceneName.contains("Zone: ") && sceneName.contains("Group: ") && sceneName.contains("Scene: "))) {
            sceneType = SceneTypes.NAMED_SCENE;
        } else if (this.ZONE_ID == 0) {
            sceneType = SceneTypes.APARTMENT_SCENE;
        } else if (this.GROUP_ID == 0) {
            sceneType = SceneTypes.ZONE_SCENE;
        }
    }

    /**
     * Activates this Scene.
     */
    public void activateScene() {
        this.active = true;
        informListener();
        if (this.devices != null) {
            for (Device device : this.devices) {
                device.callInternalScene(this);
            }
        }
    }

    /**
     * Deactivates this Scene.
     */
    public void deactivateScene() {
        this.active = false;
        informListener();
        if (this.devices != null) {
            for (Device device : this.devices) {
                device.undoInternalScene();
            }
        }
    }

    private void informListener() {
        if (this.listener != null) {
            listener.onSceneStateChanged(this.active);
        }
    }

    /**
     * Returns true, if this scene is active, otherwise false.
     *
     * @return Scene is active? (true = yes | false = no)
     */
    public boolean isActive() {
        return this.active;
    }

    /**
     * Adds an affected {@link Device} to this {@link InternalScene} device list.
     *
     * @param device
     */
    public void addDevice(Device device) {
        if (!this.devices.contains(device)) {
            this.devices.add(device);
        }
        int prio = 0;
        if (this.listener != null) {
            prio = 1000;
        } else {
            prio = 2000;
        }
        device.checkSceneConfig(SCENE_ID, prio);
    }

    /**
     * Overrides the existing device list of this {@link InternalScene} with a new reference to a {@link List} of
     * affected {@link Device}'s.
     *
     * @param deviceList
     */
    public void addReferenceDevices(List<Device> deviceList) {
        this.devices = deviceList;
        checkDeviceSceneConfig();
    }

    /**
     * Proves, if the scene configuration is saved to all {@link Device}'s. If not, the device initials the reading out
     * of the missing configuration in the following priority steps:
     * <ul>
     * <li>low priority, if no listener is added.</li>
     * <li>medium priority, if a listener is added.</li>
     * <li>high priority, if this scene has been activated.</li>
     * </ul>
     */
    public void checkDeviceSceneConfig() {
        int prio = 0;
        if (this.listener != null) {
            prio = 1000;
        } else {
            prio = 2000;
        }
        if (devices != null) {
            for (Device device : devices) {
                device.checkSceneConfig(SCENE_ID, prio);
            }
        }
    }

    /**
     * Returns the list of the affected {@link Device}'s.
     *
     * @return device list
     */
    public List<Device> getDeviceList() {
        return this.devices;

    }

    /**
     * Adds a {@link List} of affected {@link Device}'s.
     *
     * @param deviceList
     */
    public void addDevices(List<Device> deviceList) {
        for (Device device : deviceList) {
            addDevice(device);
        }
    }

    /**
     * Removes a not anymore affected {@link Device} from the device list.
     *
     * @param device
     */
    public void removeDevice(Device device) {
        this.devices.remove(device);
    }

    /**
     * Updates the affected {@link Device}'s with the given deviceList.
     *
     * @param deviceList
     */
    public void updateDeviceList(List<Device> deviceList) {
        if (!this.devices.equals(deviceList)) {
            this.devices.clear();
            addDevices(deviceList);
        }
    }

    /**
     * This method has a device to call, if this scene was activated and the device state has changed.
     *
     * @param sceneNumber
     */
    public void deviceSceneChanged(short sceneNumber) {
        if (this.SCENE_ID != sceneNumber) {
            if (active) {
                active = false;
                informListener();
            }
        }
    }

    /**
     * Returns the Scene name.
     *
     * @return scene name
     */
    public String getSceneName() {
        return SceneName;
    }

    /**
     * Sets the scene name.
     *
     * @param sceneName
     */
    public void setSceneName(String sceneName) {
        SceneName = sceneName;
    }

    /**
     * Returns the Scene id of this scene call.
     *
     * @return scene id
     */
    public Short getSceneID() {
        return SCENE_ID;
    }

    /**
     * Returns the group id of this scene call.
     *
     * @return group id
     */
    public Short getGroupID() {
        return GROUP_ID;
    }

    /**
     * Returns the zone id of this scene call.
     *
     * @return zone id
     */
    public Integer getZoneID() {
        return ZONE_ID;
    }

    /**
     * Returns the id of this scene call.
     *
     * @return scene call id
     */
    public String getID() {
        return INTERNAL_SCENE_ID;
    }

    /**
     * Registers a {@link SceneStatusListener} to this {@link InternalScene}.
     *
     * @param listener
     */
    public synchronized void registerSceneListener(SceneStatusListener listener) {
        this.listener = listener;
        this.listener.onSceneAdded(this);
        checkDeviceSceneConfig();

    }

    /**
     * Unregisters the {@link SceneStatusListener} from this {@link InternalScene}.
     */
    public synchronized void unregisterSceneListener() {
        if (listener != null) {
            // this.listener.onSceneRemoved(this);
            this.listener = null;
        }
    }

    /**
     * Returns the scene type.
     * <br>
     * <b>Note:</b>
     * The valid Scene types can be found at {@link SceneTypes}.
     *
     * @return sceneType
     */
    public String getSceneType() {
        return this.sceneType;
    }

    @Override
    public String toString() {
        return "NamedScene [SceneName=" + SceneName + ", NAMED_SCENE_ID=" + INTERNAL_SCENE_ID + "]";
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((INTERNAL_SCENE_ID == null) ? 0 : INTERNAL_SCENE_ID.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof InternalScene)) {
            return false;
        }
        InternalScene other = (InternalScene) obj;
        if (INTERNAL_SCENE_ID == null) {
            if (other.getID() != null) {
                return false;
            }
        } else if (!INTERNAL_SCENE_ID.equals(other.getID())) {
            return false;
        }
        return true;
    }
}
