/*
 * Copyright 2009 Red Hat, Inc.
 *
 * Red Hat 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 io.netty.handler.ipfilter;

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

/**
 * @author frederic bregier
 */
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;
   }

   /* (non-Javadoc)
    * @see org.jboss.netty.handler.ipfilter.CIDR#contains(java.net.InetAddress)
    */
   @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.
    * @param addr
    * @return the created InetAddress
    * @throws UnknownHostException
    * @throws UnknownHostException
    */
   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.
    * @param addr
    * @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.
    * @param address
    * @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;
   }
}
