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

import org.teleal.cling.binding.LocalServiceBinder;
import org.teleal.cling.binding.annotations.AnnotationLocalServiceBinder;
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.meta.ActionArgument;
import org.teleal.cling.model.meta.DeviceDetails;
import org.teleal.cling.model.meta.DeviceService;
import org.teleal.cling.model.meta.LocalDevice;
import org.teleal.cling.model.meta.LocalService;
import org.teleal.cling.model.types.Datatype;
import org.teleal.cling.model.types.UDADeviceType;
import org.teleal.cling.model.types.UDAServiceId;
import org.teleal.cling.model.types.UDAServiceType;
import org.teleal.cling.model.types.UnsignedIntegerFourBytes;
import org.teleal.cling.test.data.SampleData;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;


public class LocalServiceBindingTest {

    public LocalDevice createTestDevice(DeviceService deviceService) throws Exception {
        return new LocalDevice(
                SampleData.createLocalDeviceIdentity(),
                new UDADeviceType("BinaryLight", 1),
                new DeviceDetails("Example Binary Light"),
                null,
                new DeviceService[]{deviceService}
        );
    }

    @DataProvider(name = "devices")
    public Object[][] getDevices() throws Exception {

        // This is what we are actually testing
        LocalServiceBinder binder = new AnnotationLocalServiceBinder();

        return new LocalDevice[][]{
                {createTestDevice(binder.read(TestServiceOne.class))},
                {createTestDevice(binder.read(TestServiceTwo.class))},
                {createTestDevice(binder.read(TestServiceThree.class))},
                {createTestDevice(binder.read(TestServiceFour.class))},
                {createTestDevice(binder.read(TestServiceFive.class))},
        };
    }

    @Test(dataProvider = "devices")
    public void validateBinding(LocalDevice device) {

        LocalService svc = SampleData.getFirstService(device);

        //System.out.println("############################################################################");
        //ServiceDescriptorBinder binder = new DefaultRouterConfiguration().getServiceDescriptorBinderUDA10();
        //System.out.println(binder.generate(svc));
        //System.out.println("############################################################################");

        assertEquals(svc.getStateVariables().length, 4);
        assertEquals(svc.getStateVariable("Target").getTypeDetails().getDatatype().getBuiltin(), Datatype.Builtin.BOOLEAN);
        assertEquals(svc.getStateVariable("Target").getTypeDetails().getDefaultValue(), "0");
        assertEquals(svc.getStateVariable("Target").getEventDetails().isSendEvents(), false);

        assertEquals(svc.getStateVariable("Status").getTypeDetails().getDatatype().getBuiltin(), Datatype.Builtin.BOOLEAN);
        assertEquals(svc.getStateVariable("Status").getTypeDetails().getDefaultValue(), "0");
        assertEquals(svc.getStateVariable("Status").getEventDetails().isSendEvents(), true);

        assertEquals(svc.getStateVariable("SomeVar").getTypeDetails().getDatatype().getBuiltin(), Datatype.Builtin.STRING);
        assertEquals(svc.getStateVariable("SomeVar").getTypeDetails().getAllowedValues()[0], "BAR");
        assertEquals(svc.getStateVariable("SomeVar").getTypeDetails().getAllowedValues()[1], "BAZ");
        assertEquals(svc.getStateVariable("SomeVar").getEventDetails().isSendEvents(), false);

        assertEquals(svc.getStateVariable("AnotherVar").getTypeDetails().getDatatype().getBuiltin(), Datatype.Builtin.UI4);
        assertEquals(svc.getStateVariable("AnotherVar").getTypeDetails().getAllowedValueRange().getMinimum(), 0l);
        assertEquals(svc.getStateVariable("AnotherVar").getTypeDetails().getAllowedValueRange().getMaximum(), 10l);
        assertEquals(svc.getStateVariable("AnotherVar").getTypeDetails().getAllowedValueRange().getStep(), 2l);
        assertEquals(svc.getStateVariable("AnotherVar").getTypeDetails().getDefaultValue(), "6");
        assertEquals(svc.getStateVariable("AnotherVar").getEventDetails().isSendEvents(), false);

        assertEquals(svc.getDeviceService().getServiceId().toString(), "urn:" + UDAServiceId.DEFAULT_NAMESPACE + ":serviceId:SwitchPower");
        assertEquals(svc.getDeviceService().getServiceType().toString(), "urn:" + UDAServiceType.DEFAULT_NAMESPACE + ":service:SwitchPower:1");

        assertEquals(svc.getActions().length, 3);

        assertEquals(svc.getAction("SetTarget").getName(), "SetTarget");
        assertEquals(svc.getAction("SetTarget").getArguments().length, 1);
        assertEquals(svc.getAction("SetTarget").getArguments()[0].getName(), "NewTargetValue");
        assertEquals(svc.getAction("SetTarget").getArguments()[0].getDirection(), ActionArgument.Direction.IN);
        assertEquals(svc.getAction("SetTarget").getArguments()[0].getRelatedStateVariableName(), "Target");

        assertEquals(svc.getAction("GetTarget").getName(), "GetTarget");
        assertEquals(svc.getAction("GetTarget").getArguments().length, 1);
        assertEquals(svc.getAction("GetTarget").getArguments()[0].getName(), "RetTargetValue");
        assertEquals(svc.getAction("GetTarget").getArguments()[0].getDirection(), ActionArgument.Direction.OUT);
        assertEquals(svc.getAction("GetTarget").getArguments()[0].getRelatedStateVariableName(), "Target");
        assertEquals(svc.getAction("GetTarget").getArguments()[0].isReturnValue(), true);

        assertEquals(svc.getAction("GetStatus").getName(), "GetStatus");
        assertEquals(svc.getAction("GetStatus").getArguments().length, 1);
        assertEquals(svc.getAction("GetStatus").getArguments()[0].getName(), "ResultStatus");
        assertEquals(svc.getAction("GetStatus").getArguments()[0].getDirection(), ActionArgument.Direction.OUT);
        assertEquals(svc.getAction("GetStatus").getArguments()[0].getRelatedStateVariableName(), "Status");
        assertEquals(svc.getAction("GetStatus").getArguments()[0].isReturnValue(), true);

    }

    /* ####################################################################################################### */

    public static enum Foo {
        BAR,
        BAZ
    }

    /* ####################################################################################################### */

    @UpnpService(
            serviceId = @UpnpServiceId("SwitchPower"),
            serviceType = @UpnpServiceType(value = "SwitchPower", version = 1),
            supportsQueryStateVariables = false
    )
    @UpnpStateVariables({
            @UpnpStateVariable(name = "Target", defaultValue = "0", sendEvents = false),
            @UpnpStateVariable(name = "Status", defaultValue = "0"),
            @UpnpStateVariable(name = "SomeVar", sendEvents = false, allowedValuesEnum = Foo.class),
            @UpnpStateVariable(
                    name = "AnotherVar", datatype = "ui4", sendEvents = false, defaultValue = "6",
                    allowedValueMinimum = 0, allowedValueMaximum = 10, allowedValueStep = 2
            )
    })
    public static class TestServiceOne {

        private boolean target = false;
        private boolean status = false;

        @UpnpAction
        public void setTarget(@UpnpInputArgument(name = "NewTargetValue") boolean newTargetValue) {
            target = newTargetValue;
            status = newTargetValue;
        }

        @UpnpAction(out = @UpnpOutputArgument(name = "RetTargetValue"))
        public boolean getTarget() {
            return target;
        }

        @UpnpAction(name = "GetStatus", out = @UpnpOutputArgument(name = "ResultStatus", getterName = "getStatus"))
        public void retrieveStatus() {
            // NOOP
        }

        public boolean getStatus() {
            return status;
        }
    }

    /* ####################################################################################################### */

    @UpnpService(
            serviceId = @UpnpServiceId("SwitchPower"),
            serviceType = @UpnpServiceType(value = "SwitchPower", version = 1),
            supportsQueryStateVariables = false
    )
    public static class TestServiceTwo {

        @UpnpStateVariable(sendEvents = false, defaultValue = "0")
        private boolean target = false;

        @UpnpStateVariable(sendEvents = true, defaultValue = "0")
        private boolean status = false;

        @UpnpStateVariable(sendEvents = false, allowedValuesEnum = Foo.class)
        private Foo someVar;

        @UpnpStateVariable(sendEvents = false, defaultValue = "6", allowedValueMinimum = 0, allowedValueMaximum = 10, allowedValueStep = 2)
        private UnsignedIntegerFourBytes anotherVar = new UnsignedIntegerFourBytes(6);

        @UpnpAction
        public void setTarget(@UpnpInputArgument(name = "NewTargetValue") boolean newTargetValue) {
            target = newTargetValue;
            status = newTargetValue;
        }

        @UpnpAction(out = @UpnpOutputArgument(name = "RetTargetValue"))
        public boolean getTarget() {
            return target;
        }

        @UpnpAction(
                name = "GetStatus",
                out = {
                        @UpnpOutputArgument(name = "ResultStatus", getterName = "getStatus")
                }
        )
        public void retrieveStatus() {
            // Noop
        }

        public boolean getStatus() {
            return status;
        }
    }

    /* ####################################################################################################### */

    @UpnpService(
            serviceId = @UpnpServiceId("SwitchPower"),
            serviceType = @UpnpServiceType(value = "SwitchPower", version = 1),
            supportsQueryStateVariables = false
    )
    @UpnpStateVariables({
            @UpnpStateVariable(name = "Status", defaultValue = "0"),
            @UpnpStateVariable(name = "SomeVar", sendEvents = false, allowedValuesEnum = Foo.class),
            @UpnpStateVariable(
                    name = "AnotherVar", datatype = "ui4", sendEvents = false, defaultValue = "6",
                    allowedValueMinimum = 0, allowedValueMaximum = 10, allowedValueStep = 2
            )
    })
    public static class TestServiceThree  {

        @UpnpStateVariable(sendEvents = false, defaultValue = "0")
        private boolean target = false;

        private boolean status = false;

        @UpnpAction
        public void setTarget(@UpnpInputArgument(name = "NewTargetValue") boolean newTargetValue) {
            target = newTargetValue;
            status = newTargetValue;
        }

        @UpnpAction(out = @UpnpOutputArgument(name = "RetTargetValue"))
        public boolean getTarget() {
            return target;
        }

        @UpnpAction(out = {@UpnpOutputArgument(name = "ResultStatus")})
        public boolean getStatus() {
            return status;
        }
    }


    /* ####################################################################################################### */

    @UpnpService(
            serviceId = @UpnpServiceId("SwitchPower"),
            serviceType = @UpnpServiceType(value = "SwitchPower", version = 1),
            supportsQueryStateVariables = false
    )
    @UpnpStateVariables({
            @UpnpStateVariable(name = "Target", defaultValue = "0", sendEvents = false),
            @UpnpStateVariable(name = "Status", defaultValue = "0"),
            @UpnpStateVariable(name = "SomeVar", sendEvents = false, allowedValuesEnum = Foo.class),
            @UpnpStateVariable(
                    name = "AnotherVar", datatype = "ui4", sendEvents = false, defaultValue = "6",
                    allowedValueMinimum = 0, allowedValueMaximum = 10, allowedValueStep = 2
            )
    })
    public static class TestServiceFour {

        private boolean target = false;

        private boolean status = false;

        @UpnpAction
        public void setTarget(@UpnpInputArgument(name = "NewTargetValue") boolean newTargetValue) {
            target = newTargetValue;
            status = newTargetValue;
        }

        @UpnpAction(out = @UpnpOutputArgument(name = "RetTargetValue"))
        public boolean getTarget() {
            return target;
        }

        @UpnpAction(
                out = {
                        @UpnpOutputArgument(name = "ResultStatus", stateVariable = "Status")
                }
        )
        public boolean getStatus() {
            return status;
        }
    }

    /* ####################################################################################################### */

    @UpnpService(
            serviceId = @UpnpServiceId("SwitchPower"),
            serviceType = @UpnpServiceType(value = "SwitchPower", version = 1),
            supportsQueryStateVariables = false
    )
    @UpnpStateVariables({
            @UpnpStateVariable(name = "Target", defaultValue = "0", sendEvents = false),
            @UpnpStateVariable(name = "Status", defaultValue = "0"),
            @UpnpStateVariable(name = "SomeVar", sendEvents = false, allowedValuesEnum = Foo.class),
            @UpnpStateVariable(
                    name = "AnotherVar", datatype = "ui4", sendEvents = false, defaultValue = "6",
                    allowedValueMinimum = 0, allowedValueMaximum = 10, allowedValueStep = 2
            )
    })
    public static class TestServiceFive {

        private boolean target = false;
        private boolean status = false;

        @UpnpAction
        public void setTarget(@UpnpInputArgument(name = "NewTargetValue") boolean newTargetValue) {
            target = newTargetValue;
            status = newTargetValue;
        }

        @UpnpAction(out = @UpnpOutputArgument(name = "RetTargetValue"))
        public boolean getTarget() {
            return target;
        }

        @UpnpAction(out = @UpnpOutputArgument(name = "ResultStatus", getterName = "getStatus"))
        //ALTERNATIVE: @UpnpAction(out = @UpnpOutputArgument(name = "ResultStatus", stateVariable = "Status", getterName = "<some other method name in StatusHolder>"))
        public StatusHolder getStatus() {
            return new StatusHolder(status);
        }

        public class StatusHolder {
            boolean st;

            public StatusHolder(boolean st) {
                this.st = st;
            }

            public boolean getStatus() {
                return st;
            }
        }
    }

}
