package plotter

import (
	"errors"
	"fmt"
	"math"
	"math/rand"

	"galaxy/game/internal/bitmap"
)

type Plotter struct {
	factor              float64
	clearFn             func()
	circleFn            func(x, y int, r float64)
	freeCountFn         func() int
	freeNumberToCoordFn func(int) (int, int, error)
}

func NewPlotter(width, height uint32, factor float64) (Plotter, error) {
	return NewBitmapPlotter(NewBitmap(width, height, factor), factor)
}

func NewBitmap(width, height uint32, factor float64) bitmap.Bitmap {
	return bitmap.NewBitmap(AsPlotterSize(width, height, factor))
}

func NewBitmapPlotter(bm bitmap.Bitmap, factor float64) (Plotter, error) {
	if factor > 1 || factor <= 0 {
		return Plotter{}, fmt.Errorf("factor should be: 0 > F <= 1")
	}
	return Plotter{
		factor:              factor,
		clearFn:             bm.Clear,
		circleFn:            func(x, y int, r float64) { bm.Circle(x, y, r, true) },
		freeCountFn:         bm.FreeCount,
		freeNumberToCoordFn: bm.GetFreeN,
	}, nil
}

func (p Plotter) RandomFreePoint(deadZoneRaduis float64) (float64, float64, error) {
	fsCount := p.freeCountFn()
	if fsCount == 0 {
		return 0, 0, errors.New("RandomFreePoint: no free space left")
	}
	next := rand.Intn(fsCount)
	x, y, err := p.freeNumberToCoordFn(next)
	if err != nil {
		return 0, 0, fmt.Errorf("RandomFreePoint: freeNumberToCoordFn: %s", err)
	}
	if deadZoneRaduis > 0 {
		p.plotDeadZone(x, y, deadZoneRaduis)
	}
	planetX := float64(x)*p.factor + rand.Float64()*p.factor
	planetY := float64(y)*p.factor + rand.Float64()*p.factor
	return planetX, planetY, nil
}

func (p Plotter) MarkDeadZone(x, y float64, radius float64) {
	p.plotDeadZone(int(x/p.factor), int(y/p.factor), radius)
}

func (p Plotter) plotDeadZone(x, y int, radius float64) {
	// Adding extra 'pixel' to avoid radius became less than deadZoneRaduis
	// after division by factor due to floating-point operations specifics
	p.circleFn(x, y, (radius+p.factor)/p.factor)
}

func (p Plotter) Clear() { p.clearFn() }

func AsPlotterSize(width, height uint32, factor float64) (uint32, uint32) {
	if factor > 1 || factor <= 0 {
		return width, height
	}
	return uint32(math.Ceil(float64(width) / float64(factor))), uint32(math.Ceil(float64(height) / float64(factor)))
}
