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

import org.teleal.cling.binding.annotations.UpnpAction;
import org.teleal.cling.binding.annotations.UpnpInputArgument;
import org.teleal.cling.binding.annotations.UpnpOutputArgument;
import org.teleal.cling.binding.annotations.UpnpService;
import org.teleal.cling.binding.annotations.UpnpServiceId;
import org.teleal.cling.binding.annotations.UpnpServiceType;
import org.teleal.cling.binding.annotations.UpnpStateVariable;
import org.teleal.cling.binding.annotations.UpnpStateVariables;
import org.teleal.cling.model.types.ErrorCode;
import org.teleal.cling.model.types.UnsignedIntegerFourBytes;
import org.teleal.cling.model.types.UnsignedIntegerTwoBytes;
import org.teleal.cling.support.lastchange.LastChange;
import org.teleal.cling.support.renderingcontrol.event.Loudness;
import org.teleal.cling.support.renderingcontrol.event.Mute;
import org.teleal.cling.support.renderingcontrol.event.Volume;
import org.teleal.cling.support.renderingcontrol.event.VolumeDB;

import java.beans.PropertyChangeSupport;
import java.math.BigInteger;
import java.util.logging.Logger;

/**
 *
 */
@UpnpService(
        serviceId = @UpnpServiceId("RenderingControl"),
        serviceType = @UpnpServiceType(value = "RenderingControl", version = 1),
        stringConvertibleTypes = LastChange.class
)
@UpnpStateVariables({
        @UpnpStateVariable(
                name = "PresetNameList",
                sendEvents = false,
                datatype = "string"),
        @UpnpStateVariable(
                name = "Mute",
                sendEvents = false,
                datatype = "boolean"),
        @UpnpStateVariable(
                name = "Volume",
                sendEvents = false,
                datatype = "ui2",
                allowedValueMinimum = 0,
                allowedValueMaximum = 256),
        @UpnpStateVariable(
                name = "VolumeDB",
                sendEvents = false,
                datatype = "i2"),
        @UpnpStateVariable(
                name = "Loudness",
                sendEvents = false,
                datatype = "boolean"),
        @UpnpStateVariable(
                name = "A_ARG_TYPE_Channel",
                sendEvents = false,
                allowedValuesEnum = Channel.class),
        @UpnpStateVariable(
                name = "A_ARG_TYPE_PresetName",
                sendEvents = false,
                allowedValuesEnum = PresetName.class),
        @UpnpStateVariable(
                name = "A_ARG_TYPE_InstanceID",
                sendEvents = false,
                datatype = "ui4")

})
public abstract class AbstractAudioRenderingControl {

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

    @UpnpStateVariable(eventMaximumRateMilliseconds = 200)
    final private LastChange lastChange = new RenderingControlLastChange();

    final protected boolean actionsFireEvents;
    final protected PropertyChangeSupport propertyChangeSupport;

    public AbstractAudioRenderingControl(boolean actionsFireEvents) {
        this.propertyChangeSupport = new PropertyChangeSupport(this);
        this.actionsFireEvents = actionsFireEvents;
    }

    public AbstractAudioRenderingControl() {
        this(false);
    }

    public LastChange getLastChange() {
        return lastChange;
    }

    public void fireLastChange() {
        getLastChange().fire(getPropertyChangeSupport());
    }

    public PropertyChangeSupport getPropertyChangeSupport() {
        return propertyChangeSupport;
    }

    public static UnsignedIntegerFourBytes getDefaultInstanceID() {
        return new UnsignedIntegerFourBytes(0);
    }

    @UpnpAction(out = @UpnpOutputArgument(name = "CurrentPresetNameList", stateVariable = "PresetNameList"))
    public String listPresets(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId) throws RenderingControlException {
        return getInstance(instanceId).getPresetNameList();
    }

    @UpnpAction
    public void selectPreset(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                             @UpnpInputArgument(name = "PresetName") String presetName) throws RenderingControlException {
        getInstance(instanceId).selectPreset(presetName);
    }

    @UpnpAction(out = @UpnpOutputArgument(name = "CurrentMute", stateVariable = "Mute"))
    public boolean getMute(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                           @UpnpInputArgument(name = "Channel") String channelName) throws RenderingControlException {
        return getInstance(instanceId).getMute(getChannel(channelName));
    }

    @UpnpAction
    public void setMute(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                        @UpnpInputArgument(name = "Channel") String channelName,
                        @UpnpInputArgument(name = "DesiredMute", stateVariable = "Mute") boolean desiredMute) throws RenderingControlException {
        getInstance(instanceId).setMute(getChannel(channelName), desiredMute);

        if (actionsFireEvents) {
            Mute mute = new Mute();
            mute.setChannel(channelName);
            mute.setVal(desiredMute);
            getLastChange().setStateVariableFire(instanceId, mute, getPropertyChangeSupport());
        }
    }

    @UpnpAction(out = @UpnpOutputArgument(name = "CurrentVolume", stateVariable = "Volume"))
    public UnsignedIntegerTwoBytes getVolume(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                                             @UpnpInputArgument(name = "Channel") String channelName) throws RenderingControlException {
        return new UnsignedIntegerTwoBytes(getInstance(instanceId).getVolume(getChannel(channelName)));
    }

    @UpnpAction
    public void setVolume(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                          @UpnpInputArgument(name = "Channel") String channelName,
                          @UpnpInputArgument(name = "DesiredVolume", stateVariable = "Volume") UnsignedIntegerTwoBytes desiredVolume) throws RenderingControlException {
        getInstance(instanceId).setVolume(getChannel(channelName), desiredVolume.getValue().intValue());

        if (actionsFireEvents) {
            Volume volume = new Volume();
            volume.setChannel(channelName);
            volume.setVal(desiredVolume.getValue());
            getLastChange().setStateVariableFire(instanceId, volume, getPropertyChangeSupport());
        }
    }

    @UpnpAction(out = @UpnpOutputArgument(name = "CurrentVolume", stateVariable = "VolumeDB"))
    public Short getVolumeDB(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                             @UpnpInputArgument(name = "Channel") String channelName) throws RenderingControlException {
        return getInstance(instanceId).getVolumeDB(getChannel(channelName));
    }

    @UpnpAction
    public void setVolumeDB(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                            @UpnpInputArgument(name = "Channel") String channelName,
                            @UpnpInputArgument(name = "DesiredVolume", stateVariable = "VolumeDB") Short desiredVolumeDB) throws RenderingControlException {
        getInstance(instanceId).setVolumeDB(getChannel(channelName), desiredVolumeDB);

        if (actionsFireEvents) {
            VolumeDB volumeDB = new VolumeDB();
            volumeDB.setChannel(channelName);
            volumeDB.setVal(new BigInteger(desiredVolumeDB.toString()));
            getLastChange().setStateVariableFire(instanceId, volumeDB, getPropertyChangeSupport());
        }
    }

    @UpnpAction(out = {
            @UpnpOutputArgument(name = "MinValue", stateVariable = "VolumeDB", getterName = "getMinValue"),
            @UpnpOutputArgument(name = "MaxValue", stateVariable = "VolumeDB", getterName = "getMaxValue")
    })
    public VolumeDBRange getVolumeDBRange(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                                          @UpnpInputArgument(name = "Channel") String channelName) throws RenderingControlException {
        return getInstance(instanceId).getVolumeDBRange(getChannel(channelName));
    }

    @UpnpAction(out = @UpnpOutputArgument(name = "CurrentLoudness", stateVariable = "Loudness"))
    public boolean getLoudness(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                               @UpnpInputArgument(name = "Channel") String channelName) throws RenderingControlException {
        return getInstance(instanceId).getLoudness(getChannel(channelName));
    }

    @UpnpAction
    public void setLoudness(@UpnpInputArgument(name = "InstanceID") UnsignedIntegerFourBytes instanceId,
                            @UpnpInputArgument(name = "Channel") String channelName,
                            @UpnpInputArgument(name = "DesiredLoudness", stateVariable = "Loudness") boolean desiredLoudness) throws RenderingControlException {
        getInstance(instanceId).setLoudness(getChannel(channelName), desiredLoudness);

        if (actionsFireEvents) {
            Loudness loudness = new Loudness();
            loudness.setChannel(channelName);
            loudness.setVal(desiredLoudness);
            getLastChange().setStateVariableFire(instanceId, loudness, getPropertyChangeSupport());
        }
    }

    protected AbstractAudioRenderingControl getInstance(UnsignedIntegerFourBytes instanceId) throws RenderingControlException {
        if (!instanceId.equals(getDefaultInstanceID())) {
            throw new RenderingControlException(RenderingControlErrorCode.INVALID_INSTANCE_ID);
        }
        return this;
    }

    protected Channel getChannel(String channelName) throws RenderingControlException {
        try {
            return Channel.valueOf(channelName);
        } catch (IllegalArgumentException ex) {
            throw new RenderingControlException(ErrorCode.ARGUMENT_VALUE_INVALID, "Unsupported audio channel: " + channelName);
        }
    }

    public abstract boolean getMute(Channel channel) throws RenderingControlException;

    protected abstract void setMute(Channel channel, boolean desiredMute) throws RenderingControlException;

    public abstract int getVolume(Channel channel) throws RenderingControlException;

    protected abstract void setVolume(Channel channel, int desiredVolume) throws RenderingControlException;

    public abstract Short getVolumeDB(Channel channel) throws RenderingControlException;

    protected abstract void setVolumeDB(Channel channel, Short desiredVolume) throws RenderingControlException;

    public abstract VolumeDBRange getVolumeDBRange(Channel channel) throws RenderingControlException;

    public abstract boolean getLoudness(Channel channel) throws RenderingControlException;

    protected abstract void setLoudness(Channel channel, boolean desiredLoudness) throws RenderingControlException;

    public abstract String getPresetNameList() throws RenderingControlException;

    public abstract void selectPreset(String presetName) throws RenderingControlException;

}
