/*
 * 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.support.lastchange;

import org.teleal.cling.model.types.UnsignedIntegerFourBytes;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.beans.PropertyChangeSupport;
import java.io.StringReader;
import java.io.StringWriter;

/**
 *
 */
public abstract class LastChange<E> {

    private E event;
    private JAXBContext jaxbContext;
    private String previousValue;

    public LastChange(String s) {
        throw new UnsupportedOperationException("This constructor is only for service binding detection");
    }

    public LastChange(E event, Class objectFactory) {
        this(event, objectFactory, null);
    }

    public LastChange(E event, Class objectFactory, String serialized) {
        this.event = event;
        try {
            jaxbContext = JAXBContext.newInstance(objectFactory);

            if (serialized != null && serialized.length() > 0) {
                Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
                this.event = (E) unmarshaller.unmarshal(new StringReader(serialized));
            }

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    synchronized public void reset() {
        previousValue = toString();
        clear(event);
    }

    synchronized public void setStateVariable(UnsignedIntegerFourBytes instanceID, Object... stateVariables) {
        for (Object variable : stateVariables) {
            if (variable != null)
                setStateVariable(event, instanceID, variable);
        }
    }

    synchronized public <V> V getStateVariable(UnsignedIntegerFourBytes instanceId, Class<V> stateVariableType) {
        return getStateVariable(event, instanceId, stateVariableType);
    }

    synchronized public void setStateVariableFire(UnsignedIntegerFourBytes instanceID, Object stateVariable, PropertyChangeSupport propertyChangeSupport) {
        setStateVariable(instanceID, stateVariable);
        fire(propertyChangeSupport);
    }

    synchronized public void fire(PropertyChangeSupport propertyChangeSupport) {
        String lastChanges = toString();
        if (lastChanges != null && lastChanges.length() > 0) {
            propertyChangeSupport.firePropertyChange("LastChange", previousValue, lastChanges);
            reset();
        }
    }

    protected abstract void clear(E event);

    protected abstract void setStateVariable(E event, UnsignedIntegerFourBytes instanceID, Object o);

    protected abstract <V> V getStateVariable(E event, UnsignedIntegerFourBytes instanceId, Class<V> stateVariableType);

    protected abstract boolean isEmptyString(E event);

    @Override
    synchronized public String toString() {
        if (isEmptyString(event)) return "";
        try {
            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.TRUE);
            //marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            StringWriter writer = new StringWriter(100);

            marshaller.marshal(event, writer);
            return writer.toString();

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }
}
