// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you 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 com.cloud.hypervisor.ovm3.resources.helpers;

import java.util.List;

import javax.naming.ConfigurationException;

import org.apache.log4j.Logger;

import com.cloud.agent.api.Answer;
import com.cloud.agent.api.CheckNetworkAnswer;
import com.cloud.agent.api.CheckNetworkCommand;
import com.cloud.agent.api.PingTestCommand;
import com.cloud.agent.api.to.NicTO;
import com.cloud.hypervisor.ovm3.objects.CloudstackPlugin;
import com.cloud.hypervisor.ovm3.objects.Connection;
import com.cloud.hypervisor.ovm3.objects.Network;
import com.cloud.hypervisor.ovm3.objects.Ovm3ResourceException;
import com.cloud.network.PhysicalNetworkSetupInfo;
import com.cloud.network.Networks.BroadcastDomainType;
import com.cloud.network.Networks.TrafficType;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.net.NetUtils;

public class Ovm3HypervisorNetwork {
    private static final Logger LOGGER = Logger
            .getLogger(Ovm3HypervisorNetwork.class);
    private Connection c;
    private Ovm3Configuration config;
    public Ovm3HypervisorNetwork(Connection conn, Ovm3Configuration ovm3config) {
        c = conn;
        config = ovm3config;
    }

    public void configureNetworking() throws ConfigurationException {
        /* TODO: setup meta tags for the management interface (probably
        * required with multiple interfaces)?
        */
        try {
           Network net = new Network(c);
           String controlIface = config.getAgentControlNetworkName();
           if (controlIface != null
                   && net.getInterfaceByName(controlIface) == null) {
               LOGGER.debug("starting " + controlIface);
               net.startOvsLocalConfig(controlIface);
               /* ovs replies too "fast" so the bridge can be "busy" */
               int contCount = 0;
               while (net.getInterfaceByName(controlIface) == null) {
                   LOGGER.debug("waiting for " + controlIface);
                   Thread.sleep(1 * 1000);
                   if (contCount > 9) {
                       throw new ConfigurationException("Unable to configure "
                               + controlIface + " on host "
                               + config.getAgentHostname());
                   }
                   contCount++;
               }
           } else {
               LOGGER.debug("already have " + controlIface);
           }
           /*
            * The bridge is remembered upon reboot, but not the IP or the
            * config. Zeroconf also adds the route again by default.
            */
           net.ovsIpConfig(controlIface, "static",
                   NetUtils.getLinkLocalGateway(),
                   NetUtils.getLinkLocalNetMask());
           CloudstackPlugin cSp = new CloudstackPlugin(c);
           cSp.ovsControlInterface(controlIface,
                   NetUtils.getLinkLocalCIDR());
        } catch (InterruptedException e) {
            LOGGER.error("interrupted?", e);
        } catch (Ovm3ResourceException e) {
            String msg = "Basic configuration failed on " + config.getAgentHostname();
            LOGGER.error(msg, e);
            throw new ConfigurationException(msg + ", " + e.getMessage());
        }
    }

    /**/
    private boolean isNetworkSetupByName(String nameTag) {
        if (nameTag != null) {
            LOGGER.debug("Looking for network setup by name " + nameTag);

            try {
                Network net = new Network(c);
                net.getInterfaceList();
                if (net.getBridgeByName(nameTag) != null) {
                    LOGGER.debug("Found bridge with name: " + nameTag);
                    return true;
                }
            } catch (Ovm3ResourceException e) {
                LOGGER.debug("Unxpected error looking for name: " + nameTag, e);
                return false;
            }
        }
        LOGGER.debug("No bridge with name: " + nameTag);
        return false;
    }

    /* this might have to change in the future, works for now... */
    public CheckNetworkAnswer execute(CheckNetworkCommand cmd) {
        LOGGER.debug("Checking if network name setup is done on "
                    + config.getAgentHostname());

        List<PhysicalNetworkSetupInfo> infoList = cmd
                .getPhysicalNetworkInfoList();
        /* here we assume all networks are set */
        for (PhysicalNetworkSetupInfo info : infoList) {
            if (info.getGuestNetworkName() == null) {
                info.setGuestNetworkName(config.getAgentGuestNetworkName());
            }
            if (info.getPublicNetworkName() == null) {
                info.setPublicNetworkName(config.getAgentPublicNetworkName());
            }
            if (info.getPrivateNetworkName() == null) {
                info.setPrivateNetworkName(config.getAgentPrivateNetworkName());
            }
            if (info.getStorageNetworkName() == null) {
                info.setStorageNetworkName(config.getAgentStorageNetworkName());
            }

            if (!isNetworkSetupByName(info.getGuestNetworkName())) {
                String msg = "Guest Physical Network id:"
                        + info.getPhysicalNetworkId()
                        + ", Guest Network is not configured on the backend by name "
                        + info.getGuestNetworkName();
                LOGGER.error(msg);
                return new CheckNetworkAnswer(cmd, false, msg);
            }
            if (!isNetworkSetupByName(info.getPrivateNetworkName())) {
                String msg = "Private Physical Network id:"
                        + info.getPhysicalNetworkId()
                        + ", Private Network is not configured on the backend by name "
                        + info.getPrivateNetworkName();
                LOGGER.error(msg);
                return new CheckNetworkAnswer(cmd, false, msg);
            }
            if (!isNetworkSetupByName(info.getPublicNetworkName())) {
                String msg = "Public Physical Network id:"
                        + info.getPhysicalNetworkId()
                        + ", Public Network is not configured on the backend by name "
                        + info.getPublicNetworkName();
                LOGGER.error(msg);
                return new CheckNetworkAnswer(cmd, false, msg);
            }
            /* Storage network is optional, will revert to private otherwise */
        }

        return new CheckNetworkAnswer(cmd, true,
                "Network Setup check by names is done");

    }

    public Answer execute(PingTestCommand cmd) {
        try {
            if (cmd.getComputingHostIp() != null) {
                CloudstackPlugin cSp = new CloudstackPlugin(c);
                if (!cSp.ping(cmd.getComputingHostIp())) {
                    return new Answer(cmd, false, "ping failed");
                }
            } else {
                return new Answer(cmd, false, "why asks me to ping a router???");
            }
            return new Answer(cmd, true, "success");
        } catch (Ovm3ResourceException e) {
            LOGGER.debug("Ping " + cmd.getComputingHostIp() + " failed", e);
            return new Answer(cmd, false, e.getMessage());
        }
    }

    private String createVlanBridge(String networkName, Integer vlanId)
            throws Ovm3ResourceException {
        if (vlanId < 1 || vlanId > 4094) {
            String msg = "Incorrect vlan " + vlanId
                    + ", needs to be between 1 and 4094";
            LOGGER.error(msg);
            throw new CloudRuntimeException(msg);
        }
        Network net = new Network(c);
        /* figure out if our bridged vlan exists, if not then create */
        String brName = networkName + "." + vlanId.toString();
        try {
            String physInterface = net.getPhysicalByBridgeName(networkName);
            if (net.getInterfaceByName(brName) == null) {
                net.startOvsVlanBridge(brName, physInterface, vlanId);
            } else {
                LOGGER.debug("Interface " + brName + " already exists");
            }
        } catch (Ovm3ResourceException e) {
            String msg = "Unable to create vlan " + vlanId.toString()
                    + " bridge for " + networkName;
            LOGGER.warn(msg + ": " + e);
            throw new CloudRuntimeException(msg + ":" + e.getMessage());
        }
        return brName;
    }
    /* getNetwork needs to be split in pure retrieval versus creation */
    public String getNetwork(NicTO nic) throws Ovm3ResourceException {
        String vlanId = null;
        String bridgeName = null;
        if (nic.getBroadcastType() == BroadcastDomainType.Vlan) {
            vlanId = BroadcastDomainType.getValue(nic.getBroadcastUri());
        }

        if (nic.getType() == TrafficType.Guest) {
            if (nic.getBroadcastType() == BroadcastDomainType.Vlan
                    && !"untagged".equalsIgnoreCase(vlanId)) {
                /* This is completely the wrong place for this, we should NEVER
                 * create a network when we're just trying to figure out if it's there
                 * The name of this is misleading and wrong.
                 */
                bridgeName = createVlanBridge(config.getAgentGuestNetworkName(),
                        Integer.valueOf(vlanId));
            } else {
                bridgeName = config.getAgentGuestNetworkName();
            }

            /* VLANs for other mgmt traffic ? */
        } else if (nic.getType() == TrafficType.Control) {
            bridgeName = config.getAgentControlNetworkName();
        } else if (nic.getType() == TrafficType.Public) {
            bridgeName = config.getAgentPublicNetworkName();
        } else if (nic.getType() == TrafficType.Management) {
            bridgeName = config.getAgentPrivateNetworkName();
        } else if (nic.getType() == TrafficType.Storage) {
            bridgeName = config.getAgentStorageNetworkName();
        } else {
            throw new CloudRuntimeException("Unknown network traffic type:"
                    + nic.getType());
        }
        return bridgeName;
    }
}
