//
// 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 org.apache.cloudstack.network.opendaylight.agent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.inject.Inject;
import javax.naming.ConfigurationException;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;

import org.apache.cloudstack.network.ExternalNetworkDeviceManager.NetworkDevice;
import org.apache.cloudstack.network.opendaylight.api.commands.AddOpenDaylightControllerCmd;
import org.apache.cloudstack.network.opendaylight.api.commands.DeleteOpenDaylightControllerCmd;
import org.apache.cloudstack.network.opendaylight.api.commands.ListOpenDaylightControllersCmd;
import org.apache.cloudstack.network.opendaylight.api.responses.OpenDaylightControllerResponse;
import org.apache.cloudstack.network.opendaylight.dao.OpenDaylightControllerMappingDao;
import org.apache.cloudstack.network.opendaylight.dao.OpenDaylightControllerVO;

import com.cloud.api.ApiDBUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.host.Host;
import com.cloud.host.HostVO;
import com.cloud.host.dao.HostDao;
import com.cloud.network.Network;
import com.cloud.network.Networks;
import com.cloud.network.PhysicalNetwork;
import com.cloud.network.PhysicalNetworkServiceProvider;
import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.network.dao.PhysicalNetworkDao;
import com.cloud.network.dao.PhysicalNetworkServiceProviderDao;
import com.cloud.network.dao.PhysicalNetworkServiceProviderVO;
import com.cloud.network.dao.PhysicalNetworkVO;
import com.cloud.resource.ResourceManager;
import com.cloud.resource.ResourceState;
import com.cloud.resource.ServerResource;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.db.TransactionCallback;
import com.cloud.utils.db.TransactionStatus;
import com.cloud.utils.exception.CloudRuntimeException;

public class OpenDaylightControllerResourceManagerImpl implements OpenDaylightControllerResourceManager {
    protected Logger logger = LogManager.getLogger(getClass());

    @Inject
    HostDao hostDao;
    @Inject
    ResourceManager resourceManager;
    @Inject
    PhysicalNetworkDao physicalNetworkDao;
    @Inject
    PhysicalNetworkServiceProviderDao physicalNetworkServiceProviderDao;
    @Inject
    OpenDaylightControllerMappingDao openDaylightControllerMappingDao;
    @Inject
    NetworkDao networkDao;

    @Override
    public List<Class<?>> getCommands() {
        List<Class<?>> commands = new ArrayList<Class<?>>();
        commands.add(AddOpenDaylightControllerCmd.class);
        commands.add(DeleteOpenDaylightControllerCmd.class);
        commands.add(ListOpenDaylightControllersCmd.class);
        return commands;
    }

    @Override
    public OpenDaylightControllerVO addController(AddOpenDaylightControllerCmd cmd) {
        ServerResource odlController = new OpenDaylightControllerResource();

        final String deviceName = NetworkDevice.OpenDaylightController.getName();
        NetworkDevice networkDevice = NetworkDevice.getNetworkDevice(deviceName);
        if (networkDevice == null) {
            throw new CloudRuntimeException("No network device found for name " + deviceName);
        }
        final Long physicalNetworkId = cmd.getPhysicalNetworkId();
        PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId);
        if (physicalNetwork == null) {
            throw new InvalidParameterValueException("Could not find phyical network with ID: " + physicalNetworkId);
        }
        long zoneId = physicalNetwork.getDataCenterId();

        final PhysicalNetworkServiceProviderVO ntwkSvcProvider = physicalNetworkServiceProviderDao.findByServiceProvider(physicalNetwork.getId(),
                networkDevice.getNetworkServiceProvder());
        if (ntwkSvcProvider == null) {
            throw new CloudRuntimeException(String.format("Network Service Provider: %s is not enabled in the physical network: %s to add this device",
                    networkDevice.getNetworkServiceProvder(), physicalNetwork));
        } else if (ntwkSvcProvider.getState() == PhysicalNetworkServiceProvider.State.Shutdown) {
            throw new CloudRuntimeException(String.format("Network Service Provider: %s is in shutdown state in the physical network: %s to add this device",
                    ntwkSvcProvider.getProviderName(), physicalNetwork));
        }

        final Map<String, String> hostParams = new HashMap<String, String>();
        hostParams.put("guid", UUID.randomUUID().toString());
        hostParams.put("zoneId", String.valueOf(physicalNetwork.getDataCenterId()));
        hostParams.put("physicalNetworkId", String.valueOf(physicalNetwork.getId()));
        hostParams.put("name", "ODL Controller - " + hostParams.get("guid"));
        hostParams.put("url", cmd.getUrl());
        hostParams.put("username", cmd.getUsername());
        hostParams.put("password", cmd.getPassword());

        Map<String, Object> hostdetails = new HashMap<String, Object>();
        hostdetails.putAll(hostParams);

        try {
            odlController.configure(hostParams.get("name"), hostdetails);
            final Host host = resourceManager.addHost(zoneId, odlController, Host.Type.L2Networking, hostParams);
            if (host != null) {
                return Transaction.execute(new TransactionCallback<OpenDaylightControllerVO>() {
                    @Override
                    public OpenDaylightControllerVO doInTransaction(TransactionStatus status) {
                        OpenDaylightControllerVO controller = new OpenDaylightControllerVO(host.getId(), physicalNetworkId, ntwkSvcProvider.getProviderName(), hostParams
                                .get("name"));
                        openDaylightControllerMappingDao.persist(controller);
                        return controller;
                    }
                });
            } else {
                throw new CloudRuntimeException("Failed to create host object for ODL Controller");
            }
        } catch (ConfigurationException e) {
            throw new CloudRuntimeException("Failed to add ODL Controller as a resource", e);
        }
    }

    @Override
    public void deleteController(DeleteOpenDaylightControllerCmd cmd) throws InvalidParameterValueException {
        OpenDaylightControllerVO controller = openDaylightControllerMappingDao.findById(cmd.getId());
        if (controller == null) {
            throw new InvalidParameterValueException("No ODL Controller with id " + cmd.getId());
        }

        // Find the physical network we work for
        Long physicalNetworkId = controller.getPhysicalNetworkId();
        PhysicalNetworkVO physicalNetwork = physicalNetworkDao.findById(physicalNetworkId);
        if (physicalNetwork != null) {
            // Lets see if there are networks that use us
            List<NetworkVO> networkList = networkDao.listByPhysicalNetwork(physicalNetworkId);

            if (networkList != null) {
                // Networks with broadcast type lswitch are ours
                for (NetworkVO network : networkList) {
                    if (network.getBroadcastDomainType() == Networks.BroadcastDomainType.OpenDaylight) {
                        if ((network.getState() != Network.State.Shutdown) && (network.getState() != Network.State.Destroy)) {
                            throw new CloudRuntimeException("This Controller can not be deleted as there are one or more logical networks provisioned by cloudstack.");
                        }
                    }
                }
            }
        }

        HostVO host = hostDao.findById(controller.getHostId());
        Long hostId = host.getId();

        host.setResourceState(ResourceState.Maintenance);
        hostDao.update(hostId, host);
        resourceManager.deleteHost(hostId, false, false);

        openDaylightControllerMappingDao.remove(cmd.getId());
    }

    @Override
    public List<OpenDaylightControllerVO> listControllers(ListOpenDaylightControllersCmd cmd) {
        if (cmd.getId() != null) {
            List<OpenDaylightControllerVO> foundControllers = new ArrayList<OpenDaylightControllerVO>();
            OpenDaylightControllerVO controller = openDaylightControllerMappingDao.findById(cmd.getId());
            if (controller != null) {
                foundControllers.add(controller);
            }
            return foundControllers;
        } else if (cmd.getPhysicalNetworkId() != null) {
            return openDaylightControllerMappingDao.listByPhysicalNetwork(cmd.getPhysicalNetworkId());
        }
        return openDaylightControllerMappingDao.listAll();
    }

    @Override
    public OpenDaylightControllerResponse createResponseFromVO(OpenDaylightControllerVO controller) {
        OpenDaylightControllerResponse response = new OpenDaylightControllerResponse();
        HostVO controllerHost = hostDao.findById(controller.getHostId());
        hostDao.loadDetails(controllerHost);

        PhysicalNetwork pnw = ApiDBUtils.findPhysicalNetworkById(controller.getPhysicalNetworkId());
        if (pnw != null) {
            response.setPhysicalNetworkId(pnw.getUuid());
        }

        response.setObjectName("opendaylightcontroller");
        response.setId(controller.getUuid());
        response.setUrl(controllerHost.getDetail("url"));
        response.setName(controllerHost.getDetail("name"));
        response.setUsername(controllerHost.getDetail("username"));

        return response;
    }

}
