/*
 * Copyright 2016-present Open Networking Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.onosproject.incubator.net.virtual.impl;

import com.google.common.collect.Iterators;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.junit.TestUtils;
import org.onlab.osgi.TestServiceDirectory;
import org.onosproject.common.event.impl.TestEventDispatcher;
import org.onosproject.core.CoreService;
import org.onosproject.net.TenantId;
import org.onosproject.incubator.net.virtual.VirtualDevice;
import org.onosproject.incubator.net.virtual.VirtualHost;
import org.onosproject.incubator.net.virtual.VirtualNetwork;
import org.onosproject.incubator.net.virtual.store.impl.DistributedVirtualNetworkStore;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.NetTestTools;
import org.onosproject.net.TestDeviceParams;
import org.onosproject.net.host.HostService;
import org.onosproject.store.service.TestStorageService;

import java.util.Collection;
import java.util.Iterator;

import static org.junit.Assert.*;

/**
 * Junit tests for VirtualNetworkHostService.
 */
public class VirtualNetworkHostManagerTest extends TestDeviceParams {
    private final String tenantIdValue1 = "TENANT_ID1";

    private VirtualNetworkManager manager;
    private DistributedVirtualNetworkStore virtualNetworkManagerStore;
    private TestServiceDirectory testDirectory;

    @Before
    public void setUp() throws Exception {
        virtualNetworkManagerStore = new DistributedVirtualNetworkStore();

        CoreService coreService = new TestCoreService();
        TestUtils.setField(virtualNetworkManagerStore, "coreService", coreService);
        TestUtils.setField(virtualNetworkManagerStore, "storageService", new TestStorageService());
        virtualNetworkManagerStore.activate();

        manager = new VirtualNetworkManager();
        manager.store = virtualNetworkManagerStore;
        manager.coreService = coreService;
        NetTestTools.injectEventDispatcher(manager, new TestEventDispatcher());

        testDirectory = new TestServiceDirectory();
        TestUtils.setField(manager, "serviceDirectory", testDirectory);

        manager.activate();
    }

    @After
    public void tearDown() {
        virtualNetworkManagerStore.deactivate();
        manager.deactivate();
        NetTestTools.injectEventDispatcher(manager, null);
    }

    /**
     * Sets up a virtual network with hosts.
     *
     * @return virtual network
     */
    private VirtualNetwork setupVnet() {
        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));

        VirtualDevice virtualDevice1 =
                manager.createVirtualDevice(virtualNetwork.id(), DID1);
        VirtualDevice virtualDevice2 =
                manager.createVirtualDevice(virtualNetwork.id(), DID2);

        ConnectPoint hostCp1 = new ConnectPoint(DID1, P1);
        ConnectPoint hostCp2 = new ConnectPoint(DID2, P2);
        manager.createVirtualPort(virtualNetwork.id(), hostCp1.deviceId(), hostCp1.port(),
                new ConnectPoint(virtualDevice1.id(), hostCp1.port()));
        manager.createVirtualPort(virtualNetwork.id(), hostCp2.deviceId(), hostCp2.port(),
                new ConnectPoint(virtualDevice2.id(), hostCp2.port()));

        manager.createVirtualHost(virtualNetwork.id(), HID1, MAC1, VLAN1, LOC1, IPSET1);
        manager.createVirtualHost(virtualNetwork.id(), HID2, MAC2, VLAN2, LOC2, IPSET2);
        return virtualNetwork;
    }

    /**
     * Sets up a virtual network with no hosts.
     *
     * @return virtual network
     */
    private VirtualNetwork setupEmptyVnet() {
        manager.registerTenantId(TenantId.tenantId(tenantIdValue1));
        VirtualNetwork virtualNetwork = manager.createVirtualNetwork(TenantId.tenantId(tenantIdValue1));

        VirtualDevice virtualDevice1 =
                manager.createVirtualDevice(virtualNetwork.id(), DID1);
        VirtualDevice virtualDevice2 =
                manager.createVirtualDevice(virtualNetwork.id(), DID2);

        ConnectPoint hostCp1 = new ConnectPoint(DID1, P1);
        ConnectPoint hostCp2 = new ConnectPoint(DID2, P2);
        manager.createVirtualPort(virtualNetwork.id(), hostCp1.deviceId(), hostCp1.port(),
                new ConnectPoint(virtualDevice1.id(), hostCp1.port()));
        manager.createVirtualPort(virtualNetwork.id(), hostCp2.deviceId(), hostCp2.port(),
                new ConnectPoint(virtualDevice2.id(), hostCp2.port()));

        return virtualNetwork;
    }

    /**
     * Tests the getHosts(), getHost(), getHostsByXX(), getConnectedHosts() methods
     * on a non-empty virtual network.
     */
    @Test
    public void testGetHostsOnNonEmptyVnet() {
        VirtualNetwork virtualNetwork = setupEmptyVnet();
        VirtualHost vhost1 = manager.createVirtualHost(virtualNetwork.id(), HID1, MAC1, VLAN1, LOC1, IPSET1);
        VirtualHost vhost2 = manager.createVirtualHost(virtualNetwork.id(), HID2, MAC2, VLAN2, LOC2, IPSET2);
        HostService hostService = manager.get(virtualNetwork.id(), HostService.class);

        // test the getHosts() and getHostCount() methods
        Iterator<Host> itHosts = hostService.getHosts().iterator();
        assertEquals("The host set size did not match.", 2, Iterators.size(itHosts));
        assertEquals("The host count did not match.", 2, hostService.getHostCount());

        // test the getHost() method
        Host testHost = hostService.getHost(HID2);
        assertEquals("The expected host did not match.", vhost2, testHost);

        // test the getHostsByVlan(...) method
        Collection<Host> collHost = hostService.getHostsByVlan(VLAN1);
        assertEquals("The host set size did not match.", 1, collHost.size());
        assertTrue("The host did not match.", collHost.contains(vhost1));

        // test the getHostsByMac(...) method
        collHost = hostService.getHostsByMac(MAC2);
        assertEquals("The host set size did not match.", 1, collHost.size());
        assertTrue("The host did not match.", collHost.contains(vhost2));

        // test the getHostsByIp(...) method
        collHost = hostService.getHostsByIp(IP1);
        assertEquals("The host set size did not match.", 2, collHost.size());
        collHost = hostService.getHostsByIp(IP2);
        assertEquals("The host set size did not match.", 1, collHost.size());
        assertTrue("The host did not match.", collHost.contains(vhost1));

        // test the getConnectedHosts(ConnectPoint) method
        collHost = hostService.getConnectedHosts(LOC1);
        assertEquals("The host set size did not match.", 1, collHost.size());
        assertTrue("The host did not match.", collHost.contains(vhost1));

        // test the getConnectedHosts(DeviceId) method
        collHost = hostService.getConnectedHosts(DID2);
        assertEquals("The host set size did not match.", 1, collHost.size());
        assertTrue("The host did not match.", collHost.contains(vhost2));
    }

    /**
     * Tests the getHosts(), getHost(), getHostsByXX(), getConnectedHosts() methods
     * on an empty virtual network.
     */
    @Test
    public void testGetHostsOnEmptyVnet() {
        VirtualNetwork virtualNetwork = setupEmptyVnet();
        HostService hostService = manager.get(virtualNetwork.id(), HostService.class);

        // test the getHosts() and getHostCount() methods
        Iterator<Host> itHosts = hostService.getHosts().iterator();
        assertEquals("The host set size did not match.", 0, Iterators.size(itHosts));
        assertEquals("The host count did not match.", 0, hostService.getHostCount());

        // test the getHost() method
        Host testHost = hostService.getHost(HID2);
        assertNull("The host should be null.", testHost);

        // test the getHostsByVlan(...) method
        Collection<Host> collHost = hostService.getHostsByVlan(VLAN1);
        assertEquals("The host set size did not match.", 0, collHost.size());

        // test the getHostsByMac(...) method
        collHost = hostService.getHostsByMac(MAC2);
        assertEquals("The host set size did not match.", 0, collHost.size());

        // test the getHostsByIp(...) method
        collHost = hostService.getHostsByIp(IP1);
        assertEquals("The host set size did not match.", 0, collHost.size());

        // test the getConnectedHosts(ConnectPoint) method
        collHost = hostService.getConnectedHosts(LOC1);
        assertEquals("The host set size did not match.", 0, collHost.size());

        // test the getConnectedHosts(DeviceId) method
        collHost = hostService.getConnectedHosts(DID2);
        assertEquals("The host set size did not match.", 0, collHost.size());
    }

    /**
     * Tests querying for a host using a null host identifier.
     */
    @Test(expected = NullPointerException.class)
    public void testGetHostByNullId() {
        VirtualNetwork vnet = setupEmptyVnet();
        HostService hostService = manager.get(vnet.id(), HostService.class);

        hostService.getHost(null);
    }

    /**
     * Tests querying for hosts with null mac.
     */
    @Test(expected = NullPointerException.class)
    public void testGetHostsByNullMac() {
        VirtualNetwork vnet = setupEmptyVnet();
        HostService hostService = manager.get(vnet.id(), HostService.class);

        hostService.getHostsByMac(null);
    }

    /**
     * Tests querying for hosts with null vlan.
     */
    @Test(expected = NullPointerException.class)
    public void testGetHostsByNullVlan() {
        VirtualNetwork vnet = setupEmptyVnet();
        HostService hostService = manager.get(vnet.id(), HostService.class);

        hostService.getHostsByVlan(null);
    }

    /**
     * Tests querying for hosts with null ip.
     */
    @Test(expected = NullPointerException.class)
    public void testGetHostsByNullIp() {
        VirtualNetwork vnet = setupVnet();
        HostService hostService = manager.get(vnet.id(), HostService.class);

        hostService.getHostsByIp(null);
    }

    /**
     * Tests querying for connected hosts with null host location (connect point).
     */
    @Test(expected = NullPointerException.class)
    public void testGetConnectedHostsByNullLoc() {
        VirtualNetwork vnet = setupEmptyVnet();
        HostService hostService = manager.get(vnet.id(), HostService.class);

        hostService.getConnectedHosts((ConnectPoint) null);
    }

    /**
     * Tests querying for connected hosts with null device id.
     */
    @Test(expected = NullPointerException.class)
    public void testGetConnectedHostsByNullDeviceId() {
        VirtualNetwork vnet = setupVnet();
        HostService hostService = manager.get(vnet.id(), HostService.class);

        hostService.getConnectedHosts((DeviceId) null);
    }

}
