/*
 * Copyright 2012 The Netty Project
 *
 * The Netty Project 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.jboss.netty.handler.ipfilter;

import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;

/**
 */
public class CIDR4 extends CIDR {
    /** The integer for the base address */
    private int addressInt;

    /** The integer for the end address */
    private final int addressEndInt;

    /**
     * @param newaddr
     * @param mask
     */
    protected CIDR4(Inet4Address newaddr, int mask) {
        cidrMask = mask;
        addressInt = ipv4AddressToInt(newaddr);
        int newmask = ipv4PrefixLengthToMask(mask);
        addressInt &= newmask;
        try {
            baseAddress = intToIPv4Address(addressInt);
        } catch (UnknownHostException e) {
            // this should never happen
        }
        addressEndInt = addressInt + ipv4PrefixLengthToLength(cidrMask) - 1;
    }

    @Override
    public InetAddress getEndAddress() {
        try {
            return intToIPv4Address(addressEndInt);
        } catch (UnknownHostException e) {
            // this should never happen
            return null;
        }
    }

    public int compareTo(CIDR arg) {
        if (arg instanceof CIDR6) {
            byte[] address = getIpV4FromIpV6((Inet6Address) arg.baseAddress);
            int net = ipv4AddressToInt(address);
            if (net == addressInt && arg.cidrMask == cidrMask) {
                return 0;
            }
            if (net < addressInt) {
                return 1;
            } else if (net > addressInt) {
                return -1;
            } else if (arg.cidrMask < cidrMask) {
                return -1;
            }
            return 1;
        }
        CIDR4 o = (CIDR4) arg;
        if (o.addressInt == addressInt && o.cidrMask == cidrMask) {
            return 0;
        }
        if (o.addressInt < addressInt) {
            return 1;
        } else if (o.addressInt > addressInt) {
            return -1;
        } else if (o.cidrMask < cidrMask) {
            // greater Mask means less IpAddresses so -1
            return -1;
        }
        return 1;
    }

    @Override
    public boolean contains(InetAddress inetAddress) {
        int search = ipv4AddressToInt(inetAddress);
        return search >= addressInt && search <= addressEndInt;
    }

    /**
     * Given an IPv4 baseAddress length, return the block length.  I.e., a
     * baseAddress length of 24 will return 256.
     */
    private static int ipv4PrefixLengthToLength(int prefix_length) {
        return 1 << 32 - prefix_length;
    }

    /**
     * Given a baseAddress length, return a netmask.  I.e, a baseAddress length
     * of 24 will return 0xFFFFFF00.
     */
    private static int ipv4PrefixLengthToMask(int prefix_length) {
        return ~((1 << 32 - prefix_length) - 1);
    }

    /**
     * Convert an integer into an (IPv4) InetAddress.
     *
     * @return the created InetAddress
     */
    private static InetAddress intToIPv4Address(int addr) throws UnknownHostException {
        byte[] a = new byte[4];
        a[0] = (byte) (addr >> 24 & 0xFF);
        a[1] = (byte) (addr >> 16 & 0xFF);
        a[2] = (byte) (addr >> 8 & 0xFF);
        a[3] = (byte) (addr & 0xFF);
        return InetAddress.getByAddress(a);
    }

    /**
     * Given an IPv4 address, convert it into an integer.
     *
     * @return the integer representation of the InetAddress
     * @throws IllegalArgumentException if the address is really an
     *                                  IPv6 address.
     */
    private static int ipv4AddressToInt(InetAddress addr) {
        byte[] address = null;
        if (addr instanceof Inet6Address) {
            address = getIpV4FromIpV6((Inet6Address) addr);
        } else {
            address = addr.getAddress();
        }
        return ipv4AddressToInt(address);
    }

    /**
     * Given an IPv4 address as array of bytes, convert it into an integer.
     *
     * @return the integer representation of the InetAddress
     * @throws IllegalArgumentException if the address is really an
     *                                  IPv6 address.
     */
    private static int ipv4AddressToInt(byte[] address) {
        int net = 0;
        for (byte addres : address) {
            net <<= 8;
            net |= addres & 0xFF;
        }
        return net;
    }
}
