#
#   Copyright (C) Stonesoft Corporation 2010 - 2013.
#   All rights reserved.
#
#   The StoneGate software, manuals, and technical
#   literature may not be reproduced in any form or
#   by any means except by permission in writing from
#   Stonesoft Corporation.
#

module IPForge
  class BrokenIPv6Address < BrokenAddressException
  end
  
  #Example IPv6 address: 2001:0db8:1234:0000:0000:0000:0000:0000
  #Double colons can be used to skip zero bytes:
  #   2001:0db8:0000:0000:0000:0000:1428:57ab
  #   2001:0db8:0000:0000:0000::1428:57ab
  #   2001:0db8:0:0:0:0:1428:57ab
  #   2001:0db8:0:0::1428:57ab
  #   2001:0db8::1428:57ab
  #   2001:db8::1428:57ab
  
  class IPv6Address
    S_LOOPBACK = "::1"
    S_COMPLETE_LOOPBACK = "0000:0000:0000:0000:0000:0000:0000:0001"
    attr_accessor :IP_string, :IP_num
            
    def initialize(value)
      @IP_string = ""
      @IP_num = 0
          
      if value.instance_of? String
        @IP_string = value

        if @IP_string == S_LOOPBACK
          final_ip = S_COMPLETE_LOOPBACK
        else
          arr = nil
          arr = @IP_string.split(":")
          if arr.size <= 8
            final_ip = ""
            arr.size.times do |i|
              part = arr[i]
              if part != ""
                if not part =~ /^[a-fA-F0-9]+$/
                  raise BrokenIPv6Address.new("String #{value} could not be converted into legal IPv6 addressess - value #{part} malformed!")
                end

                if part.size == 4
                  final_ip << part + ":"
                else
                  (4 - part.size).times do
                    final_ip << "0"
                  end
                  final_ip << part + ":"
                end
              else
                #We got a ::
                (8 - arr.size + 1).times do
                  final_ip << "0000:"
                end
              end
            end
            final_ip.chop! #Remove trailing :
          else
            raise BrokenIPv6Address.new("String #{value} could not be converted into a legal IPv6 address! Got too many parts separated by :")
          end
        end
        
        
        if not final_ip =~ /^[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}$/
          raise BrokenIPv6Address.new("Got malformed ip #{final_ip} out of input string #{value} - Internal Error")
        end
        
        @IP_string = final_ip
        
        parts = final_ip.split(":")
        @IP_num = (parts[0].hex << 112) + (parts[1].hex << 96) + (parts[2].hex << 80) + (parts[3].hex << 64) +
                  (parts[4].hex << 48) + (parts[5].hex << 32) + (parts[6].hex << 16) + (parts[7].hex)
      elsif value.instance_of? Fixnum or value.instance_of? Bignum
        @IP_num = value
        
        if @IP_num < 0 or @IP_num > 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
          raise BrokenIPv6Address.new("Value #{value.to_s(base=16)} could not be converted into a legal IP address, should be between [0x0,0xFFFFFFFF]")
        end
        
        @IP_string = sprintf("%4.4x", ((@IP_num >> 112) & 0xffff)) + ":" + 
                     sprintf("%4.4x", ((@IP_num >> 96) & 0xffff)) + ":" +
                     sprintf("%4.4x", ((@IP_num >> 80) & 0xffff)) + ":" +
                     sprintf("%4.4x", ((@IP_num >> 64) & 0xffff)) + ":" +
                     sprintf("%4.4x", ((@IP_num >> 48) & 0xffff)) + ":" +
                     sprintf("%4.4x", ((@IP_num >> 32) & 0xffff)) + ":" +
                     sprintf("%4.4x", ((@IP_num >> 16) & 0xffff)) + ":" +
                     sprintf("%4.4x", ((@IP_num >> 0) & 0xffff))
      elsif value.instance_of? IPForge::IPv6Address
        @IP_num = value.IP_num
        @IP_string = value.IP_string
      else
        raise BrokenIPv6Address.new("Input to IPv4Address.new() in invalid format, expected either Fixnum or String and got #{value.class}")
      end
    end
    
    ALL_NODES_ADDRESS_NODE_LOCAL = IPv6Address.new("FF01:0:0:0:0:0:0:1")
    ALL_NODES_ADDRESS_LINK_LOCAL = IPv6Address.new("FF02:0:0:0:0:0:0:1")
    
    ALL_ROUTERS_NODE_LOCAL = IPv6Address.new("FF01:0:0:0:0:0:0:2")
    ALL_ROUTERS_LINK_LOCAL = IPv6Address.new("FF02:0:0:0:0:0:0:2")
    ALL_ROUTERS_SITE_LOCAL = IPv6Address.new("FF05:0:0:0:0:0:0:2")
    
    SOLICITED_NODE_ADDRESS_RANGE_BEGIN = IPv6Address.new("FF02:0:0:0:0:1:FF00:0000")
    SOLICITED_NODE_ADDRESS_RANGE_END = IPv6Address.new("FF02:0:0:0:0:1:FFFF:FFFF")
    
    def ==(other)
      return ((other.instance_of? IPv6Address) and (@IP_num == other.IP_num))
    end
    
    def +(other)
      if other.instance_of? Fixnum
        return IPv6Address.new(@IP_num + other)
      elsif other.instance_of? IPv6Address
        return IPv6Address.new(@IP_num + other.IP_num)
      else
        raise "HELL"
      end
    end
    
    def -(other)
      if other.instance_of? Fixnum
        return IPv6Address.new(@IP_num - other)
      elsif other.instance_of? IPv6Address
        return IPv6Address.new(@IP_num - other.IP_num)
      else
        raise "HELL"
      end
    end
    
    def to_s
      return @IP_string
    end
    
    def to_i
      return @IP_num
    end
    
    def to_a
      array = []
      array.push((@IP_num & 338953138925153547590470800371487866880) >> 120)
      array.push((@IP_num & 1324035698926381045275276563951124480) >> 112)
      array.push((@IP_num & 5172014448931175958106549077934080) >> 104)
      array.push((@IP_num & 20203181441137406086353707335680) >> 96)
      array.push((@IP_num & 78918677504442992524819169280) >> 88)
      array.push((@IP_num & 308276084001730439550074880) >> 80)
      array.push((@IP_num & 1204203453131759529492480) >> 72)
      array.push((@IP_num & 4703919738795935662080) >> 64)
      array.push((@IP_num & 18374686479671623680) >> 56)
      array.push((@IP_num & 71776119061217280) >> 48)
      array.push((@IP_num & 280375465082880) >> 40)
      array.push((@IP_num & 1095216660480) >> 32)
      array.push((@IP_num & 4278190080) >> 24)
      array.push((@IP_num & 16711680) >> 16)
      array.push((@IP_num & 65280) >> 8)
      array.push(@IP_num & 255)        
    end
    
    def type_unspecified?
      if @IP_num == 0
        return true
      end
      return false
    end
    
    def type_loopback?
      if @IP_num == 1
        return true
      end
      return false
    end
    
    def type_multicast?
      if ((@IP_num >> 15) && 0xFF) != 0
        return true
      end
      return false
    end
    
    def type_multicast_all_routers?
      if type_multicast_all_routers_node_local? or type_multicast_all_routers_link_local? or 
          type_multicast_all_routers_site_local?
        return true
      end
      return false
    end
    
    def type_multicast_all_routers_node_local?
      if self == ALL_ROUTERS_NODE_LOCAL
        return true
      end
      return false
    end
    
    def type_multicast_all_routers_link_local?
      if self == ALL_ROUTERS_LINK_LOCAL
        return true
      end
      return false
    end
    
    def type_multicast_all_routers_site_local?
      if self == ALL_ROUTERS_SITE_LOCAL
        return true
      end
      return false
    end
    
    def type_multicast_solicited_node?
      if @IP_num >= SOLICITED_NODE_ADDRESS_RANGE_BEGIN and @IP_num <= SOLICITED_NODE_ADDRESS_RANGE_END
        return true
      end
      return false
    end
        
    def type_link_local_unicast?
      if ((@IP_num >> 14) && 0xFE80) != 0
        return true
      end
      return false
    end
    
    def get_solicited_node_multicast
      last_bytes = @IP_num & 0xFFFFFF
      return IPv6Address.new(SOLICITED_NODE_ADDRESS_RANGE_BEGIN.IP_num + last_bytes)
    end
           
        
    def IPv6Address.s_to_i(string)
      arr = nil
      arr = string.split(":")
      if arr.size <= 8
        final_ip = ""
        arr.size.times do |i|
          part = arr[i]
          if part != ""
            if not part =~ /^[a-fA-F0-9]+$/
              raise BrokenIPv6Address.new("String #{value} could not be converted into legal IPv6 addressess - value #{part} malformed!")
            end
            
            if part.size == 4
              final_ip << part + ":"
            else
              (4 - part.size).times do 
                final_ip << "0"
              end
              final_ip << part + ":"
            end
          else
            #We got a ::
            (arr.size - i + 1).times do
              final_ip << "0000:" 
            end 
          end
        end
      else
        raise BrokenIPv6Address.new("String #{value} could not be converted into a legal IPv6 address! Got too many parts separated by :")
      end
      
      if final_ip != /^[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}:[a-fA-F0-9]{4}$/
        raise BrokenIPv6Address.new("Got malformed ip #{final_ip} out of input string #{value} - Internal Error")
      end
      
      parts = final_ip.split(":")
      return (parts[0].hex << 112) + (parts[1].hex << 96) + (parts[2].hex << 80) + (parts[3].hex << 64) +
             (parts[4].hex << 48) + (parts[5].hex << 32) + (parts[6].hex << 16) + (parts[7].hex)
    end
    
    def IPv6Address.i_to_s(integer)
      return ((integer >> 112) & 0xffff).to_s + ":" + 
             ((integer >> 96) & 0xffff).to_s + ":" +
             ((integer >> 80) & 0xffff).to_s + ":" +
             ((integer >> 64) & 0xffff).to_s + ":" +
             ((integer >> 48) & 0xffff).to_s + ":" +
             ((integer >> 32) & 0xffff).to_s + ":" +
             ((integer >> 16) & 0xffff).to_s + ":" +
             ((integer >> 0) & 0xffff).to_s + ":"
    end
    
    def <=>(obj)
      self.to_i <=> obj.to_i
    end
    
    def eql?(obj)
      obj.class == self.class and self.to_i.eql?(obj.to_i)
    end
    
    def hash
      return(((@IP_num & 338953138925153547590470800371487866880) >> 97) +
             ((@IP_num & 78918677504442992524819169280) >> 72) +
             ((@IP_num & 18374686479671623680) >> 48) +
             ((@IP_num & 4278190080) >> 24))
    end
  end
end
