// 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.test;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;

import com.cloud.network.Networks.TrafficType;
import com.cloud.utils.component.ComponentContext;
import com.cloud.utils.db.DB;
import com.cloud.utils.db.Transaction;
import com.cloud.utils.net.NetUtils;

public class PodZoneConfig {

    public static void main(String[] args) {
        PodZoneConfig config = ComponentContext.inject(PodZoneConfig.class);
        //config.run(args);
        System.exit(0);
    }

    public void savePod(boolean printOutput, long id, String name, long dcId, String gateway, String cidr, int vlanStart, int vlanEnd) {
        // Check that the cidr was valid
        if (!IPRangeConfig.validCIDR(cidr)) printError("Please enter a valid CIDR for pod: " + name);

        // Get the individual cidrAddress and cidrSize values
        String[] cidrPair = cidr.split("\\/");
        String cidrAddress = cidrPair[0];
        String cidrSize = cidrPair[1];

        String sql = null;
        if (id != -1) sql = "INSERT INTO `cloud`.`host_pod_ref` (id, name, data_center_id, gateway, cidr_address, cidr_size) " + "VALUES ('" + id + "','" + name + "','" + dcId + "','" + gateway + "','" + cidrAddress + "','" + cidrSize + "')";
        else sql = "INSERT INTO `cloud`.`host_pod_ref` (name, data_center_id, gateway, cidr_address, cidr_size) " + "VALUES ('" + name + "','" + dcId + "','" + gateway + "','" + cidrAddress + "','" + cidrSize + "')";

        DatabaseConfig.saveSQL(sql, "Failed to save pod due to exception. Please contact Cloud Support.");

        if (printOutput) System.out.println("Successfuly saved pod.");
    }

    public void checkAllPodCidrSubnets() {
        Vector<Long> allZoneIDs = getAllZoneIDs();
        for (Long dcId : allZoneIDs) {
            HashMap<Long, Vector<Object>> currentPodCidrSubnets = getCurrentPodCidrSubnets(dcId.longValue());
            String result = checkPodCidrSubnets(dcId.longValue(), currentPodCidrSubnets);
            if (!result.equals("success")) printError(result);
        }
    }

    private String checkPodCidrSubnets(long dcId, HashMap<Long, Vector<Object>> currentPodCidrSubnets) {

//		DataCenterDao _dcDao = null;
//        final ComponentLocator locator = ComponentLocator.getLocator("management-server");

//        _dcDao = locator.getDao(DataCenterDao.class);
        // For each pod, return an error if any of the following is true:
        // 1. The pod's CIDR subnet conflicts with the guest network subnet
        // 2. The pod's CIDR subnet conflicts with the CIDR subnet of any other pod

        String zoneName  = PodZoneConfig.getZoneName(dcId);

        //get the guest network cidr and guest netmask from the zone
//		DataCenterVO dcVo = _dcDao.findById(dcId);

        String guestNetworkCidr = IPRangeConfig.getGuestNetworkCidr(dcId);

        if (guestNetworkCidr == null || guestNetworkCidr.isEmpty()) return "Please specify a valid guest cidr";
        String[] cidrTuple = guestNetworkCidr.split("\\/");

        String guestIpNetwork = NetUtils.getIpRangeStartIpFromCidr(cidrTuple[0], Long.parseLong(cidrTuple[1]));
        long guestCidrSize = Long.parseLong(cidrTuple[1]);

        // Iterate through all pods in this zone
        for (Long podId : currentPodCidrSubnets.keySet()) {
            String podName;
            if (podId.longValue() == -1) podName = "newPod";
            else podName = PodZoneConfig.getPodName(podId.longValue(), dcId);

            Vector<Object> cidrPair = currentPodCidrSubnets.get(podId);
            String cidrAddress = (String) cidrPair.get(0);
            long cidrSize = ((Long) cidrPair.get(1)).longValue();

            long cidrSizeToUse = -1;
            if (cidrSize < guestCidrSize) cidrSizeToUse = cidrSize;
            else cidrSizeToUse = guestCidrSize;

            String cidrSubnet = NetUtils.getCidrSubNet(cidrAddress, cidrSizeToUse);
            String guestSubnet = NetUtils.getCidrSubNet(guestIpNetwork, cidrSizeToUse);

            // Check that cidrSubnet does not equal guestSubnet
            if (cidrSubnet.equals(guestSubnet)) {
                if (podName.equals("newPod")) {
                    return "The subnet of the pod you are adding conflicts with the subnet of the Guest IP Network. Please specify a different CIDR.";
                } else {
                    return "Warning: The subnet of pod " + podName + " in zone " + zoneName + " conflicts with the subnet of the Guest IP Network. Please change either the pod's CIDR or the Guest IP Network's subnet, and re-run install-vmops-management.";
                }
            }

            // Iterate through the rest of the pods
            for (Long otherPodId : currentPodCidrSubnets.keySet()) {
                if (podId.equals(otherPodId)) continue;

                // Check that cidrSubnet does not equal otherCidrSubnet
                Vector<Object> otherCidrPair = currentPodCidrSubnets.get(otherPodId);
                String otherCidrAddress = (String) otherCidrPair.get(0);
                long otherCidrSize = ((Long) otherCidrPair.get(1)).longValue();

                if (cidrSize < otherCidrSize) cidrSizeToUse = cidrSize;
                else cidrSizeToUse = otherCidrSize;

                cidrSubnet = NetUtils.getCidrSubNet(cidrAddress, cidrSizeToUse);
                String otherCidrSubnet = NetUtils.getCidrSubNet(otherCidrAddress, cidrSizeToUse);

                if (cidrSubnet.equals(otherCidrSubnet)) {
                    String otherPodName = PodZoneConfig.getPodName(otherPodId.longValue(), dcId);
                    if (podName.equals("newPod")) {
                        return "The subnet of the pod you are adding conflicts with the subnet of pod " + otherPodName + " in zone " + zoneName + ". Please specify a different CIDR.";
                    } else {
                        return "Warning: The pods " + podName + " and " + otherPodName + " in zone " + zoneName + " have conflicting CIDR subnets. Please change the CIDR of one of these pods.";
                    }
                }
            }
        }

        return "success";
    }

    @DB
    protected HashMap<Long, Vector<Object>> getCurrentPodCidrSubnets(long dcId) {
        HashMap<Long, Vector<Object>> currentPodCidrSubnets = new HashMap<Long, Vector<Object>>();

        String selectSql = "SELECT id, cidr_address, cidr_size FROM host_pod_ref WHERE data_center_id=" + dcId;
        Transaction txn = Transaction.currentTxn();
        try {
            PreparedStatement stmt = txn.prepareAutoCloseStatement(selectSql);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()) {
                Long podId = rs.getLong("id");
                String cidrAddress = rs.getString("cidr_address");
                long cidrSize = rs.getLong("cidr_size");
                Vector<Object> cidrPair = new Vector<Object>();
                cidrPair.add(0, cidrAddress);
                cidrPair.add(1, new Long(cidrSize));
                currentPodCidrSubnets.put(podId, cidrPair);
            }
        } catch (SQLException ex) {
            System.out.println(ex.getMessage());
            printError("There was an issue with reading currently saved pod CIDR subnets. Please contact Cloud Support.");
            return null;
        }

        return currentPodCidrSubnets;
    }

    public void deletePod(String name, long dcId) {
        String sql = "DELETE FROM `cloud`.`host_pod_ref` WHERE name=\"" + name + "\" AND data_center_id=\"" + dcId + "\"";
        DatabaseConfig.saveSQL(sql, "Failed to delete pod due to exception. Please contact Cloud Support.");
    }

    public long getVlanDbId(String zone, String vlanId) {
        long zoneId = getZoneId(zone);

        return DatabaseConfig.getDatabaseValueLong("SELECT * FROM `cloud`.`vlan` WHERE data_center_id=\"" + zoneId + "\" AND vlan_id =\"" + vlanId + "\"", "id",
                "Unable to start DB connection to read vlan DB id. Please contact Cloud Support.");
    }

    public List<String> modifyVlan(String zone, boolean add, String vlanId, String vlanGateway, String vlanNetmask, String pod, String vlanType, String ipRange, long networkId, long physicalNetworkId) {
        // Check if the zone is valid
        long zoneId = getZoneId(zone);
        if (zoneId == -1)
            return genReturnList("false", "Please specify a valid zone.");

        //check if physical network is valid
        long physicalNetworkDbId = checkPhysicalNetwork(physicalNetworkId);
        if (physicalNetworkId == -1)
            return genReturnList("false", "Please specify a valid physical network.");


        Long podId = pod!=null?getPodId(pod, zone):null;
        if (podId != null && podId == -1)
            return genReturnList("false", "Please specify a valid pod.");

        if (add) {

            // Make sure the gateway is valid
            if (!NetUtils.isValidIp(vlanGateway))
                return genReturnList("false", "Please specify a valid gateway.");

            // Make sure the netmask is valid
            if (!NetUtils.isValidIp(vlanNetmask))
                return genReturnList("false", "Please specify a valid netmask.");

            // Check if a vlan with the same vlanId already exists in the specified zone
            if (getVlanDbId(zone, vlanId) != -1)
                return genReturnList("false", "A VLAN with the specified VLAN ID already exists in zone " + zone + ".");

            /*
    		// Check if another vlan in the same zone has the same subnet
    		String newVlanSubnet = NetUtils.getSubNet(vlanGateway, vlanNetmask);
    		List<VlanVO> vlans = _vlanDao.findByZone(zoneId);
    		for (VlanVO vlan : vlans) {
    			String currentVlanSubnet = NetUtils.getSubNet(vlan.getVlanGateway(), vlan.getVlanNetmask());
    			if (newVlanSubnet.equals(currentVlanSubnet))
    				return genReturnList("false", "The VLAN with ID " + vlan.getVlanId() + " in zone " + zone + " has the same subnet. Please specify a different gateway/netmask.");
    		}
             */

            // Everything was fine, so persist the VLAN
            saveVlan(zoneId, podId, vlanId, vlanGateway, vlanNetmask, vlanType, ipRange, networkId, physicalNetworkDbId);
            if (podId != null) {
                long vlanDbId = getVlanDbId(zone, vlanId);
                String sql = "INSERT INTO `cloud`.`pod_vlan_map` (pod_id, vlan_db_id) " + "VALUES ('" + podId + "','" + vlanDbId  + "')";
                DatabaseConfig.saveSQL(sql, "Failed to save pod_vlan_map due to exception vlanDbId=" + vlanDbId + ", podId=" + podId + ". Please contact Cloud Support.");
            }

            return genReturnList("true", "Successfully added VLAN.");

        } else {
            return genReturnList("false", "That operation is not suppored.");
        }

        /*
    	else {

    		// Check if a VLAN actually exists in the specified zone
    		long vlanDbId = getVlanDbId(zone, vlanId);
    		if (vlanDbId == -1)
    			return genReturnList("false", "A VLAN with ID " + vlanId + " does not exist in zone " + zone);

    		// Check if there are any public IPs that are in the specified vlan.
    		List<IPAddressVO> ips = _publicIpAddressDao.listByVlanDbId(vlanDbId);
    		if (ips.size() != 0)
    			return genReturnList("false", "Please delete all IP addresses that are in VLAN " + vlanId + " before deleting the VLAN.");

    		// Delete the vlan
    		_vlanDao.delete(vlanDbId);

    		return genReturnList("true", "Successfully deleted VLAN.");
    	}
         */
    }

    @DB
    public void saveZone(boolean printOutput, long id, String name, String dns1, String dns2, String dns3, String dns4, String guestNetworkCidr, String networkType) {

        if (printOutput) System.out.println("Saving zone, please wait...");

        String columns = null;
        String values = null;

        if (id != -1) {
            columns = "(id, name";
            values = "('" + id + "','" + name + "'";
        } else {
            columns = "(name";
            values = "('" + name + "'";
        }

        if (dns1 != null) {
            columns += ", dns1";
            values += ",'" + dns1 + "'";
        }

        if (dns2 != null) {
            columns += ", dns2";
            values += ",'" + dns2 + "'";
        }

        if (dns3 != null) {
            columns += ", internal_dns1";
            values += ",'" + dns3 + "'";
        }

        if (dns4 != null) {
            columns += ", internal_dns2";
            values += ",'" + dns4 + "'";
        }

        if(guestNetworkCidr != null) {
            columns += ", guest_network_cidr";
            values += ",'" + guestNetworkCidr + "'";
        }

        if(networkType != null) {
            columns += ", networktype";
            values += ",'" + networkType + "'";
        }

        columns += ", uuid";
        values += ", UUID()";

        columns += ")";
        values += ")";

        String sql = "INSERT INTO `cloud`.`data_center` " + columns +  " VALUES " + values;

        DatabaseConfig.saveSQL(sql, "Failed to save zone due to exception. Please contact Cloud Support.");

        if (printOutput) System.out.println("Successfully saved zone.");
    }

    @DB
    public void savePhysicalNetwork(boolean printOutput, long id, long dcId, int vnetStart, int vnetEnd) {

        if (printOutput) System.out.println("Saving physical network, please wait...");

        String columns = null;
        String values = null;

        columns = "(id ";
        values = "('" + id + "'";

        columns += ", name ";
        values += ",'physical network'";

        columns += ", data_center_id ";
        values += ",'" + dcId + "'";

        //save vnet information
        columns += ", vnet";
        values += ",'" + vnetStart + "-" + vnetEnd + "'";

        columns += ", state";
        values += ", 'Enabled'";

        columns += ", uuid";
        values += ", UUID()";

        columns += ")";
        values += ")";

        String sql = "INSERT INTO `cloud`.`physical_network` " + columns +  " VALUES " + values;

        DatabaseConfig.saveSQL(sql, "Failed to save physical network due to exception. Please contact Cloud Support.");

        // Hardcode the vnet range to be the full range
        int begin = 0x64;
        int end = 64000;

        // If vnet arguments were passed in, use them
        if (vnetStart != -1 && vnetEnd != -1) {
            begin = vnetStart;
            end = vnetEnd;
        }

        String insertVnet = "INSERT INTO `cloud`.`op_dc_vnet_alloc` (vnet, data_center_id, physical_network_id) VALUES ( ?, ?, ?)";

        Transaction txn = Transaction.currentTxn();
        try {
            PreparedStatement stmt = txn.prepareAutoCloseStatement(insertVnet);
            for (int i = begin; i <= end; i++) {
                stmt.setString(1, Integer.toString(i));
                stmt.setLong(2, dcId);
                stmt.setLong(3, id);
                stmt.addBatch();
            }
            stmt.executeBatch();
        } catch (SQLException ex) {
            printError("Error creating vnet for the physical network. Please contact Cloud Support.");
        }

        //add default traffic types

        //get default Xen network labels
        String defaultXenPrivateNetworkLabel = getDefaultXenNetworkLabel(TrafficType.Management);
        String defaultXenPublicNetworkLabel = getDefaultXenNetworkLabel(TrafficType.Public);
        String defaultXenStorageNetworkLabel = getDefaultXenNetworkLabel(TrafficType.Storage);
        String defaultXenGuestNetworkLabel = getDefaultXenNetworkLabel(TrafficType.Guest);

        String insertTraficType = "INSERT INTO `cloud`.`physical_network_traffic_types` " +
                "(physical_network_id, traffic_type, xen_network_label) VALUES ( ?, ?, ?)";

        try {
            PreparedStatement stmt = txn.prepareAutoCloseStatement(insertTraficType);
            for (TrafficType traffic : TrafficType.values()) {
                if(traffic.equals(TrafficType.Control) || traffic.equals(TrafficType.Vpn) ||
                        traffic.equals(TrafficType.None)){
                    continue;
                }
                stmt.setLong(1, id);
                stmt.setString(2, traffic.toString());
                if(traffic.equals(TrafficType.Public)){
                    stmt.setString(3, defaultXenPublicNetworkLabel);
                }else if(traffic.equals(TrafficType.Management)){
                    stmt.setString(3, defaultXenPrivateNetworkLabel);
                }else if(traffic.equals(TrafficType.Storage)){
                    stmt.setString(3, defaultXenStorageNetworkLabel);
                }else if(traffic.equals(TrafficType.Guest)){
                    stmt.setString(3, defaultXenGuestNetworkLabel);
                }

                stmt.addBatch();
            }
            stmt.executeBatch();
        } catch (SQLException ex) {
            printError("Error adding default traffic types for the physical network. Please contact Cloud Support.");
        }

        if (printOutput) System.out.println("Successfully saved physical network.");
    }

    private String getDefaultXenNetworkLabel(TrafficType trafficType){
        String xenLabel = null;
        String configName = null;
        switch(trafficType){
        case Public: configName = "xen.public.network.device";
        break;
        case Guest: configName = "xen.guest.network.device";
        break;
        case Storage: configName = "xen.storage.network.device1";
        break;
        case Management: configName = "xen.private.network.device";
        break;
        }

        if(configName != null){
            xenLabel = getConfiguredValue(configName);
        }
        return xenLabel;
    }

    public static String getConfiguredValue(String configName)
    {
        return DatabaseConfig.getDatabaseValueString("SELECT value FROM `cloud`.`configuration` where name = \"" + configName + "\"","value",
                "Unable to start DB connection to read configuration. Please contact Cloud Support.");
    }

    public void deleteZone(String name) {
        String sql = "DELETE FROM `cloud`.`data_center` WHERE name=\"" + name + "\"";
        DatabaseConfig.saveSQL(sql, "Failed to delete zone due to exception. Please contact Cloud Support.");
    }

    public void saveVlan(long zoneId, Long podId, String vlanId, String vlanGateway, String vlanNetmask, String vlanType, String ipRange, long networkId, long physicalNetworkId) {
        String sql = "INSERT INTO `cloud`.`vlan` (vlan_id, vlan_gateway, vlan_netmask, data_center_id, vlan_type, description, network_id, physical_network_id) " + "VALUES ('" + vlanId + "','" + vlanGateway + "','" + vlanNetmask + "','" + zoneId + "','" + vlanType + "','" + ipRange +  "','" + networkId +  "','" + physicalNetworkId + "')";
        DatabaseConfig.saveSQL(sql, "Failed to save vlan due to exception. Please contact Cloud Support.");
    }

    public static long getPodId(String pod, String zone) {
        long dcId = getZoneId(zone);
        String selectSql = "SELECT * FROM `cloud`.`host_pod_ref` WHERE name = \"" + pod + "\" AND data_center_id = \"" + dcId + "\"";
        String errorMsg = "Could not read pod ID fro mdatabase. Please contact Cloud Support.";
        return DatabaseConfig.getDatabaseValueLong(selectSql, "id", errorMsg);
    }

    public static long getPodId(String pod, long dcId) {
        String selectSql = "SELECT * FROM `cloud`.`host_pod_ref` WHERE name = \"" + pod + "\" AND data_center_id = \"" + dcId + "\"";
        String errorMsg = "Could not read pod ID fro mdatabase. Please contact Cloud Support.";
        return DatabaseConfig.getDatabaseValueLong(selectSql, "id", errorMsg);
    }

    public static long getZoneId(String zone) {
        String selectSql = "SELECT * FROM `cloud`.`data_center` WHERE name = \"" + zone + "\"";
        String errorMsg = "Could not read zone ID from database. Please contact Cloud Support.";
        return DatabaseConfig.getDatabaseValueLong(selectSql, "id", errorMsg);
    }

    public static long checkPhysicalNetwork(long physicalNetworkId) {
        String selectSql = "SELECT * FROM `cloud`.`physical_network` WHERE id = \"" + physicalNetworkId + "\"";
        String errorMsg = "Could not read physicalNetwork ID from database. Please contact Cloud Support.";
        return DatabaseConfig.getDatabaseValueLong(selectSql, "id", errorMsg);
    }	

    @DB
    public Vector<Long> getAllZoneIDs() {
        Vector<Long> allZoneIDs = new Vector<Long>();

        String selectSql = "SELECT id FROM data_center";
        Transaction txn = Transaction.currentTxn();
        try {
            PreparedStatement stmt = txn.prepareAutoCloseStatement(selectSql);
            ResultSet rs = stmt.executeQuery();
            while (rs.next()) {
                Long dcId = rs.getLong("id");
                allZoneIDs.add(dcId);
            }
        } catch (SQLException ex) {
            System.out.println(ex.getMessage());
            printError("There was an issue with reading zone IDs. Please contact Cloud Support.");
            return null;
        }

        return allZoneIDs;
    }


    public static boolean validPod(String pod, String zone) {
        return (getPodId(pod, zone) != -1);
    }

    public static boolean validZone(String zone) {
        return (getZoneId(zone) != -1);
    }

    public static String getPodName(long podId, long dcId) {
        return DatabaseConfig.getDatabaseValueString("SELECT * FROM `cloud`.`host_pod_ref` WHERE id=" + podId + " AND data_center_id=" + dcId, "name",
                "Unable to start DB connection to read pod name. Please contact Cloud Support.");
    }

    public static String getZoneName(long dcId) {
        return DatabaseConfig.getDatabaseValueString("SELECT * FROM `cloud`.`data_center` WHERE id=" + dcId, "name",
                "Unable to start DB connection to read zone name. Please contact Cloud Support.");
    }

    private static void printError(String message) {
        DatabaseConfig.printError(message);
    }

    private List<String> genReturnList(String success, String message) {
        List<String> returnList = new ArrayList<String>();
        returnList.add(0, success);
        returnList.add(1, message);
        return returnList;
    }

}
