package main

import (
	"context"
	"fmt"
	"image/color"
	"log"
	"net/http"
	"sync"
	"time"

	"git.dvdt.dev/david/bitcoin-ticker-pi/display"
	"git.dvdt.dev/david/bitcoin-ticker-pi/fonts"
	rgbmatrix "git.dvdt.dev/david/bitcoin-ticker-pi/go-rpi-rgb-led-matrix"
	"git.dvdt.dev/david/bitcoin-ticker-pi/mempool"
)

const (
	RefreshRateHz = 1
	MempoolURL    = "mempool.space"
)

func main() {
	cfg := rgbmatrix.DefaultConfig
	cfg.Rows = 32
	cfg.Cols = 64

	m, _ := rgbmatrix.NewRGBLedMatrix(&cfg)
	c := rgbmatrix.NewCanvas(m)

	d := display.New(c,
		display.NewSlot(9, 5, 4, 0, 0),
		display.NewSlot(5, 5, 4, 44, 0),
		display.NewSlot(7, 13, 8, 5, 9),
		display.NewSlot(12, 5, 4, 7, 27),
	)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	ticker := time.NewTicker(time.Second / RefreshRateHz)
	tdone := make(chan struct{})
	mdone := make(chan struct{})
	msgs := make(chan mempool.Message)
	errs := make(chan error)

	mp := mempool.New(MempoolURL, &http.Client{
		Timeout: 5 * time.Second,
	})
	err := mp.Init()
	if err != nil {
		log.Fatal(err)
	}

	currentBlockHeight, err := mp.CurrentBlockHeight()
	if err != nil {
		log.Fatal(err)
	}

	highfee, medfee, lowfee, err := mp.CurrentFees()
	if err != nil {
		log.Fatal(err)
	}

	currentPrice, err := mp.CurrentPrice()
	if err != nil {
		log.Fatal(err)
	}

	var blockHeight int
	blockHeightMutex := &sync.Mutex{}
	var fees mempool.Fees
	feesMutex := &sync.Mutex{}
	var price int
	priceMutex := &sync.Mutex{}

	blockHeight = currentBlockHeight
	fees.FastestFee = highfee
	fees.HalfHourFee = medfee
	fees.HourFee = lowfee
	price = currentPrice

	go mp.Listen(ctx, msgs, errs, mdone)

	go func() {
		for {
			select {
			case m := <-msgs:
				fmt.Printf("%+v\n", m)
				if m.Fees != nil {
					feesMutex.Lock()
					fees = *m.Fees
					feesMutex.Unlock()
				}
				if m.Block != nil {
					blockHeightMutex.Lock()
					blockHeight = int(m.Block.Height)
					blockHeightMutex.Unlock()
				}
				if m.Conversions != nil {
					priceMutex.Lock()
					price = m.Conversions.USD
					priceMutex.Unlock()
				}
			}
		}
	}()

	for {
		select {
		case <-tdone:
			return
		case <-ticker.C:
			d.Clear()
			err = updateSlots(d, price, blockHeight, fees.HourFee, fees.HalfHourFee, fees.FastestFee)
			if err != nil {
				log.Fatal(err)
			}
		}
	}
}

func updateSlots(d *display.Display, price, height, lfee, mfee, hfee int) error {
	d.Clear()

	var priceSegments []display.Segment
	priceSegments = append(priceSegments, display.Segment{Text: "dollar", Color: fonts.Green})
	priceSegments = append(priceSegments, strToDisplaySegments(fmt.Sprintf("%d", price), fonts.White)...)

	err := d.UpdateTopLeft(priceSegments)
	if err != nil {
		return err
	}

	var moscowTimeSegments []display.Segment
	moscowTime := 0
	if price > 0 {
		moscowTime = 100000000 / price
	}
	moscowTimeSegments = append(moscowTimeSegments, display.Segment{Text: "satoshi", Color: fonts.Orange})
	moscowTimeSegments = append(moscowTimeSegments, strToDisplaySegments(fmt.Sprintf("%d", moscowTime), fonts.White)...)

	err = d.UpdateTopRight(moscowTimeSegments)
	if err != nil {
		return err
	}

	var heightSegments []display.Segment
	heightSegments = append(heightSegments, display.Segment{Text: "height", Color: fonts.Blue})
	heightSegments = append(heightSegments, strToDisplaySegments(fmt.Sprintf("%d", height), fonts.White)...)

	err = d.UpdateMain(heightSegments)
	if err != nil {
		return err
	}

	var feeSegments []display.Segment
	feeSegments = append(feeSegments, display.Segment{Text: "low", Color: fonts.Green})
	feeSegments = append(feeSegments, strToDisplaySegments(fmt.Sprintf("%d", lfee), fonts.White)...)
	feeSegments = append(feeSegments, display.Segment{Text: "medium", Color: fonts.Orange})
	feeSegments = append(feeSegments, strToDisplaySegments(fmt.Sprintf("%d", mfee), fonts.White)...)
	feeSegments = append(feeSegments, display.Segment{Text: "high", Color: fonts.Red})
	feeSegments = append(feeSegments, strToDisplaySegments(fmt.Sprintf("%d", hfee), fonts.White)...)

	err = d.UpdateBottom(feeSegments)
	if err != nil {
		return err
	}

	return d.Render()
}

func strToDisplaySegments(s string, c color.RGBA) []display.Segment {
	var segs []display.Segment
	for _, r := range s {
		segs = append(segs, display.Segment{Text: string(r), Color: c})
	}
	return segs
}
