package cloudcover

import (
	"image"
	"image/color"

	"math"
	"math/rand"
	"os"
	"time"

	log "github.com/sirupsen/logrus"
	"gocv.io/x/gocv"
)

const (
	k                   = 2   // 聚类数目，2表示云和非云
	maxIters            = 100 // 最大迭代次数
	tolerance           = 1.0 // 聚类中心变化容忍度
	brightnessThreshold = 220 // 亮度阈值，值越大要求云更亮
)

type Pixel struct {
	r, g, b float64
}

func CloudPercentByKMeans(imagePath string) float64 {
	file, err := os.Open(imagePath)
	if err != nil {
		log.Errorf("无法打开图像: %v", err)
		return 0.0
	}
	defer file.Close()

	img, _, err := image.Decode(file)
	if err != nil {
		log.Errorf("无法解码图像: %v", err)
		return 0.0
	}

	return computeCloudCoverByKMeans(img)
}

func CloudPercentByKMeans2(imgMat gocv.Mat) float64 {
	img, err := imgMat.ToImage()
	if err != nil {
		log.Errorf("无法将Mat转换为Image: %v", err)
		return 0.0
	}
	return computeCloudCoverByKMeans(img)
}

func computeCloudCoverByKMeans(img image.Image) float64 {
	log.Info("cloud cover computing...")
	bounds := img.Bounds()
	width, height := bounds.Max.X, bounds.Max.Y
	pixels := make([]Pixel, 0, width*height)

	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			r, g, b, _ := img.At(x, y).RGBA()
			pixels = append(pixels, Pixel{
				r: float64(r >> 8),
				g: float64(g >> 8),
				b: float64(b >> 8),
			})
		}
	}

	rand.Seed(time.Now().UnixNano())
	centroids := initializeCentroids(pixels)
	assignments := make([]int, len(pixels))

	for iter := 0; iter < maxIters; iter++ {
		for i, pixel := range pixels {
			assignments[i] = nearestCentroid(pixel, centroids)
		}

		newCentroids := updateCentroids(pixels, assignments)
		if hasConverged(centroids, newCentroids) {
			log.Debugf("Convergence after the %d iteration", iter+1)
			break
		}
		centroids = newCentroids
	}

	cloudPixels := 0
	totalPixels := width * height

	// 新增：检查哪个聚类的平均亮度更高，确保白色表示云
	cloudCluster := 0
	if centroids[1].r+centroids[1].g+centroids[1].b > centroids[0].r+centroids[0].g+centroids[0].b {
		cloudCluster = 1
	}

	outputImg := image.NewGray(bounds)
	for y := 0; y < height; y++ {
		for x := 0; x < width; x++ {
			i := y*width + x
			pixel := pixels[i]
			// 计算亮度和颜色比值
			brightness := (pixel.r + pixel.g + pixel.b) / 3
			blueRedRatio := pixel.b / pixel.r
			greenRedRatio := pixel.g / pixel.r

			// 判断是否为云的条件：亮度大于阈值并且蓝色/红色比率较高
			if (assignments[i] == cloudCluster && brightness > brightnessThreshold && blueRedRatio > 0.5) ||
				(assignments[i] == cloudCluster && greenRedRatio > 0.8) {
				outputImg.SetGray(x, y, color.Gray{Y: 255}) // 云区域
				cloudPixels++
			} else {
				outputImg.SetGray(x, y, color.Gray{Y: 0}) // 非云区域
			}
		}
	}

	cloudRatio := float64(cloudPixels) / float64(totalPixels)
	return cloudRatio
}

func initializeCentroids(pixels []Pixel) []Pixel {
	centroids := make([]Pixel, k)
	for i := range centroids {
		centroids[i] = pixels[rand.Intn(len(pixels))]
	}
	return centroids
}

func nearestCentroid(pixel Pixel, centroids []Pixel) int {
	minDist := math.Inf(1)
	bestIndex := 0
	for i, c := range centroids {
		dist := math.Sqrt(math.Pow(pixel.r-c.r, 2) + math.Pow(pixel.g-c.g, 2) + math.Pow(pixel.b-c.b, 2))
		if dist < minDist {
			minDist = dist
			bestIndex = i
		}
	}
	return bestIndex
}

func updateCentroids(pixels []Pixel, assignments []int) []Pixel {
	sums := make([]Pixel, k)
	counts := make([]int, k)

	for i, assignment := range assignments {
		sums[assignment].r += pixels[i].r
		sums[assignment].g += pixels[i].g
		sums[assignment].b += pixels[i].b
		counts[assignment]++
	}

	newCentroids := make([]Pixel, k)
	for i := range newCentroids {
		if counts[i] > 0 {
			newCentroids[i].r = sums[i].r / float64(counts[i])
			newCentroids[i].g = sums[i].g / float64(counts[i])
			newCentroids[i].b = sums[i].b / float64(counts[i])
		} else {
			newCentroids[i] = sums[i]
		}
	}

	return newCentroids
}

func hasConverged(oldCentroids, newCentroids []Pixel) bool {
	for i := range oldCentroids {
		if math.Abs(oldCentroids[i].r-newCentroids[i].r) > tolerance ||
			math.Abs(oldCentroids[i].g-newCentroids[i].g) > tolerance ||
			math.Abs(oldCentroids[i].b-newCentroids[i].b) > tolerance {
			return false
		}
	}
	return true
}
