/*
 * Copyright (C) 2010 Teleal GmbH, Switzerland
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.teleal.cling.model.state;

import org.teleal.cling.model.Command;
import org.teleal.cling.model.ServiceManager;
import org.teleal.cling.model.meta.LocalService;
import org.teleal.cling.model.meta.StateVariable;
import org.teleal.common.util.Exceptions;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

/**
 * @author Christian Bauer
 */
public class ServiceState<T> {

    private static Logger log = Logger.getLogger(ServiceState.class.getName());

    public static final String ALL_STATE_VARIABLE_VALUES = "_allStateVariableValues";

    final protected LocalService<T> service;
    final protected PropertyChangeSupport propertyChangeSupport;

    // Synchronized!
    protected List<StateVariableValue> variableValues = new ArrayList();

    public ServiceState(LocalService<T> service, PropertyChangeSupport propertyChangeSupport) throws Exception {
        this.service = service;
        this.propertyChangeSupport = propertyChangeSupport;

        variableValues = readEventedStateVariableValues();

        getPropertyChangeSupport().addPropertyChangeListener(
                new PropertyChangeListener() {

                    public void propertyChange(PropertyChangeEvent e) {
                        log.finer("Property change event on local service: " + e.getPropertyName());

                        // Prevent recursion
                        if (e.getPropertyName().equals(ALL_STATE_VARIABLE_VALUES)) return;

                        // Is it an evented state variable?
                        final StateVariable sv = getService().getStateVariable(e.getPropertyName());
                        if (sv == null || !sv.getEventDetails().isSendEvents()) {
                            return;
                        }

                        try {
                            log.fine("Evented state variable value changed, reading state of service: " + sv);
                            List<StateVariableValue> oldValues = variableValues;
                            variableValues = readEventedStateVariableValues();

                            getPropertyChangeSupport().firePropertyChange(
                                    ALL_STATE_VARIABLE_VALUES,
                                    oldValues,
                                    variableValues
                            );

                        } catch (Exception ex) {
                            log.severe(
                                    "Error reading state of service after state variable update event: "
                                            + Exceptions.unwrap(ex)
                            );
                        }
                    }
                }
        );
    }

    synchronized public LocalService getService() {
        return service;
    }

    synchronized public PropertyChangeSupport getPropertyChangeSupport() {
        return propertyChangeSupport;
    }

    synchronized public List<StateVariableValue> readEventedStateVariableValues() throws Exception {
        if (service == null)
            throw new IllegalStateException("Call the init(LocalService) method first");

        final List<StateVariableValue> values = new ArrayList();

        getService().getManager().executeLocked(new Command() {

            public void execute(ServiceManager serviceManager) throws Exception {
                for (StateVariable stateVariable : serviceManager.getService().getStateVariables()) {
                    if (stateVariable.getEventDetails().isSendEvents()) {

                        StateVariableAccessor accessor = serviceManager.getService().getAccessor(stateVariable);
                        if (accessor == null)
                            throw new IllegalStateException("No accessor for evented state variable");

                        values.add(
                                accessor.read(
                                        stateVariable,
                                        serviceManager.getImplementation()
                                )
                        );
                    }
                }
            }

            @Override
            public String toString() {
                return "Reading evented state variables: " +  getService();
            }
        });

        return values;
    }

}
