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

import org.teleal.cling.controlpoint.SubscriptionCallback;
import org.teleal.cling.model.gena.CancelReason;
import org.teleal.cling.model.gena.GENASubscription;
import org.teleal.cling.model.message.StreamResponseMessage;
import org.teleal.cling.model.message.UpnpResponse;
import org.teleal.cling.model.message.header.SubscriptionIdHeader;
import org.teleal.cling.model.message.header.TimeoutHeader;
import org.teleal.cling.model.message.header.UpnpHeader;
import org.teleal.cling.model.meta.LocalDevice;
import org.teleal.cling.model.meta.LocalService;
import org.teleal.cling.test.MockUpnpService;
import org.teleal.cling.test.data.SampleData;
import org.teleal.common.util.Reflections;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.List;

import static org.testng.Assert.assertEquals;


public class LocalSubscriptionLifecycleTest {

    @Test
    public void subscriptionLifecycle() throws Exception {

        MockUpnpService upnpService = new MockUpnpService() {
            @Override
            public StreamResponseMessage[] getStreamResponseMessages() {
                return new StreamResponseMessage[]{
                        createSubscribeResponseMessage(),
                        createUnsubscribeResponseMessage()
                };
            }
        };

        final List<Boolean> testAssertions = new ArrayList();

        // Register local device and its service
        LocalDevice device = GenaSampleData.createTestDevice(GenaSampleData.LocalTestService.class);
        upnpService.getRegistry().addDevice(device);

        LocalService service = SampleData.getFirstService(device);

        SubscriptionCallback callback = new SubscriptionCallback(service.getDeviceService(), 180) {

            @Override
            public void failed(GENASubscription subscription, UpnpResponse responseStatus, Exception exception) {
                testAssertions.add(false);
            }

            @Override
            public void established(GENASubscription subscription) {
                testAssertions.add(true);
            }

            @Override
            public void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
                assert subscription != null;
                assert reason == null;
                assert responseStatus == null;
                testAssertions.add(true);
            }

            public void eventReceived(GENASubscription subscription) {
                if (subscription.getCurrentSequence().getValue() == 0) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    testAssertions.add(true);
                } else if (subscription.getCurrentSequence().getValue() == 1) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "1");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    testAssertions.add(true);
                } else {
                    testAssertions.add(false);
                }
            }

            public void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
                testAssertions.add(false);
            }

        };

        upnpService.getControlPoint().execute(callback);

        Thread.sleep(100);

        Object serviceImpl = service.getManager().getImplementation();

        // And immediately "modify" the state of the service, this should result in "concurrent" event messages
        Reflections.set(Reflections.getField(serviceImpl.getClass(), "status"), serviceImpl, true);
        service.getManager().getState().getPropertyChangeSupport().firePropertyChange("Status", false, true);

        Thread.sleep(100);

        assertEquals(callback.getSubscription().getCurrentSequence().getValue(), Long.valueOf(2)); // It's the NEXT sequence!
        assert callback.getSubscription().getSubscriptionId().startsWith("uuid:");
        assertEquals(callback.getSubscription().getActualDurationSeconds(), 180);

        callback.end();

        Thread.sleep(100);

        assertEquals(testAssertions.size(), 4);
        for (Boolean testAssertion : testAssertions) {
            assert testAssertion;
        }

        assertEquals(upnpService.getSentStreamRequestMessages().size(), 0);
    }


    @Test
    public void subscriptionLifecycleChangeSupport() throws Exception {

        MockUpnpService upnpService = new MockUpnpService() {
            @Override
            public StreamResponseMessage[] getStreamResponseMessages() {
                return new StreamResponseMessage[]{
                        createSubscribeResponseMessage(),
                        createUnsubscribeResponseMessage()
                };
            }
        };

        final List<Boolean> testAssertions = new ArrayList();

        // Register local device and its service
        LocalDevice device = GenaSampleData.createTestDevice(GenaSampleData.LocalTestServiceOwnPropertyChangeSupport.class);
        upnpService.getRegistry().addDevice(device);

        LocalService<GenaSampleData.LocalTestServiceOwnPropertyChangeSupport> service = SampleData.getFirstService(device);

        SubscriptionCallback callback = new SubscriptionCallback(service.getDeviceService(), 180) {

            @Override
            public void failed(GENASubscription subscription, UpnpResponse responseStatus, Exception exception) {
                testAssertions.add(false);
            }

            @Override
            public void established(GENASubscription subscription) {
                testAssertions.add(true);
            }

            @Override
            public void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
                assert subscription != null;
                assert reason == null;
                assert responseStatus == null;
                testAssertions.add(true);
            }

            public void eventReceived(GENASubscription subscription) {
                if (subscription.getCurrentSequence().getValue() == 0) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    testAssertions.add(true);
                } else if (subscription.getCurrentSequence().getValue() == 1) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "1");
                    testAssertions.add(true);
                } else {
                    testAssertions.add(false);
                }
            }

            public void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
                testAssertions.add(false);
            }

        };

        upnpService.getControlPoint().execute(callback);

        Thread.sleep(100);

        // This triggers the internal PropertyChangeSupport of the service impl!
        service.getManager().getImplementation().setTarget(true);

        Thread.sleep(100);

        assertEquals(callback.getSubscription().getCurrentSequence().getValue(), Long.valueOf(2)); // It's the NEXT sequence!
        assert callback.getSubscription().getSubscriptionId().startsWith("uuid:");
        assertEquals(callback.getSubscription().getActualDurationSeconds(), 180);

        callback.end();

        Thread.sleep(100);

        assertEquals(testAssertions.size(), 4);
        for (Boolean testAssertion : testAssertions) {
            assert testAssertion;
        }

        assertEquals(upnpService.getSentStreamRequestMessages().size(), 0);
    }

    @Test
    public void moderateMaxRate() throws Exception {

        MockUpnpService upnpService = new MockUpnpService() {
            @Override
            public StreamResponseMessage[] getStreamResponseMessages() {
                return new StreamResponseMessage[]{
                        createSubscribeResponseMessage(),
                        createUnsubscribeResponseMessage()
                };
            }
        };

        final List<Boolean> testAssertions = new ArrayList();

        // Register local device and its service
        LocalDevice device = GenaSampleData.createTestDevice(GenaSampleData.LocalTestServiceModerated.class);
        upnpService.getRegistry().addDevice(device);

        LocalService service = SampleData.getFirstService(device);

        SubscriptionCallback callback = new SubscriptionCallback(service.getDeviceService()) {

            @Override
            public void failed(GENASubscription subscription, UpnpResponse responseStatus, Exception exception) {
                testAssertions.add(false);
            }

            @Override
            public void established(GENASubscription subscription) {
                testAssertions.add(true);
            }

            @Override
            public void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
                assert subscription != null;
                assert reason == null;
                assert responseStatus == null;
                testAssertions.add(true);
            }

            public void eventReceived(GENASubscription subscription) {
                if (subscription.getCurrentSequence().getValue() == 0) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    assertEquals(subscription.getCurrentValues().get("ModeratedMaxRateVar").toString(), "one");
                    testAssertions.add(true);
                } else if (subscription.getCurrentSequence().getValue() == 1) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    assert subscription.getCurrentValues().get("ModeratedMaxRateVar") == null;
                    testAssertions.add(true);
                } else if (subscription.getCurrentSequence().getValue() == 2) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    assert subscription.getCurrentValues().get("ModeratedMaxRateVar") == null;
                    testAssertions.add(true);
                } else if (subscription.getCurrentSequence().getValue() == 3) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    assertEquals(subscription.getCurrentValues().get("ModeratedMaxRateVar").toString(), "four");
                    testAssertions.add(true);
                } else {
                    testAssertions.add(false);
                }
            }

            public void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
                testAssertions.add(false);
            }

        };

        upnpService.getControlPoint().execute(callback);

        Thread.sleep(200);

        Object serviceImpl = service.getManager().getImplementation();

        Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMaxRateVar"), serviceImpl, "two");
        service.getManager().getState().getPropertyChangeSupport().firePropertyChange("ModeratedMaxRateVar", "one", "two");

        Thread.sleep(200);

        Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMaxRateVar"), serviceImpl, "three");
        service.getManager().getState().getPropertyChangeSupport().firePropertyChange("ModeratedMaxRateVar", "two", "three");

        Thread.sleep(200);

        Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMaxRateVar"), serviceImpl, "four");
        service.getManager().getState().getPropertyChangeSupport().firePropertyChange("ModeratedMaxRateVar", "three", "four");

        Thread.sleep(100);

        assertEquals(callback.getSubscription().getCurrentSequence().getValue(), Long.valueOf(4)); // It's the NEXT sequence!

        callback.end();

        Thread.sleep(100);

        assertEquals(testAssertions.size(), 6);
        for (Boolean testAssertion : testAssertions) {
            assert testAssertion;
        }

        assertEquals(upnpService.getSentStreamRequestMessages().size(), 0);
    }

    @Test
    public void moderateMinDelta() throws Exception {

        MockUpnpService upnpService = new MockUpnpService() {
            @Override
            public StreamResponseMessage[] getStreamResponseMessages() {
                return new StreamResponseMessage[]{
                        createSubscribeResponseMessage(),
                        createUnsubscribeResponseMessage()
                };
            }
        };

        final List<Boolean> testAssertions = new ArrayList();

        // Register local device and its service
        LocalDevice device = GenaSampleData.createTestDevice(GenaSampleData.LocalTestServiceModerated.class);
        upnpService.getRegistry().addDevice(device);

        LocalService service = SampleData.getFirstService(device);

        SubscriptionCallback callback = new SubscriptionCallback(service.getDeviceService()) {

            @Override
            public void failed(GENASubscription subscription, UpnpResponse responseStatus, Exception exception) {
                testAssertions.add(false);
            }

            @Override
            public void established(GENASubscription subscription) {
                testAssertions.add(true);
            }

            @Override
            public void ended(GENASubscription subscription, CancelReason reason, UpnpResponse responseStatus) {
                assert subscription != null;
                assert reason == null;
                assert responseStatus == null;
                testAssertions.add(true);
            }

            public void eventReceived(GENASubscription subscription) {
                if (subscription.getCurrentSequence().getValue() == 0) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    assertEquals(subscription.getCurrentValues().get("ModeratedMaxRateVar").toString(), "one");
                    assertEquals(subscription.getCurrentValues().get("ModeratedMinDeltaVar").toString(), "1");
                    testAssertions.add(true);
                } else if (subscription.getCurrentSequence().getValue() == 1) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    assert subscription.getCurrentValues().get("ModeratedMaxRateVar") == null;
                    assert subscription.getCurrentValues().get("ModeratedMinDeltaVar") == null;
                    testAssertions.add(true);
                } else if (subscription.getCurrentSequence().getValue() == 2) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    assert subscription.getCurrentValues().get("ModeratedMaxRateVar") == null;
                    assert subscription.getCurrentValues().get("ModeratedMinDeltaVar") == null;
                    testAssertions.add(true);
                } else if (subscription.getCurrentSequence().getValue() == 3) {
                    assertEquals(subscription.getCurrentValues().get("Status").toString(), "0");
                    assertEquals(subscription.getCurrentValues().get("SomeVar").toString(), "");
                    assert subscription.getCurrentValues().get("ModeratedMaxRateVar") == null;
                    assertEquals(subscription.getCurrentValues().get("ModeratedMinDeltaVar").toString(), "4");
                    testAssertions.add(true);
                } else {
                    testAssertions.add(false);
                }
            }

            public void eventsMissed(GENASubscription subscription, int numberOfMissedEvents) {
                testAssertions.add(false);
            }

        };

        upnpService.getControlPoint().execute(callback);

        Thread.sleep(100);

        Object serviceImpl = service.getManager().getImplementation();

        Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMinDeltaVar"), serviceImpl, 2);
        service.getManager().getState().getPropertyChangeSupport().firePropertyChange("ModeratedMinDeltaVar", 1, 2);

        Thread.sleep(100);

        Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMinDeltaVar"), serviceImpl, 3);
        service.getManager().getState().getPropertyChangeSupport().firePropertyChange("ModeratedMinDeltaVar", 2, 3);

        Thread.sleep(100);

        Reflections.set(Reflections.getField(serviceImpl.getClass(), "moderatedMinDeltaVar"), serviceImpl, 4);
        service.getManager().getState().getPropertyChangeSupport().firePropertyChange("ModeratedMinDeltaVar", 3, 4);

        Thread.sleep(100);

        assertEquals(callback.getSubscription().getCurrentSequence().getValue(), Long.valueOf(4)); // It's the NEXT sequence!

        callback.end();

        Thread.sleep(100);

        assertEquals(testAssertions.size(), 6);
        for (Boolean testAssertion : testAssertions) {
            assert testAssertion;
        }

        assertEquals(upnpService.getSentStreamRequestMessages().size(), 0);
    }

    protected StreamResponseMessage createSubscribeResponseMessage() {
        StreamResponseMessage msg = new StreamResponseMessage(new UpnpResponse(UpnpResponse.Status.OK));
        msg.getHeaders().add(
                UpnpHeader.Type.SID, new SubscriptionIdHeader("uuid:1234")
        );
        msg.getHeaders().add(
                UpnpHeader.Type.TIMEOUT, new TimeoutHeader(180)
        );
        return msg;
    }

    protected StreamResponseMessage createUnsubscribeResponseMessage() {
        return new StreamResponseMessage(new UpnpResponse(UpnpResponse.Status.OK));
    }

}