// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .

package values

import (
	"bytes"
	"net"
	"strings"
)

type IPRangeType = string

const (
	IPRangeTypeCIDR    IPRangeType = "cidr"     // CIDR
	IPRangeTypeSingeIP IPRangeType = "singleIP" // 单个IP
	IPRangeTypeRange   IPRangeType = "range"    // IP范围，IP1-IP2
)

type IPRange struct {
	Type   IPRangeType
	CIDR   *net.IPNet
	IPFrom net.IP
	IPTo   net.IP
}

func (this *IPRange) Contains(netIP net.IP) bool {
	if netIP == nil {
		return false
	}
	switch this.Type {
	case IPRangeTypeCIDR:
		if this.CIDR != nil {
			return this.CIDR.Contains(netIP)
		}
	case IPRangeTypeSingeIP:
		if this.IPFrom != nil {
			return bytes.Equal(this.IPFrom, netIP)
		}
	case IPRangeTypeRange:
		return bytes.Compare(this.IPFrom, netIP) <= 0 && bytes.Compare(this.IPTo, netIP) >= 0
	}
	return false
}

type IPRangeList struct {
	Ranges []*IPRange
}

func NewIPRangeList() *IPRangeList {
	return &IPRangeList{}
}

func ParseIPRangeList(value string) *IPRangeList {
	var list = NewIPRangeList()

	if len(value) == 0 {
		return list
	}

	var lines = strings.Split(value, "\n")
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if len(line) == 0 {
			continue
		}

		if strings.Contains(line, ",") { // IPFrom,IPTo
			var pieces = strings.SplitN(line, ",", 2)
			if len(pieces) == 2 {
				var ipFrom = net.ParseIP(strings.TrimSpace(pieces[0]))
				var ipTo = net.ParseIP(strings.TrimSpace(pieces[1]))
				if ipFrom != nil && ipTo != nil {
					if bytes.Compare(ipFrom, ipTo) > 0 {
						ipFrom, ipTo = ipTo, ipFrom
					}
					list.Ranges = append(list.Ranges, &IPRange{
						Type:   IPRangeTypeRange,
						IPFrom: ipFrom,
						IPTo:   ipTo,
					})
				}
			}
		} else if strings.Contains(line, "-") { // IPFrom-IPTo
			var pieces = strings.SplitN(line, "-", 2)
			if len(pieces) == 2 {
				var ipFrom = net.ParseIP(strings.TrimSpace(pieces[0]))
				var ipTo = net.ParseIP(strings.TrimSpace(pieces[1]))
				if ipFrom != nil && ipTo != nil {
					if bytes.Compare(ipFrom, ipTo) > 0 {
						ipFrom, ipTo = ipTo, ipFrom
					}
					list.Ranges = append(list.Ranges, &IPRange{
						Type:   IPRangeTypeRange,
						IPFrom: ipFrom,
						IPTo:   ipTo,
					})
				}
			}
		} else if strings.Contains(line, "/") { // CIDR
			_, cidr, _ := net.ParseCIDR(line)
			if cidr != nil {
				list.Ranges = append(list.Ranges, &IPRange{
					Type: IPRangeTypeCIDR,
					CIDR: cidr,
				})
			}
		} else { // single ip
			var netIP = net.ParseIP(line)
			if netIP != nil {
				list.Ranges = append(list.Ranges, &IPRange{
					Type:   IPRangeTypeSingeIP,
					IPFrom: netIP,
				})
			}
		}
	}

	return list
}

func (this *IPRangeList) Contains(ip string) bool {
	var netIP = net.ParseIP(ip)
	if netIP == nil {
		return false
	}
	for _, r := range this.Ranges {
		if r.Contains(netIP) {
			return true
		}
	}
	return false
}
