package rrc

import (
	"bufio"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sync"

	log "github.com/sirupsen/logrus"
	"gocv.io/x/gocv"
	"starwiz.cn/sjy01/image-proc/pkg/payload"
	"starwiz.cn/sjy01/image-proc/pkg/utils"
)

// Relative Radiation Correction
// 采用在轨统计定标方法。利用卫星在轨后获取的常规影像数据，统计每个探元出现的灰度频次。

const (
	PANCameraProbeNum = payload.PAN_PIXEL_WIDTH // 全色探元数
	MSSCameraProbeNum = payload.MSS_PIXEL_WIDTH // 多光谱探元数
	MaxGrayLevel      = 65536                   // 16bit像素值
)

type RRC struct {
	LUTOutDir  string
	Histograms [5]ProbeHistogram
}

func NewRRC(dir string) *RRC {
	r := RRC{LUTOutDir: dir}
	r.Histograms[0].init(PANCameraProbeNum)
	for i := 1; i < 5; i++ {
		r.Histograms[i].init(MSSCameraProbeNum)
	}

	os.MkdirAll(r.LUTOutDir, 0755)

	return &r
}

func (rrc *RRC) Statistical(dsPAN, dsMSS string) error {
	rrc.StatisticalPAN(dsPAN)
	rrc.StatisticalMSS(dsMSS)

	return nil
}

func (rrc *RRC) Close() {}

// 基于探元直方图匹配的CBERS01星CCD数据相对辐射校正方法 [潘志强, 2005]
// 统计探元灰度的累积概率密度
func (rrc *RRC) StatisticalPAN(dsfile string) error {
	utils.PrintMemStats()
	f, err := os.Open(dsfile)
	if err != nil {
		return err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	var files []string
	for scanner.Scan() {
		files = append(files, scanner.Text())
	}

	// 并发处理
	var wg sync.WaitGroup
	jobs := make(chan struct{}, 5)

	var mutex sync.Mutex
	for _, file := range files {
		wg.Add(1)
		go func(l0 string) {
			defer wg.Done()

			jobs <- struct{}{}
			defer func() { <-jobs }()

			log.Println("statistical PAN RAW: ", l0)
			data, err := os.ReadFile(l0)
			if err != nil {
				log.Error("Error reading file: ", err)
				return
			}

			height := len(data) / (PANCameraProbeNum * 2)
			img, err := gocv.NewMatFromBytes(height, PANCameraProbeNum, gocv.MatTypeCV16U, data)
			if err != nil {
				log.Error("Error creating Mat from bytes: ", err)
				return
			}

			var hist ProbeHistogram
			hist.init0(PANCameraProbeNum)
			hist.statistical(img)
			img.Close()

			mutex.Lock()

			log.Println("sum PAN histogram...")
			rrc.Histograms[0].sum([]*ProbeHistogram{&hist})
			hist.free()
			runtime.GC()
			utils.PrintMemStats()

			mutex.Unlock()

		}(file)
	}

	wg.Wait()

	log.Println("compute PAN histogram...")
	rrc.Histograms[0].compute()
	log.Println("save PAN gray table matrix.")
	rrc.Histograms[0].saveLUT(filepath.Join(rrc.LUTOutDir, "B0.LUT"))

	return nil
}

func (rrc *RRC) StatisticalMSS(dsfile string) error {
	utils.PrintMemStats()
	f, err := os.Open(dsfile)
	if err != nil {
		return err
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)

	var files []string
	for scanner.Scan() {
		files = append(files, scanner.Text())
	}

	var wg sync.WaitGroup
	jobs := make(chan struct{}, 5)

	var mutex sync.Mutex
	for _, file := range files {
		wg.Add(1)
		go func(l0 string) {
			defer wg.Done()
			jobs <- struct{}{}
			defer func() { <-jobs }()

			log.Println("statistical MSS RAW: ", l0)
			data, err := os.ReadFile(l0)
			if err != nil {
				log.Error("Error reading file: ", err)
				return
			}

			width := MSSCameraProbeNum
			height := len(data) / (width * 4 * 2)
			mssData := make([][]byte, 4)
			for h := 0; h < height; h++ {
				row := data[h*width*4*2 : (h+1)*width*4*2]
				for i := 0; i < 4; i++ {
					mssData[i] = append(mssData[i], row[i*width*2:(i+1)*width*2]...)
				}
			}

			var mssImages [4]gocv.Mat
			for i := 0; i < 4; i++ {
				mssImages[i], err = gocv.NewMatFromBytes(height, width, gocv.MatTypeCV16U, mssData[i])
				if err != nil {
					log.Error("Error creating Mat from bytes: ", err)
					return
				}

				var hist ProbeHistogram
				hist.init0(MSSCameraProbeNum)
				hist.statistical(mssImages[i])
				mssImages[i].Close()

				mutex.Lock()

				log.Println("sum MSS histogram...")
				rrc.Histograms[i+1].sum([]*ProbeHistogram{&hist})
				hist.free()
				runtime.GC()
				utils.PrintMemStats()

				mutex.Unlock()
			}
		}(file)
	}

	wg.Wait()
	for i := 1; i < 5; i++ {
		log.Println("compute MSS histogram...")
		rrc.Histograms[i].compute()
		log.Println("save MSS gray table matrix.")
		rrc.Histograms[i].saveLUT(filepath.Join(rrc.LUTOutDir, fmt.Sprintf("B%d.LUT", i)))
	}

	return nil
}
