package zip

import (
	_ "embed"
	"math"
	"strconv"
	"strings"
)

//go:embed testdata/simplemaps_uszips_basicv1.79/uszips.csv
var csv string

type Zip struct {
	Lat   float64
	Lng   float64
	City  string
	State string
}

var zips map[string]Zip

func init() {
	if len(zips) > 0 {
		return
	}
	zips = map[string]Zip{}
	trim := func(s string) string {
		return strings.Trim(s, `"`)
	}
	for _, line := range strings.Split(csv, "\n")[1:] {
		strs := strings.Split(line, ",")
		if len(strs) < 5 {
			continue
		}
		zip := trim(strs[0])
		lat, _ := strconv.ParseFloat(trim(strs[1]), 32)
		lng, _ := strconv.ParseFloat(trim(strs[2]), 32)
		city := trim(alphafy(strs[3]))
		state := strings.ToUpper(trim(strs[4]))
		zips[zip] = Zip{
			Lat:   lat,
			Lng:   lng,
			City:  city,
			State: state,
		}
	}
}

func alphafy(s string) string {
	bs := make([]byte, 0, len(s)+1)
	for i := range s {
		if (s[i] < 'a' || s[i] > 'z') && (s[i] < 'A' || s[i] > 'Z') {
		} else {
			bs = append(bs, s[i])
		}
	}
	return strings.ToLower(string(bs))
}

func (zip Zip) MilesTo(other Zip) int {
	// c**2 = a**2 + b**2
	// 69.2 mi per lat
	// 60.0 mi per lng
	return int(math.Sqrt(
		math.Pow(69.2*(math.Abs(zip.Lat)-math.Abs(other.Lat)), 2) +
			math.Pow(60.0*(math.Abs(zip.Lng)-math.Abs(other.Lng)), 2),
	))
}

func Get(zip string) Zip {
	if z, ok := zips[zip]; ok {
		return z
	}
	zipAsI, err := strconv.Atoi(strings.Split(zip, "-")[0])
	if err != nil {
		return Zip{}
	}
	for i := 0; i < 5; i++ {
		j := i - 2
		if z2, ok := zips[strconv.Itoa(zipAsI+j)]; ok {
			return z2
		}
	}
	return Zip{}
}

func GetStatesWithin(zip Zip, miles int) []string {
	states := map[string]struct{}{}
	for _, other := range zips {
		if zip.MilesTo(other) <= miles {
			states[other.State] = struct{}{}
		}
	}
	result := []string{}
	for state := range states {
		result = append(result, state)
	}
	return result
}

func FromCityState(city, state string) Zip {
	city = alphafy(city)
	state = strings.ToUpper(state)
	for _, z := range zips {
		if z.City == city && z.State == state {
			return z
		}
	}
	return Zip{}
}
