// Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cloud .

package iputils

import (
	"bytes"
	"encoding/binary"
	"encoding/hex"
	"math"
	"math/big"
	"net"
	"strconv"
	"sync"
)

type IP struct {
	rawIP  net.IP
	bigInt *big.Int
}

var uint32BigInt = big.NewInt(int64(math.MaxUint32))

func ParseIP(ipString string) IP {
	return NewIP(net.ParseIP(ipString))
}

func NewIP(rawIP net.IP) IP {
	if rawIP == nil {
		return IP{}
	}

	if rawIP.To4() == nil {
		var bigInt = big.NewInt(0)
		bigInt.SetBytes(rawIP.To16())
		bigInt.Add(bigInt, uint32BigInt)
		return IP{
			rawIP:  rawIP,
			bigInt: bigInt,
		}
	}

	return IP{
		rawIP: rawIP,
	}
}

func IsIPv4(ipString string) bool {
	var rawIP = net.ParseIP(ipString)
	return rawIP != nil && rawIP.To4() != nil
}

func IsIPv6(ipString string) bool {
	var rawIP = net.ParseIP(ipString)
	return rawIP != nil && rawIP.To4() == nil && rawIP.To16() != nil
}

func IsSameVersion(ip1 string, ip2 string) bool {
	return IsIPv4(ip1) == IsIPv4(ip2)
}

func IsValid(ipString string) bool {
	return net.ParseIP(ipString) != nil
}

func CompareLong(i1 string, i2 string) int {
	if i1 == "" {
		i1 = "0"
	}
	if i2 == "" {
		i2 = "0"
	}

	var l = len(i1) - len(i2)
	if l > 0 {
		return 1
	}
	if l < 0 {
		return -1
	}

	if i1 > i2 {
		return 1
	}
	if i1 < i2 {
		return -1
	}

	return 0
}

var bigIntPool = &sync.Pool{
	New: func() any {
		return big.NewInt(0)
	},
}

func ToLong(ip string) string {
	var rawIP = net.ParseIP(ip)
	if rawIP == nil {
		return "0"
	}

	var i4 = rawIP.To4()
	if i4 != nil {
		return strconv.FormatUint(uint64(binary.BigEndian.Uint32(i4)), 10)
	}

	var bigInt = bigIntPool.Get().(*big.Int)
	bigInt.SetBytes(rawIP.To16())
	bigInt.Add(bigInt, uint32BigInt)
	var s = bigInt.String()
	bigIntPool.Put(bigInt)
	return s
}

func ToHex(ip string) string {
	if len(ip) == 0 {
		return ""
	}
	var rawIP = net.ParseIP(ip)
	if rawIP == nil {
		return ""
	}

	if rawIP.To4() != nil {
		return hex.EncodeToString(rawIP.To4())
	}

	return hex.EncodeToString(rawIP.To16())
}

func ToBytes(ip string) []byte {
	if len(ip) == 0 {
		return nil
	}
	var rawIP = net.ParseIP(ip)
	if rawIP == nil {
		return nil
	}

	if rawIP.To4() != nil {
		return rawIP.To4()
	}

	return rawIP.To16()
}

func CompareBytes(b1 []byte, b2 []byte) int {
	var l1 = len(b1)
	var l2 = len(b2)
	if l1 < l2 {
		return -1
	}
	if l1 > l2 {
		return 1
	}
	return bytes.Compare(b1, b2)
}

func CompareIP(ip1 string, ip2 string) int {
	return CompareBytes(ToBytes(ip1), ToBytes(ip2))
}

func ToLittleLong(ip string) string {
	var rawIP = net.ParseIP(ip)
	if rawIP == nil {
		return "0"
	}

	var i4 = rawIP.To4()
	if i4 != nil {
		return strconv.FormatUint(uint64(binary.BigEndian.Uint32(i4)), 10)
	}

	var bigInt = bigIntPool.Get().(*big.Int)
	bigInt.SetBytes(rawIP.To16())
	var s = bigInt.String()
	bigIntPool.Put(bigInt)
	return s
}

func (this IP) ToLong() string {
	if this.rawIP == nil {
		return "0"
	}
	if this.bigInt != nil {
		return this.bigInt.String()
	}
	return strconv.FormatUint(uint64(binary.BigEndian.Uint32(this.rawIP.To4())), 10)
}

func (this IP) Mod(d int) int {
	if this.rawIP == nil {
		return 0
	}
	if this.bigInt != nil {
		return int(this.bigInt.Mod(this.bigInt, big.NewInt(int64(d))).Int64())
	}
	return int(binary.BigEndian.Uint32(this.rawIP.To4()) % uint32(d))
}

func (this IP) Compare(anotherIP IP) int {
	if this.rawIP == nil {
		if anotherIP.rawIP == nil {
			return 0
		}
		return -1
	} else if anotherIP.rawIP == nil {
		return 1
	}

	if this.bigInt != nil {
		if anotherIP.bigInt == nil {
			return 1 // IPv6 always greater than IPv4
		}
		return this.bigInt.Cmp(anotherIP.bigInt)
	}

	if anotherIP.bigInt == nil {
		var i1 = binary.BigEndian.Uint32(this.rawIP.To4())
		var i2 = binary.BigEndian.Uint32(anotherIP.rawIP.To4())

		if i1 > i2 {
			return 1
		}
		if i1 < i2 {
			return -1
		}
		return 0
	}

	return -1
}

func (this IP) Between(ipFrom IP, ipTo IP) bool {
	return ipFrom.Compare(this) <= 0 && ipTo.Compare(this) >= 0
}

func (this IP) IsIPv4() bool {
	return this.rawIP != nil && this.bigInt == nil
}

func (this IP) IsIPv6() bool {
	return this.bigInt != nil
}

func (this IP) IsValid() bool {
	return this.rawIP != nil
}

func (this IP) Raw() net.IP {
	return this.rawIP
}

func (this IP) String() string {
	if this.rawIP == nil {
		return ""
	}
	return this.rawIP.String()
}
