Indexer

package examples

import (
	"fmt"
	"sync"
	"time"

	"github.com/availproject/avail-go-sdk/primitives"
	"github.com/availproject/avail-go-sdk/sdk"
	SDK "github.com/availproject/avail-go-sdk/sdk"
)

func RunIndexer() {
	sdk, err := SDK.NewSDK(SDK.TuringEndpoint)
	PanicOnError(err)

	indexer := Indexer{sdk: sdk}
	// Initializing indexer with default values
	indexer.Init()
	// Running indexer in the background
	go indexer.Run()

	// Fetching blocks in procedural way
	sub := indexer.Subscribe()
	for i := 0; i < 3; i++ {
		block := sub.Fetch()
		fmt.Println(fmt.Sprintf("Current: Block Height: %v, Block hash: %v", block.height, block.hash))
	}

	// Fetching historical blocks
	sub.Height = sub.Height - 100
	for i := 0; i < 3; i++ {
		block := sub.Fetch()
		fmt.Println(fmt.Sprintf("Historical: Block Height: %v, Block hash: %v", block.height, block.hash))
	}

	// Using Callbacks
	callBack := func(block IndexedBlock) {
		fmt.Println(fmt.Sprintf("Callback: Block Height: %v, Block hash: %v", block.height, block.hash))
	}
	sub2 := indexer.Callback(callBack)
	time.Sleep(25 * time.Second)

	sub2.Shutdown = true
	indexer.Shutdown()

	time.Sleep(3 * time.Second)
	fmt.Println("RunIndexer finished correctly.")
}

type IndexedBlock struct {
	hash   primitives.H256
	height uint32
	block  sdk.Block
}

type Indexer struct {
	sdk      SDK.SDK
	shutdown bool
	block    IndexedBlock
	lock     sync.Mutex
}

func (this *Indexer) Init() {
	hash, err := this.sdk.Client.FinalizedBlockHash()
	PanicOnError(err)

	block, err := SDK.NewBlock(this.sdk.Client, hash)
	PanicOnError(err)

	number, err := this.sdk.Client.BlockNumber(hash)
	PanicOnError(err)

	this.lock.Lock()
	this.block.block = block
	this.block.hash = hash
	this.block.height = number
	this.lock.Unlock()

}

func (this *Indexer) Run() {
	for {
		block, shutdown := this.fetchBlock()
		if shutdown {
			return
		}

		this.lock.Lock()
		this.block.block = block.block
		this.block.hash = block.hash
		this.block.height = block.height
		this.lock.Unlock()
	}
}

func (this *Indexer) fetchBlock() (IndexedBlock, bool) {
	for {
		if this.shutdown {
			return IndexedBlock{}, true
		}

		hash, err := this.sdk.Client.FinalizedBlockHash()
		PanicOnError(err)
		if this.block.hash == hash {
			time.Sleep(15 * time.Second)
			continue
		}

		block, err := SDK.NewBlock(this.sdk.Client, hash)
		PanicOnError(err)

		number, err := this.sdk.Client.BlockNumber(hash)
		PanicOnError(err)

		return IndexedBlock{hash: hash, height: number, block: block}, false
	}
}

func (this *Indexer) GetBlock(blockNumber uint32) IndexedBlock {
	for {
		if this.shutdown {
			return IndexedBlock{}
		}

		this.lock.Lock()
		block := this.block
		this.lock.Unlock()

		if blockNumber > this.block.height {
			time.Sleep(15 * time.Second)
			continue
		}

		if blockNumber == this.block.height {
			return block
		}

		hash, err := this.sdk.Client.BlockHash(blockNumber)
		PanicOnError(err)

		oldBlock, err := SDK.NewBlock(this.sdk.Client, hash)
		PanicOnError(err)

		number, err := this.sdk.Client.BlockNumber(hash)
		PanicOnError(err)

		return IndexedBlock{hash: hash, height: number, block: oldBlock}
	}
}

func (this *Indexer) Shutdown() {
	this.shutdown = true
}

func (this *Indexer) Callback(cb func(IndexedBlock)) *BlockSubscription {
	sub := this.Subscribe()
	go func() {
		for {
			block := sub.Fetch()
			if this.shutdown || sub.Shutdown {
				return
			}

			cb(block)
		}

	}()
	return &sub
}

func (this *Indexer) Subscribe() BlockSubscription {
	return BlockSubscription{Height: this.block.height, indexer: this}
}

type BlockSubscription struct {
	Height   uint32
	indexer  *Indexer
	Shutdown bool
}

func (this *BlockSubscription) Fetch() IndexedBlock {
	if this.Shutdown {
		return IndexedBlock{}
	}

	block := this.indexer.GetBlock(this.Height)
	this.Height += 1

	return block
}