/*
 * 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.network.element;

import com.cloud.agent.api.to.FirewallRuleTO;
import com.cloud.agent.api.to.NetworkACLTO;
import com.cloud.agent.api.to.PortForwardingRuleTO;
import org.midonet.client.dto.DtoRule;
import org.midonet.client.resource.*;
import com.google.common.collect.*;


import java.util.*;
// Used for translation between MidoNet firewall rules and
// CloudStack firewall rules
public class SimpleFirewallRule {

    public List<String> sourceCidrs;
    public String protocol;
    public String dstIp;
    public int dstPortStart = 0;
    public int dstPortEnd = 0;
    public int icmpType = 0;
    public int icmpCode = 0;

    private static BiMap<Integer,String> protocolNumberToString;
    static {
        protocolNumberToString = HashBiMap.create();
        protocolNumberToString.put(1, "icmp");
        protocolNumberToString.put(6, "tcp");
        protocolNumberToString.put(17, "udp");
        protocolNumberToString.put(0, "none");
    }

    public SimpleFirewallRule(FirewallRuleTO rule){
        // Destination IP (confusingly called SourceIP in FirewallRule attributes)
        dstIp = rule.getSrcIp();
        protocol = rule.getProtocol();

        if("icmp".equals(protocol)){
            icmpType = rule.getIcmpType();
            icmpCode = rule.getIcmpCode();
        } else {
            int[] portNumbers = rule.getSrcPortRange();

            // if port start and end are not set, they
            // should be 0,0, and that's already the case
            if(portNumbers != null && portNumbers.length == 2){
                dstPortStart = portNumbers[0];
                dstPortEnd = portNumbers[1];
            }
        }

        sourceCidrs = rule.getSourceCidrList();

        // If no CIDRs specified, it is an "all sources" rule
        if (sourceCidrs == null || sourceCidrs.isEmpty())
        {
            sourceCidrs = new ArrayList<String>();
            sourceCidrs.add("0.0.0.0/0");
        }
    }

    public SimpleFirewallRule(NetworkACLTO rule) {
        dstIp = "null";
        protocol = rule.getProtocol();

        if("icmp".equals(protocol)){
            icmpType = rule.getIcmpType();
            icmpCode = rule.getIcmpCode();
        } else {
            int[] portNumbers = rule.getSrcPortRange();

            // if port start and end are not set, they
            // should be 0,0, and that's already the case
            if(portNumbers != null && portNumbers.length == 2){
                dstPortStart = portNumbers[0];
                dstPortEnd = portNumbers[1];
            }
        }

        sourceCidrs = rule.getSourceCidrList();

        // If no CIDRs specified, it is an "all sources" rule
        if (sourceCidrs == null || sourceCidrs.isEmpty())
        {
            sourceCidrs = new ArrayList<String>();
            sourceCidrs.add("0.0.0.0/0");
        }
    }

    public SimpleFirewallRule(PortForwardingRuleTO rule) {
        dstIp = rule.getSrcIp();
        protocol = rule.getProtocol();

        int[] srcPortNumbers = rule.getSrcPortRange();
        int[] dstPortNumbers = rule.getDstPortRange();

        // if port start and end are not set, they
        // should be 0,0, and that's already the case
        if(srcPortNumbers != null && srcPortNumbers.length == 2 &&
           dstPortNumbers != null && dstPortNumbers.length == 2){
            dstPortStart = dstPortNumbers[0];
            dstPortEnd = srcPortNumbers[0];
        }

        sourceCidrs = new ArrayList<String>();
        sourceCidrs.add(rule.getDstIp());
    }

    public SimpleFirewallRule(Rule rule){

        String sourceIP = rule.getNwSrcAddress();
        int sourceLength = rule.getNwSrcLength();

        sourceCidrs = new ArrayList<String>();
        /*
         * Only one IP in the CIDR list
         * Port Forwarding Rules don't have sourceCidrs, but they do have
         * targets. Use those instead if they exist.
         */
        DtoRule.DtoNatTarget[] targets = rule.getNatTargets();
        if (targets != null) {
            sourceCidrs.add(targets[0].addressFrom);
        } else {
            sourceCidrs.add(String.format("%s/%d", sourceIP, sourceLength));
        }

        int protoNum = rule.getNwProto();
        protocol = SimpleFirewallRule.protocolNumberToString(protoNum);

        dstIp = rule.getNwDstAddress();

        if("icmp".equals(protocol)){
            if(rule.getTpSrc() != null && rule.getTpDst() != null){
                icmpType = rule.getTpSrc().start;
                icmpCode = rule.getTpDst().start;
            } else {
                icmpType = -1;
                icmpCode = -1;
            }

        } else {
            /*
             * If this is port forwarding, we want to take the start
             * port for the public port range, and the start port for
             * the private port range to uniquely identify this rule.
             */
            if (targets != null) {
                dstPortStart = targets[0].portFrom;
            } else {
                dstPortStart = rule.getTpDst().start;
            }
            dstPortEnd = rule.getTpDst().end;
        }

        // cidr, protocol, dstIp, dstPortStart, dstPortEnd, icmpType, icmpCode);
    }

    public static String protocolNumberToString(int protocolNumber){
        return protocolNumberToString.get(protocolNumber);
    }

    public static int stringToProtocolNumber(String protoString){
        return protocolNumberToString.inverse().get(protoString);
    }

    public int getFieldOne(){
        if(protocol.equals("icmp")){
            return icmpType;

        } else {
            return dstPortStart;
        }
    }

    public int getFieldTwo(){
        if(protocol.equals("icmp")){
            return icmpCode;
        } else {
            return dstPortEnd;
        }
    }

    public String[] toStringArray() {
        List<String> stringRules = new ArrayList<String>();

        // Create a rule string per source CIDR, since each MidoNet
        // rule is for one CIDR
        for(String sourceCidr : sourceCidrs){

            // Follows the rule String format defined in SetFirewallRulesCommand.java::generateFirewallRules()
            int field1 = getFieldOne();
            int field2 = getFieldTwo();

            String stringRule = String.format("%s:%s:%d:%d:%s:", dstIp, protocol, field1, field2, sourceCidr);
            stringRules.add(stringRule);
        }
        String[] stringArray = new String[stringRules.size()];
        return stringRules.toArray(stringArray);
    }
}
