package downloader

import (
	"errors"
	"math/big"
	"sync"

	"github.com/ethereum/go-ethereum/common"
)

const (
	workingState = 2
	idleState    = 4
)

type hashFetcherFn func(common.Hash) error
type blockFetcherFn func([]common.Hash) error

// XXX make threadsafe!!!!
type peers map[string]*peer

func (p peers) get(state int) []*peer {
	var peers []*peer
	for _, peer := range p {
		peer.mu.RLock()
		if peer.state == state {
			peers = append(peers, peer)
		}
		peer.mu.RUnlock()
	}

	return peers
}

func (p peers) setState(id string, state int) {
	if peer, exist := p[id]; exist {
		peer.mu.Lock()
		defer peer.mu.Unlock()
		peer.state = state
	}
}

func (p peers) getPeer(id string) *peer {
	return p[id]
}

func (p peers) bestPeer() *peer {
	var peer *peer
	for _, cp := range p {
		if peer == nil || cp.td.Cmp(peer.td) > 0 {
			peer = cp
		}
	}
	return peer
}

// peer represents an active peer
type peer struct {
	state int // Peer state (working, idle)
	rep   int // TODO peer reputation

	mu         sync.RWMutex
	id         string
	td         *big.Int
	recentHash common.Hash

	getHashes hashFetcherFn
	getBlocks blockFetcherFn
}

// create a new peer
func newPeer(id string, td *big.Int, hash common.Hash, getHashes hashFetcherFn, getBlocks blockFetcherFn) *peer {
	return &peer{id: id, td: td, recentHash: hash, getHashes: getHashes, getBlocks: getBlocks, state: idleState}
}

// fetch a chunk using the peer
func (p *peer) fetch(chunk *chunk) error {
	p.mu.Lock()
	defer p.mu.Unlock()

	if p.state == workingState {
		return errors.New("peer already fetching chunk")
	}

	// set working state
	p.state = workingState
	// convert the set to a fetchable slice
	hashes, i := make([]common.Hash, chunk.hashes.Size()), 0
	chunk.hashes.Each(func(v interface{}) bool {
		hashes[i] = v.(common.Hash)
		i++
		return true
	})
	p.getBlocks(hashes)

	return nil
}