// Copyright 2018 The dexon-consensus Authors
// This file is part of the dexon-consensus library.
//
// The dexon-consensus library is free software: you can redistribute it
// and/or modify it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// The dexon-consensus library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the dexon-consensus library. If not, see
// <http://www.gnu.org/licenses/>.

package core

import (
	"fmt"
	"sync"

	"github.com/dexon-foundation/dexon-consensus/common"
	"github.com/dexon-foundation/dexon-consensus/core/types"
)

type blockConfirmedEvent struct {
	block *types.Block
}

type stronglyAckedEvent struct {
	blockHash common.Hash
}

type totalOrderingDeliveredEvent struct {
	blockHashes common.Hashes
	mode        uint32
}

type blockDeliveredEvent struct {
	blockHash common.Hash
	result    *types.FinalizationResult
}

// nonBlocking implements these interfaces and is a decorator for
// them that makes the methods to be non-blocking.
//  - Application
//  - Debug
//  - It also provides nonblockig for blockdb update.
type nonBlocking struct {
	app          Application
	debug        Debug
	eventChan    chan interface{}
	events       []interface{}
	eventsChange *sync.Cond
	running      sync.WaitGroup
}

func newNonBlocking(app Application, debug Debug) *nonBlocking {
	nonBlockingModule := &nonBlocking{
		app:          app,
		debug:        debug,
		eventChan:    make(chan interface{}, 6),
		events:       make([]interface{}, 0, 100),
		eventsChange: sync.NewCond(&sync.Mutex{}),
	}
	go nonBlockingModule.run()
	return nonBlockingModule
}

func (nb *nonBlocking) addEvent(event interface{}) {
	nb.eventsChange.L.Lock()
	defer nb.eventsChange.L.Unlock()
	nb.events = append(nb.events, event)
	nb.eventsChange.Broadcast()
}

func (nb *nonBlocking) run() {
	// This go routine consume the first event from events and call the
	// corresponding methods of Application/Debug/blockdb.
	for {
		var event interface{}
		func() {
			nb.eventsChange.L.Lock()
			defer nb.eventsChange.L.Unlock()
			for len(nb.events) == 0 {
				nb.eventsChange.Wait()
			}
			event = nb.events[0]
			nb.events = nb.events[1:]
			nb.running.Add(1)
		}()
		switch e := event.(type) {
		case stronglyAckedEvent:
			nb.debug.StronglyAcked(e.blockHash)
		case blockConfirmedEvent:
			nb.app.BlockConfirmed(*e.block)
		case totalOrderingDeliveredEvent:
			nb.debug.TotalOrderingDelivered(e.blockHashes, e.mode)
		case blockDeliveredEvent:
			nb.app.BlockDelivered(e.blockHash, *e.result)
		default:
			fmt.Printf("Unknown event %v.", e)
		}
		nb.running.Done()
		nb.eventsChange.Broadcast()
	}
}

// wait will wait for all event in events finishes.
func (nb *nonBlocking) wait() {
	nb.eventsChange.L.Lock()
	defer nb.eventsChange.L.Unlock()
	for len(nb.events) > 0 {
		nb.eventsChange.Wait()
	}
	nb.running.Wait()
}

// PreparePayload cannot be non-blocking.
func (nb *nonBlocking) PreparePayload(position types.Position) ([]byte, error) {
	return nb.app.PreparePayload(position)
}

// PrepareWitness cannot be non-blocking.
func (nb *nonBlocking) PrepareWitness(height uint64) (types.Witness, error) {
	return nb.app.PrepareWitness(height)
}

// VerifyBlock cannot be non-blocking.
func (nb *nonBlocking) VerifyBlock(block *types.Block) types.BlockVerifyStatus {
	return nb.app.VerifyBlock(block)
}

// BlockConfirmed is called when a block is confirmed and added to lattice.
func (nb *nonBlocking) BlockConfirmed(block types.Block) {
	nb.addEvent(blockConfirmedEvent{&block})
}

// StronglyAcked is called when a block is strongly acked.
func (nb *nonBlocking) StronglyAcked(blockHash common.Hash) {
	if nb.debug != nil {
		nb.addEvent(stronglyAckedEvent{blockHash})
	}
}

// TotalOrderingDelivered is called when the total ordering algorithm deliver
// a set of block.
func (nb *nonBlocking) TotalOrderingDelivered(
	blockHashes common.Hashes, mode uint32) {
	if nb.debug != nil {
		nb.addEvent(totalOrderingDeliveredEvent{blockHashes, mode})
	}
}

// BlockDelivered is called when a block is add to the compaction chain.
func (nb *nonBlocking) BlockDelivered(
	blockHash common.Hash, result types.FinalizationResult) {
	nb.addEvent(blockDeliveredEvent{
		blockHash: blockHash,
		result:    &result,
	})
}
</option>
<option value='branches/2020Q2'>branches/2020Q2</option>
<option value='branches/2020Q3'>branches/2020Q3</option>
<option value='branches/2020Q4'>branches/2020Q4</option>
<option value='branches/2021Q1'>branches/2021Q1</option>
<option value='branches/RELEASE_8_4_0'>branches/RELEASE_8_4_0</option>
<option value='branches/RELENG_2_1_0'>branches/RELENG_2_1_0</option>
<option value='branches/RELENG_2_2'>branches/RELENG_2_2</option>
<option value='branches/RELENG_9_1_0'>branches/RELENG_9_1_0</option>
<option value='branches/RELENG_9_2_0'>branches/RELENG_9_2_0</option>
<option value='dependabot/npm_and_yarn/devel/electron4/files/eslint-utils-1.4.3'>dependabot/npm_and_yarn/devel/electron4/files/eslint-utils-1.4.3</option>
<option value='dependabot/npm_and_yarn/devel/electron4/files/lodash-4.17.15'>dependabot/npm_and_yarn/devel/electron4/files/lodash-4.17.15</option>
<option value='dependabot/npm_and_yarn/devel/electron4/files/lodash.merge-4.6.2'>dependabot/npm_and_yarn/devel/electron4/files/lodash.merge-4.6.2</option>
<option value='dependabot/npm_and_yarn/devel/electron4/files/lodash.template-4.5.0'>dependabot/npm_and_yarn/devel/electron4/files/lodash.template-4.5.0</option>
<option value='dependabot/npm_and_yarn/devel/electron4/files/minimist-1.2.2'>dependabot/npm_and_yarn/devel/electron4/files/minimist-1.2.2</option>
<option value='dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2' selected='selected'>dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2</option>
<option value='main'>main</option>
<option value='master'>master</option>
<option value='svn_head'>svn_head</option>
</select> <input type='submit' value='switch'/></form></td></tr>
<tr><td class='sub'>FreeBSD Ports (https://github.com/freebsd/freebsd-ports)</td><td class='sub right'></td></tr></table>
<table class='tabs'><tr><td>
<a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/about/?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2'>about</a><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2'>summary</a><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/refs/?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2&amp;id=ad97efc883eae561d53ed035d785ab01d970e9bd'>refs</a><a class='active' href='/~lantw44/cgit/cgit.cgi/freebsd-ports/log/mail/emil?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2'>log</a><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/tree/mail/emil?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2&amp;id=ad97efc883eae561d53ed035d785ab01d970e9bd'>tree</a><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/commit/mail/emil?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2&amp;id=ad97efc883eae561d53ed035d785ab01d970e9bd'>commit</a><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/diff/mail/emil?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2&amp;id=ad97efc883eae561d53ed035d785ab01d970e9bd'>diff</a><a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/stats/mail/emil?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2'>stats</a></td><td class='form'><form class='right' method='get' action='/~lantw44/cgit/cgit.cgi/freebsd-ports/log/mail/emil'>
<input type='hidden' name='h' value='dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2'/><input type='hidden' name='id' value='ad97efc883eae561d53ed035d785ab01d970e9bd'/><select name='qt'>
<option value='grep'>log msg</option>
<option value='author'>author</option>
<option value='committer'>committer</option>
<option value='range'>range</option>
</select>
<input class='txt' type='search' size='10' name='q' value=''/>
<input type='submit' value='search'/>
</form>
</td></tr></table>
<div class='path'>path: <a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/log/?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2&amp;id=ad97efc883eae561d53ed035d785ab01d970e9bd'>root</a>/<a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/log/mail?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2&amp;id=ad97efc883eae561d53ed035d785ab01d970e9bd'>mail</a>/<a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/log/mail/emil?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2&amp;id=ad97efc883eae561d53ed035d785ab01d970e9bd'>emil</a></div><div class='content'><table class='list nowrap'><tr class='nohover'><th></th><th class='left'>Commit message (<a href='/~lantw44/cgit/cgit.cgi/freebsd-ports/log/mail/emil?h=dependabot/npm_and_yarn/devel/electron4/files/mixin-deep-1.3.2&amp;id=ad97efc883eae561d53ed035d785ab01d970e9bd&amp;showmsg=1'>Expand</a>)</th><th class='left'>Author</th><th class='left'>Age</th><th class='left'>Files</th><th class='left'>Lines</th></tr>