/*
 * Copyright (C) 2013 4th Line GmbH, Switzerland
 *
 * The contents of this file are subject to the terms of either the GNU
 * Lesser General Public License Version 2 or later ("LGPL") or the
 * Common Development and Distribution License Version 1 or later
 * ("CDDL") (collectively, the "License"). You may not use this file
 * except in compliance with the License. See LICENSE.txt for more
 * information.
 *
 * 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.
 */

package org.fourthline.cling.test.model;

import org.fourthline.cling.model.Constants;
import org.fourthline.cling.model.ServerClientTokens;
import org.fourthline.cling.model.message.header.*;
import org.fourthline.cling.model.types.DeviceType;
import org.fourthline.cling.model.types.NamedDeviceType;
import org.fourthline.cling.model.types.NamedServiceType;
import org.fourthline.cling.model.types.ServiceType;
import org.fourthline.cling.model.types.UDADeviceType;
import org.fourthline.cling.model.types.UDAServiceType;
import org.seamless.util.MimeType;
import org.testng.annotations.Test;

import java.net.URI;
import java.util.Locale;

import static org.testng.Assert.assertEquals;


public class HeaderParsingTest {

    @Test
    public void parseContentTypeHeader() {
        ContentTypeHeader header = new ContentTypeHeader(MimeType.valueOf("foo/bar;charset=\"utf-8\""));
        assertEquals(header.getString(), "foo/bar;charset=\"utf-8\"");
    }

    @Test
    public void parseDeviceType() {
        DeviceType deviceType = DeviceType.valueOf("urn:foo-bar:device:MyDeviceType:123");
        assertEquals(deviceType.getNamespace(), "foo-bar");
        assertEquals(deviceType.getType(), "MyDeviceType");
        assertEquals(deviceType.getVersion(), 123);
    }

    @Test
    public void parseUDADeviceType() {
        UDADeviceType deviceType = (UDADeviceType)DeviceType.valueOf("urn:schemas-upnp-org:device:MyDeviceType:123");
        assertEquals(deviceType.getType(), "MyDeviceType");
        assertEquals(deviceType.getVersion(), 123);
    }

    @Test
    public void parseInvalidDeviceTypeHeader() {
        DeviceTypeHeader header = new DeviceTypeHeader();
        header.setString("urn:foo-bar:device:!@#:123");
        assertEquals(header.getValue().getNamespace(), "foo-bar");
        assertEquals(header.getValue().getType(), "---");
        assertEquals(header.getValue().getVersion(), 123);
        assertEquals(header.getString(), "urn:foo-bar:device:---:123");
    }

    @Test
    public void parseDeviceTypeHeaderURI() {
        DeviceTypeHeader header = new DeviceTypeHeader(URI.create("urn:schemas-upnp-org:device:MyDeviceType:123"));
        assertEquals(header.getValue().getNamespace(), "schemas-upnp-org");
        assertEquals(header.getValue().getType(), "MyDeviceType");
        assertEquals(header.getValue().getVersion(), 123);
        assertEquals(header.getString(), "urn:schemas-upnp-org:device:MyDeviceType:123");

    }

    @Test
    public void parseDeviceUSNHeader() {
        DeviceUSNHeader header = new DeviceUSNHeader();
        header.setString("uuid:MY-DEVICE-123::urn:schemas-upnp-org:device:MY-DEVICE-TYPE:1");
        assertEquals(header.getValue().getUdn().getIdentifierString(), "MY-DEVICE-123");
        assert header.getValue().getDeviceType() instanceof UDADeviceType;
    }

    @Test
    public void parseDeviceUSNHeaderStatic() {
        DeviceUSNHeader header = new DeviceUSNHeader(
                NamedDeviceType.valueOf("uuid:MY-DEVICE-123::urn:schemas-upnp-org:device:MY-DEVICE-TYPE:1")
        );
        assertEquals(header.getValue().getUdn().getIdentifierString(), "MY-DEVICE-123");
        assert header.getValue().getDeviceType() instanceof UDADeviceType;
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidDeviceUSNHeader() {
        DeviceUSNHeader header = new DeviceUSNHeader();
        header.setString("uuid:MY-DEVICE-123--urn:schemas-upnp-org:device:MY-DEVICE-TYPE:1");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidEXTHeader() {
        EXTHeader header = new EXTHeader();
        header.setString("MUST BE EMPTY STRING");
    }

    @Test
    public void parseHostHeaderConstructor() {
        HostHeader header = new HostHeader("foo.bar", 1234);
        assertEquals(header.getValue().getHost(), "foo.bar");
        assertEquals(header.getValue().getPort(), 1234);

        header = new HostHeader(1234);
        assertEquals(header.getValue().getHost(), Constants.IPV4_UPNP_MULTICAST_GROUP);
        assertEquals(header.getValue().getPort(), 1234);
    }

    @Test
    public void parseHostHeader() {
        HostHeader header = new HostHeader();
        assertEquals(header.getValue().getHost(), Constants.IPV4_UPNP_MULTICAST_GROUP);
        assertEquals(header.getValue().getPort(), Constants.UPNP_MULTICAST_PORT);

        header = new HostHeader();
        header.setString("foo.bar:1234");
        assertEquals(header.getValue().getHost(), "foo.bar");
        assertEquals(header.getValue().getPort(), 1234);

        header = new HostHeader();
        header.setString("foo.bar");
        assertEquals(header.getValue().getHost(), "foo.bar");
        assertEquals(header.getValue().getPort(), Constants.UPNP_MULTICAST_PORT);
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidHostHeader() {
        HostHeader header = new HostHeader();
        header.setString("foo.bar:abc");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidLocationHeader() {
        LocationHeader header = new LocationHeader();
        header.setString("this://is.not...a valid URL");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidMANHeader() {
        MANHeader header = new MANHeader("abc");
        header.setString("\"foo.bar\"; ns = baz"); // Not valid
    }

    @Test
    public void parseMANHeaderNoNS() {
        MANHeader header = new MANHeader("abc");
        header.setString("\"foo.bar\"");
        assert header.getValue().equals("foo.bar");
        assert header.getNamespace() == null;
        assert header.getString().equals("\"foo.bar\"");
    }

    @Test
    public void parseMANHeaderNS() {
        MANHeader header = new MANHeader("abc");
        header.setString("\"foo.bar\"; ns =12");
        assert header.getValue().equals("foo.bar");
        assert header.getNamespace().equals("12");
        assert header.getString().equals("\"foo.bar\"; ns=12");
    }

    @Test
    public void parseMaxAgeHeader() {
        MaxAgeHeader header = new MaxAgeHeader();
        header.setString("max-age=1234, foobar=baz");
        assertEquals(header.getValue(), new Integer(1234));
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidMaxAgeHeader() {
        MaxAgeHeader header = new MaxAgeHeader();
        header.setString("max-foo=123");
    }

    @Test
    public void parseMXHeader() {
        MXHeader header = new MXHeader();
        header.setString("111");
        assertEquals(header.getValue(), new Integer(111));
        
        header = new MXHeader();
        header.setString("123");
        assertEquals(header.getValue(), MXHeader.DEFAULT_VALUE);

    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidMXHeader() {
        MXHeader header = new MXHeader();
        header.setString("abc");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidNTSHeader() {
        NTSHeader header = new NTSHeader();
        header.setString("foo");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidRootDeviceHeader() {
        RootDeviceHeader header = new RootDeviceHeader();
        header.setString("upnp:foodevice");
    }

    @Test
    public void parseServerHeader() {
        ServerHeader header = new ServerHeader();
        header.setString("foo/1 UPnP/1.1 bar/2");
        assertEquals(header.getValue().getOsName(), "foo");
        assertEquals(header.getValue().getOsVersion(), "1");
        assertEquals(header.getValue().getProductName(), "bar");
        assertEquals(header.getValue().getProductVersion(), "2");
        assertEquals(header.getValue().getMajorVersion(), 1);
        assertEquals(header.getValue().getMinorVersion(), 1);

        // Commas...
        header = new ServerHeader();
        header.setString("foo/1, UPnP/1.1, bar/2");
        assertEquals(header.getValue().getOsName(), "foo");
        assertEquals(header.getValue().getOsVersion(), "1");
        assertEquals(header.getValue().getProductName(), "bar");
        assertEquals(header.getValue().getProductVersion(), "2");
        assertEquals(header.getValue().getMajorVersion(), 1);
        assertEquals(header.getValue().getMinorVersion(), 1);

        // Whitespace in tokens
        header = new ServerHeader();
        header.setString("foo baz/1 UPnP/1.1 bar abc/2");
        assertEquals(header.getValue().getOsName(), "foo baz");
        assertEquals(header.getValue().getOsVersion(), "1");
        assertEquals(header.getValue().getProductName(), "bar abc");
        assertEquals(header.getValue().getProductVersion(), "2");
        assertEquals(header.getValue().getMajorVersion(), 1);
        assertEquals(header.getValue().getMinorVersion(), 1);

        // Commas and whitespace!
        header = new ServerHeader();
        header.setString("foo baz/1, UPnP/1.1, bar abc/2");
        assertEquals(header.getValue().getOsName(), "foo baz");
        assertEquals(header.getValue().getOsVersion(), "1");
        assertEquals(header.getValue().getProductName(), "bar abc");
        assertEquals(header.getValue().getProductVersion(), "2");
        assertEquals(header.getValue().getMajorVersion(), 1);
        assertEquals(header.getValue().getMinorVersion(), 1);

        // Absolutely not valid!
        header = new ServerHeader();
        header.setString("foo/1 UPnP/1.");
        assertEquals(header.getValue().getOsName(), ServerClientTokens.UNKNOWN_PLACEHOLDER);
        assertEquals(header.getValue().getOsVersion(), ServerClientTokens.UNKNOWN_PLACEHOLDER);
        assertEquals(header.getValue().getProductName(), ServerClientTokens.UNKNOWN_PLACEHOLDER);
        assertEquals(header.getValue().getProductVersion(), ServerClientTokens.UNKNOWN_PLACEHOLDER);
        assertEquals(header.getValue().getMajorVersion(), 1);
        assertEquals(header.getValue().getMinorVersion(), 0); // Assume UDA 1.0

    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidServerHeader() {
        ServerHeader header = new ServerHeader();
        header.setString("foo/1 baz/123 bar/2");
    }

    @Test
    public void parseServiceType() {
        ServiceType serviceType = ServiceType.valueOf("urn:foo-bar:service:MyServiceType:123");
        assertEquals(serviceType.getNamespace(), "foo-bar");
        assertEquals(serviceType.getType(), "MyServiceType");
        assertEquals(serviceType.getVersion(), 123);
    }

    @Test
    public void parseUDAServiceType() {
        UDAServiceType serviceType = (UDAServiceType)ServiceType.valueOf("urn:schemas-upnp-org:service:MyServiceType:123");
        assertEquals(serviceType.getType(), "MyServiceType");
        assertEquals(serviceType.getVersion(), 123);
    }

    @Test
    public void parseInvalidServiceTypeHeader() {
        ServiceTypeHeader header = new ServiceTypeHeader();
        header.setString("urn:foo-bar:service:!@#:123");
        assertEquals(header.getValue().getNamespace(), "foo-bar");
        assertEquals(header.getValue().getType(), "---");
        assertEquals(header.getValue().getVersion(), 123);
        assertEquals(header.getString(), "urn:foo-bar:service:---:123");
    }

    @Test
    public void parseServiceTypeHeaderURI() {
        ServiceTypeHeader header = new ServiceTypeHeader(URI.create("urn:schemas-upnp-org:service:MyServiceType:123"));
        assertEquals(header.getValue().getNamespace(), "schemas-upnp-org");
        assertEquals(header.getValue().getType(), "MyServiceType");
        assertEquals(header.getValue().getVersion(), 123);
        assertEquals(header.getString(), "urn:schemas-upnp-org:service:MyServiceType:123");
    }

    @Test
    public void parseServiceUSNHeader() {
        ServiceUSNHeader header = new ServiceUSNHeader();
        header.setString("uuid:MY-SERVICE-123::urn:schemas-upnp-org:service:MY-SERVICE-TYPE:1");
        assertEquals(header.getValue().getUdn().getIdentifierString(), "MY-SERVICE-123");
        assert header.getValue().getServiceType() instanceof UDAServiceType;
    }

    @Test
    public void parseServiceUSNHeaderStatic() {
        ServiceUSNHeader header = new ServiceUSNHeader(
                NamedServiceType.valueOf("uuid:MY-SERVICE-123::urn:schemas-upnp-org:service:MY-SERVICE-TYPE:1")
        );
        assertEquals(header.getValue().getUdn().getIdentifierString(), "MY-SERVICE-123");
        assert header.getValue().getServiceType() instanceof UDAServiceType;
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidServiceUSNHeader() {
        ServiceUSNHeader header = new ServiceUSNHeader();
        header.setString("uuid:MY-SERVICE-123--urn:schemas-upnp-org:service:MY-SERVICE-TYPE:1");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidSTAllHeader() {
        STAllHeader header = new STAllHeader();
        header.setString("ssdp:foo");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidUDADeviceTypeHeader() {
        UDADeviceTypeHeader header = new UDADeviceTypeHeader();
        header.setString("urn:foo-bar:device:!@#:123");
    }

    @Test
    public void parseUDADeviceTypeHeaderURI() {
        UDADeviceTypeHeader header = new UDADeviceTypeHeader(URI.create("urn:schemas-upnp-org:device:MyDeviceType:123"));
        assertEquals(header.getValue().getNamespace(), "schemas-upnp-org");
        assertEquals(header.getValue().getType(), "MyDeviceType");
        assertEquals(header.getValue().getVersion(), 123);
        assertEquals(header.getString(), "urn:schemas-upnp-org:device:MyDeviceType:123");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidUDAServiceTypeHeader() {
        UDAServiceTypeHeader header = new UDAServiceTypeHeader();
        header.setString("urn:foo-bar:service:!@#:123");
    }

    @Test
    public void parseUDAServiceTypeHeaderURI() {
        UDAServiceTypeHeader header = new UDAServiceTypeHeader(URI.create("urn:schemas-upnp-org:service:MyServiceType:123"));
        assertEquals(header.getValue().getNamespace(), "schemas-upnp-org");
        assertEquals(header.getValue().getType(), "MyServiceType");
        assertEquals(header.getValue().getVersion(), 123);
        assertEquals(header.getString(), "urn:schemas-upnp-org:service:MyServiceType:123");

    }

    @Test
    public void parseUDNHeader() {
        UDNHeader header = new UDNHeader();
        header.setString("uuid:MY-UUID-1234");
        assertEquals(header.getValue().getIdentifierString(), "MY-UUID-1234");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidUDNHeaderPrefix() {
        UDNHeader header = new UDNHeader();
        header.setString("MY-UUID-1234");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidUDNHeaderURN() {
        UDNHeader header = new UDNHeader();
        header.setString("uuid:MY-UUID-1234::urn:foo-bar:baz");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidUSNRootDeviceHeader() {
        USNRootDeviceHeader header = new USNRootDeviceHeader();
        header.setString("uuid:MY-UUID-1234::upnp:rootfoo");
    }

    @Test
    public void parseSoapActionHeader() {
        SoapActionHeader header = new SoapActionHeader(URI.create("urn:schemas-upnp-org:service:MyServiceType:1#MyAction"));
        assertEquals(header.getValue().getServiceType().getNamespace(), "schemas-upnp-org");
        assertEquals(header.getValue().getServiceType().getType(), "MyServiceType");
        assertEquals(header.getValue().getServiceType().getVersion(), 1);
        assertEquals(header.getValue().getActionName(), "MyAction");
        assertEquals(header.getString(), "\"urn:schemas-upnp-org:service:MyServiceType:1#MyAction\"");

    }

    @Test
    public void parseSoapActionHeaderString() {
        SoapActionHeader header = new SoapActionHeader();
        header.setString("\"urn:schemas-upnp-org:service:MyServiceType:1#MyAction\"");
        assertEquals(header.getValue().getServiceType().getNamespace(), "schemas-upnp-org");
        assertEquals(header.getValue().getServiceType().getType(), "MyServiceType");
        assertEquals(header.getValue().getServiceType().getVersion(), 1);
        assertEquals(header.getValue().getActionName(), "MyAction");
        assertEquals(header.getString(), "\"urn:schemas-upnp-org:service:MyServiceType:1#MyAction\"");
    }

    @Test
    public void parseSoapActionHeaderQueryString() {
        SoapActionHeader header = new SoapActionHeader();
        header.setString("\"urn:schemas-upnp-org:control-1-0#QueryStateVariable\"");
        assertEquals(header.getValue().getServiceType(), null);
        assertEquals(header.getValue().getType(), "control-1-0");
        assertEquals(header.getValue().getVersion(), null);
        assertEquals(header.getValue().getActionName(), "QueryStateVariable");
        assertEquals(header.getString(), "\"urn:schemas-upnp-org:control-1-0#QueryStateVariable\"");
    }

    @Test
    public void parseEventSequenceHeaderString() {
        EventSequenceHeader header = new EventSequenceHeader();
        header.setString("0");
        assertEquals(header.getValue().getValue(), Long.valueOf(0));
        header.setString("001");
        assertEquals(header.getValue().getValue(), Long.valueOf(1));
        header.setString("123");
        assertEquals(header.getValue().getValue(), Long.valueOf(123));

    }

    @Test
    public void parseTimeoutHeaderString() {
        TimeoutHeader header = new TimeoutHeader();
        header.setString("Second-123");
        assertEquals(header.getValue(), Integer.valueOf(123));
        header.setString("Second-infinite");
        assertEquals(header.getValue(), TimeoutHeader.INFINITE_VALUE);
    }

    @Test
    public void parseCallbackHeaderString() {
        CallbackHeader header = new CallbackHeader();
        header.setString("<http://127.0.0.1/foo>");
        assertEquals(header.getValue().size(), 1);
        assertEquals(header.getValue().get(0).toString(), "http://127.0.0.1/foo");

        header.setString("<http://127.0.0.1/foo><http://127.0.0.1/bar>");
        assertEquals(header.getValue().size(), 2);
        assertEquals(header.getValue().get(0).toString(), "http://127.0.0.1/foo");
        assertEquals(header.getValue().get(1).toString(), "http://127.0.0.1/bar");

        header.setString("<http://127.0.0.1/foo> <http://127.0.0.1/bar>");
        assertEquals(header.getValue().size(), 2);
        assertEquals(header.getValue().get(0).toString(), "http://127.0.0.1/foo");
        assertEquals(header.getValue().get(1).toString(), "http://127.0.0.1/bar");
    }

    @Test
    public void parseInvalidCallbackHeaderString() {
        CallbackHeader header = new CallbackHeader();
        header.setString("<http://127.0.0.1/foo> <ftp://127.0.0.1/bar>");
        assertEquals(header.getValue().size(), 1);
        assertEquals(header.getValue().get(0).toString(), "http://127.0.0.1/foo");

        /* TODO: I'm having trouble finding a valid URL that is
           an invalid URI in the standard JDK...
        header.setString("<http://127.0.0.1/foo> <http://we_need_a_valid_URL_but_invalid_URI>");
        assertEquals(header.getValue().size(), 1);
        assertEquals(header.getValue().get(0).toString(), "http://127.0.0.1/foo");
        */
    }

    @Test
    public void parseSubscriptionIdHeaderString() {
        SubscriptionIdHeader header = new SubscriptionIdHeader();
        header.setString("uuid:123-123-123-123");
        assertEquals(header.getValue(), "uuid:123-123-123-123");
    }

    @Test(expectedExceptions = InvalidHeaderException.class)
    public void parseInvalidSubscriptionIdHeaderString() {
        SubscriptionIdHeader header = new SubscriptionIdHeader();
        header.setString("abc:123-123-123-123");
    }

    @Test
    public void parseInterfaceMacAddress() {
        InterfaceMacHeader header = new InterfaceMacHeader("00:17:ab:e9:65:a0");
        assertEquals(header.getValue().length, 6);
        assertEquals(header.getString().toUpperCase(Locale.ENGLISH), "00:17:AB:E9:65:A0");
    }

    @Test
    public void parseRange() {
        RangeHeader header = new RangeHeader("bytes=1539686400-1540210688");
        assertEquals(header.getValue().getFirstByte(),new Long(1539686400));
        assertEquals(header.getValue().getLastByte(), new Long(1540210688));
        assertEquals(header.getString(), "bytes=1539686400-1540210688");
    }
    
    @Test
    public void parseContentRange() {
        ContentRangeHeader header = new ContentRangeHeader("bytes 1539686400-1540210688/21323123");
        assertEquals(header.getValue().getFirstByte(),new Long(1539686400));
        assertEquals(header.getValue().getLastByte(), new Long(1540210688));
        assertEquals(header.getValue().getByteLength(), new Long(21323123));
        assertEquals(header.getString(), "bytes 1539686400-1540210688/21323123");
    }
    
    @Test
    public void parsePragma() {
        PragmaHeader header = new PragmaHeader("no-cache");
        assertEquals(header.getValue().getValue(),"no-cache");
        assertEquals(header.getString(), "no-cache");
        
        header.setString("token=value");
        assertEquals(header.getValue().getToken(),"token");
        assertEquals(header.getValue().getValue(),"value");
        assertEquals(header.getString(), "token=value");
        
        header.setString("token=\"value\"");
        assertEquals(header.getValue().getToken(),"token");
        assertEquals(header.getValue().getValue(),"value");
        assertEquals(header.getString(), "token=\"value\"");
    }
}
