// Copyright 2020 Pilosa Corp.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package pilosa

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
	"sync"

	"github.com/pilosa/pilosa/v2/roaring"
	txkey "github.com/pilosa/pilosa/v2/short_txkey"
	//txkey "github.com/pilosa/pilosa/v2/txkey"
)

// blueGreenTx runs two Tx together and notices differences in their output.
// By convention, the 'b' Tx is the output that is returned to caller.
//
// Warning: DATA RACES are expected if RoaringTx is one side of the Tx pair.
// The checkDatabase() call will do reads of the fragments at Commit/Rollback,
// while the snapshotqueue may be doing writes.
//
// Do not run with go test -race and expect it to be race free with RoaringTx
// on one arm.
//
// Note: using the dbshard.go DBShard.mut RWMutex to begin and end
// both the A and B transactions atomically, we support a single importer and
// lots of readers running under blue-green transactions. Two writers a.k.a. two
// github ingests at once will deadlock eventually, but I think that may be asking
// for more than we want to test under blue-green, as it would require a bunch of
// test-only internal executor logic that could mess with the production path.
// So, for now, a limitation on blue green tests is that they be single
// writer/single importer going at once.
//
type blueGreenTx struct {
	a Tx
	b Tx // b's output is returned

	o  Txo
	as string
	bs string

	types      []txtype
	hasRoaring bool

	// roaring will not create as many Tx (they are
	// psuedo Tx anyway), espcially when deleting
	// files. Return the non-roaring Sn if
	// possible, by referencing useSnA.
	useSnA bool

	idx *Index

	checker              blueGreenChecker
	mu                   sync.Mutex
	rollbackOrCommitDone bool

	txf *TxFactory

	short bool // short Dump or long

	FullDump bool // else quieter, don't attemp Dump() if false.
}

// blueGreenRegistry is used to force checking of (read) transactions
// before writes happen, if roaring is on one of the A/B branches.
// Because roaring won't have an MVCC view of the world. Writes to
// roaring will show up, while writes to the DB won't show up on
// readTx that have already started.
type blueGreenRegistry struct {
	mu         sync.Mutex
	m          map[int64]*blueGreenTx
	types      []txtype
	hasRoaring bool
}

// if we have raoring in the mix we cannot expect reads
// to match up, but otherwise do.
func newBlueGreenReg(types []txtype) *blueGreenRegistry {

	hasRoaring := false
	if types[0] == roaringTxn || types[1] == roaringTxn {
		hasRoaring = true
	}
	return &blueGreenRegistry{
		m:          make(map[int64]*blueGreenTx),
		types:      types,
		hasRoaring: hasRoaring,
	}
}

// add remembers the tx so we can check that
// all tx were finished before Close().
func (b *blueGreenRegistry) add(c *blueGreenTx) {
	b.mu.Lock()
	defer b.mu.Unlock()
	if c.useSnA {
		b.m[c.a.Sn()] = c
	} else {
		b.m[c.b.Sn()] = c
	}
}

func (b *blueGreenRegistry) finishedTx(tx *blueGreenTx) {
	b.mu.Lock()
	defer b.mu.Unlock()
	sn := tx.Sn()
	delete(b.m, sn)
	//vv("blueGreenRegistry deleted _sn_ %v", sn)

	// Note that a tx.o.dbs.Cleanup(tx) call should not be needed,
	// because the individual tx will call cleanup themselves.
}

func (b *blueGreenRegistry) Close() {
	b.mu.Lock()
	defer b.mu.Unlock()
	if len(b.m) > 0 {
		panic(fmt.Sprintf("still have open/unchecked blueGreenTx: '%#v'", b.m))
		//AlwaysPrintf("still have unchecked blueGreenTx: '%#v'", b.m)
	}
}

func (txf *TxFactory) newBlueGreenTx(a, b Tx, idx *Index, o Txo) *blueGreenTx {
	as := a.Type()
	bs := b.Type()
	c := &blueGreenTx{a: a,
		b:          b,
		idx:        idx,
		as:         as,
		bs:         bs,
		txf:        txf,
		types:      txf.types,
		hasRoaring: txf.blueGreenReg.hasRoaring,
		short:      true,
	}

	if c.types[1] == roaringTxn {
		c.useSnA = true
	}
	//vv("newBlueGreenTx with a.sn=%v with o.Shard=%v", c.Sn(), int(o.Shard))

	c.checker.c = c
	c.o = o
	txf.blueGreenReg.add(c)
	return c
}

var _ Tx = (*blueGreenTx)(nil)

func (c *blueGreenTx) Type() string {
	return c.a.Type() + "_" + c.b.Type()
}

var blueGreenTxDumpMut sync.Mutex

func (c *blueGreenTx) Dump(short bool, shard uint64) {

	if !c.FullDump {
		return
	}

	blueGreenTxDumpMut.Lock()
	defer blueGreenTxDumpMut.Unlock()
	fmt.Printf("%v blueGreenTx.Dump ============== \n", FileLine(2))
	fmt.Printf("A(%v) Dump:\n", c.as)
	c.a.Dump(short, shard)
	fmt.Printf("B(%v) Dump:\n", c.bs)
	c.b.Dump(short, shard)

	if !short {
		fmt.Printf("dbPerShard.DumpAll(): idx=%p\n", c.idx)
		c.idx.holder.txf.dbPerShard.DumpAll()
	}
}

func (c *blueGreenTx) Readonly() bool {
	a := c.a.Readonly()
	b := c.b.Readonly()
	if a != b {
		panic(fmt.Sprintf("Readonly difference, a=%v, but b =%v", a, b))
	}
	return b
}

// for now we just return B's list, since this is involved in
// holder Open which can happen before any blue-green is done;
// in fact this is instrumental in setting up the sync from
// green to blue.
func (c *blueGreenTx) GetSortedFieldViewList(idx *Index, shard uint64) (fvB []txkey.FieldView, errB error) {
	fvB, errB = c.b.GetSortedFieldViewList(idx, shard)
	return
}

func (c *blueGreenTx) NewTxIterator(index, field, view string, shard uint64) *roaring.Iterator {
	c.checker.see(index, field, view, shard)
	// can't really do simultaneous iteration on A and B, so punt and
	// just give back B.
	return c.b.NewTxIterator(index, field, view, shard)
}

func (c *blueGreenTx) Pointer() string {
	return fmt.Sprintf("%p", c)
}

func (c *blueGreenTx) IncrementOpN(index, field, view string, shard uint64, changedN int) {
	c.checker.see(index, field, view, shard)
	c.a.IncrementOpN(index, field, view, shard, changedN)
	c.b.IncrementOpN(index, field, view, shard, changedN)
}

// compareTxState is called for the first Commit or Rollback a blueGreenTx sees.
func (c *blueGreenTx) compareTxState(index, field, view string, shard uint64) {
	if c.o.blueGreenOff {
		return
	}
	here := fmt.Sprintf("%v/%v/%v/%v", index, field, view, shard)
	//vv("compareTxState here = '%v', _sn_ %v gid=%v", here, c.Sn(), curGID())
	aIter, aFound, aErr := c.a.ContainerIterator(index, field, view, shard, 0)
	bIter, bFound, bErr := c.b.ContainerIterator(index, field, view, shard, 0)
	if aErr == nil || aIter != nil {
		defer aIter.Close()
	}
	if bErr == nil || bIter != nil {
		defer bIter.Close()
	}

	if aFound != bFound {
		c.Dump(c.short, shard)
		panic(fmt.Sprintf("compareTxState[%v]: A(%v) ContainerIterator had aFound=%v, but B(%v) had bFound=%v; at '%v'", here, c.as, aFound, c.bs, bFound, stack()))
	}

	if aErr != nil || bErr != nil {
		if aErr != nil && bErr != nil {
			c.Dump(c.short, shard)
			panic(fmt.Sprintf("compareTxState[%v]: A(%v) reported err '%v'; B(%v) reported err '%v' at %v", here, c.as, aErr, c.bs, bErr, stack()))
		}
		if aErr != nil {
			c.Dump(c.short, shard)
			panic(fmt.Sprintf("compareTxState[%v]: A(%v) reported err %v at %v; but B(%v) did not", here, c.as, aErr, c.bs, stack()))
		}
		if bErr != nil {
			c.Dump(c.short, shard)
			panic(fmt.Sprintf("compareTxState[%v]: B(%v) reported err %v at %v; but A(%v) did not", here, c.bs, bErr, c.as, stack()))
		}
	}
	for aIter.Next() {
		aKey, aValue := aIter.Value()

		if !bIter.Next() {
			AlwaysPrintf("compareTxState[%v]: A(%v) found key %v, B(%v) didn't, dump to follow, stack=\n %v\n\n and here is dump:", here, c.as, aKey, c.bs, stack())
			c.Dump(c.short, shard)
			panic(fmt.Sprintf("compareTxState[%v]: A(%v) found key %v, B(%v) didn't, at %v", here, c.as, aKey, c.bs, stack()))
		}
		bKey, bValue := bIter.Value()
		if bKey != aKey {
			AlwaysPrintf("problem in caller %v", Caller(2))
			c.Dump(c.short, shard)
			panic(fmt.Sprintf("compareTxState[%v]: A(%v) found key %v, B(%v) found %v, at %v", here, c.as, aKey, c.bs, bKey, stack()))
		}
		if err := aValue.BitwiseCompare(bValue); err != nil {
			c.Dump(c.short, shard)
			//vv("compareTxState[%v]: key %v differs: %v;  A=%v; B=%v; at stack=%v", here, aKey, err, c.as, c.bs, stack())
			panic(fmt.Sprintf("compareTxState[%v]: key %v differs: %v;  A=%v; B=%v; at stack=%v", here, aKey, err, c.as, c.bs, stack()))
		}
		//vv("successfully matched aKey(%v)='%v' and bKey(%v)='%v'", c.as, aKey, c.bs, bKey)
	}
	// end checking everything in A, but does B have more?
	if bIter.Next() {
		AlwaysPrintf("bIter has more than it should. problem in caller %v. _sn_ %v", Caller(2), c.Sn())
		c.Dump(c.short, shard)
		bKey, _ := bIter.Value()
		panic(fmt.Sprintf("compareTxState[%v]: B(%v) found key %v, A(%v) didn't, (a.sn=%v) (b.sn=%v) at %v", here, c.bs, bKey, c.as, c.a.Sn(), c.b.Sn(), stack()))
	}
	//vv("done without problem. compareTxState here = '%v', _sn_ %v gid=%v", here, c.Sn(), curGID())
}

func (c *blueGreenTx) checkDatabase() {
	if c.o.blueGreenOff {
		return
	}
	if c.hasRoaring && !c.o.Write {
		// With roaring on one arm, we only check the we are A/B
		// consistent after every write.
		//
		// Ideally reads can only see that consitent state, and don't need
		// to be checked themselves-- but we do try if both A and B
		// are transactional. Sketch of proof by induction that
		// write checking should, theoretically, suffice:
		// Starting with zero data, if we have agreement in both A/B
		// database state after each write, then
		// because there is only ever a single
		// writer (for LMDB/RBF), we should always have the same
		// data state between A and B as long as every prior
		// A/B check of the serialized writes suceeded.
		//
		// This avoids a key problem we discovered when A/B checking reads
		// with roaring on one arm.
		// The MVCC of the transactional engines means that reads that
		// start before a write commit will look very different
		// when comparing to roaring's non-transactional state.
		return
	}

	c.checker.mu.Lock()
	defer c.checker.mu.Unlock()
	if c.checker.checkDone {
		return // idemopotent. checkDatabase can be called twice. Only the first does the checks.
	}
	c.checker.checkDone = true

	for index, fields := range c.checker.seen() {
		for field, views := range fields {
			for view, shards := range views {
				for shard := range shards {
					c.compareTxState(index, field, view, shard)
				}
			}
		}
	}
}

func (c *blueGreenTx) IsDone() bool {
	return c.b.IsDone()
}

func (c *blueGreenTx) Rollback() {
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.rollbackOrCommitDone {
		return // avoid using discarded tx for Dump, which will panic.
	}
	c.rollbackOrCommitDone = true

	if c.o.Write {
		c.checkDatabase()
	}
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Rollback() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	//vv("blueGreenTx.Rollback() about to call (%v) a.Rollback()", c.as)
	c.a.Rollback()
	//vv("blueGreenTx.Rollback() about to call (%v) b.Rollback()", c.bs)
	c.b.Rollback()
	//vv("blueGreenTx.Rollback() done. bgtx p=%p", c)

	c.txf.blueGreenReg.finishedTx(c)
}

func (c *blueGreenTx) Commit() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.rollbackOrCommitDone {
		return nil
	}
	//vv("blueGreenTx.Commit() called. bgtx p=%p", c)
	c.rollbackOrCommitDone = true
	if c.o.Write {
		if !c.o.blueGreenOff {
			c.checkDatabase()
		}
	}
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Commit() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	errA := c.a.Commit()
	_ = errA
	errB := c.b.Commit()

	compareErrors(errA, errB)
	c.txf.blueGreenReg.finishedTx(c)
	return errB
}

func (c *blueGreenTx) RoaringBitmap(index, field, view string, shard uint64) (*roaring.Bitmap, error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see RoaringBitmap() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	a, errA := c.a.RoaringBitmap(index, field, view, shard)
	_, _ = a, errA
	b, errB := c.b.RoaringBitmap(index, field, view, shard)
	if !c.o.blueGreenOff {
		compareErrors(errA, errB)

		slcA := a.Slice()
		slcB := b.Slice()
		if !reflect.DeepEqual(slcA, slcB) {
			panic("blueGreenTx.RoaringBitmap() returning different roaring.Bitmaps!")
		}
	}
	return b, errB
}

func (c *blueGreenTx) Container(index, field, view string, shard uint64, key uint64) (ct *roaring.Container, err error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Container() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	a, errA := c.a.Container(index, field, view, shard, key)
	b, errB := c.b.Container(index, field, view, shard, key)

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
		err = a.BitwiseCompare(b)
		panicOn(err)
	}
	return b, errB
}

func (c *blueGreenTx) PutContainer(index, field, view string, shard uint64, key uint64, rc *roaring.Container) error {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see PutContainer() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	errA := c.a.PutContainer(index, field, view, shard, key, rc)
	errB := c.b.PutContainer(index, field, view, shard, key, rc)

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}
	return errB
}

func (c *blueGreenTx) ImportRoaringBits(index, field, view string, shard uint64, rit roaring.RoaringIterator, clear bool, log bool, rowSize uint64, data []byte) (changed int, rowSet map[uint64]int, err error) {
	c.checker.see(index, field, view, shard)

	// these are the first port of call for debugging, so we leave them in.
	// ================== begin save comments.
	//c.checkDatabase()
	////vv("got past database check at TOP of ImportRoaringBits")
	//c.Dump(c.short, shard)
	////vv("done with top dump; clear=%v", clear)
	// ==================   end save comments.
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see ImportRoaringBits() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()

	// remember where the iterator started, so we can replay it a second time.
	rit2 := rit.Clone()
	panicOn(err)

	changedA, rowSetA, errA := c.a.ImportRoaringBits(index, field, view, shard, rit, clear, log, rowSize, data)
	changedB, rowSetB, errB := c.b.ImportRoaringBits(index, field, view, shard, rit2, clear, log, rowSize, data)

	if !c.o.blueGreenOff {

		if len(data) == 0 {
			// okay to check! otherwise we are in the fragment.fillFragmentFromArchive
			// case where we know that RoaringTx.ImportRoaringBits changed and rowSet will
			// be inaccurate.
			if changedA != changedB {
				panic(fmt.Sprintf("changedA = %v, but changedB = %v", changedA, changedB))
			}
			if len(rowSetA) != len(rowSetB) {
				panic(fmt.Sprintf("rowSetA = %#v, but rowSetB = %#v", rowSetA, rowSetB))
			}
			for k, va := range rowSetA {
				vb, ok := rowSetB[k]
				if !ok {
					panic(fmt.Sprintf("diff on key '%v': present in rowSetA, but not in rowSet B. rowSetA = %#v, but rowSetB = %#v", k, rowSetA, rowSetB))
				}
				if va != vb {
					panic(fmt.Sprintf("diff on key '%v', rowSetA has value '%v', but rowSetB has value '%v'", k, va, vb))
				}
			}
		}
		compareErrors(errA, errB)
		c.checkDatabase()
	}
	return changedB, rowSetB, errB
}

func (c *blueGreenTx) RemoveContainer(index, field, view string, shard uint64, key uint64) error {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see RemoveContainer() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	errA := c.a.RemoveContainer(index, field, view, shard, key)
	errB := c.b.RemoveContainer(index, field, view, shard, key)

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}
	return errB
}

func (c *blueGreenTx) UseRowCache() bool {
	// avoid cross-talk between our two implementations
	// by never allowing either to use the row cache.
	return false
}

var _ = (&blueGreenTx{}).isIn // happy linter

func (c *blueGreenTx) isIn(index, field, view string, shard uint64, ckey uint64) (r []bool) {
	r = make([]bool, 2)
	inA, errA := c.a.Contains(index, field, view, shard, ckey)
	panicOn(errA)
	inB, errB := c.b.Contains(index, field, view, shard, ckey)
	panicOn(errB)
	r[0] = inA
	r[1] = inB
	return
}

func (c *blueGreenTx) Add(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) {
	c.checker.see(index, field, view, shard)
	//vv("blueGreenTx) Add(index=%v, field=%v, view=%v, shard=%v", index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Add() panic '%v' for index='%v', field='%v', view='%v', shard='%v' at '%v'", r, index, field, view, shard, stack())
			panic(r)
		}
	}()

	// must copy a before calling Add(), since RoaringTx.Add() uses roaring.DirectAddN()
	// which modifies the input array a.
	a2 := make([]uint64, len(a))
	copy(a2, a)

	ach, errA := c.a.Add(index, field, view, shard, a...)
	_, _ = ach, errA

	bch, errB := c.b.Add(index, field, view, shard, a2...)

	if !c.o.blueGreenOff {

		if ach != bch {
			panic(fmt.Sprintf("Add() difference, ach=%v, but bch=%v; errA='%v'; errB='%v'", ach, bch, errA, errB))
		}
		compareErrors(errA, errB)
	}

	return bch, errB
}

func compareErrors(errA, errB error) {
	switch {
	case errA == nil && errB == nil:
		// OK
	case errA == nil:
		panic(fmt.Sprintf("errA is nil, but errB = %#v", errB))
	case errB == nil:
		panic(fmt.Sprintf("errB is nil, but errA = %#v", errA))
	default:
		ae := errA.Error()
		be := errB.Error()
		if ae != be {
			panic(fmt.Sprintf("errA is '%v', but errB is '%v'", ae, be))
		}
	}
}

func (c *blueGreenTx) Remove(index, field, view string, shard uint64, a ...uint64) (changeCount int, err error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Remove() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	ach, errA := c.a.Remove(index, field, view, shard, a...)
	_, _ = ach, errA
	bch, errB := c.b.Remove(index, field, view, shard, a...)

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}
	return bch, errB
}

func (c *blueGreenTx) Contains(index, field, view string, shard uint64, key uint64) (exists bool, err error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Contains() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	ax, errA := c.a.Contains(index, field, view, shard, key)
	_, _ = ax, errA
	bx, errB := c.b.Contains(index, field, view, shard, key)

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}
	return bx, errB
}

func (c *blueGreenTx) ContainerIterator(index, field, view string, shard uint64, firstRoaringContainerKey uint64) (citer roaring.ContainerIterator, found bool, err error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see ContainerIterator() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()

	ait, afound, errA := c.a.ContainerIterator(index, field, view, shard, firstRoaringContainerKey)
	_, _, _ = ait, afound, errA

	bit, bfound, errB := c.b.ContainerIterator(index, field, view, shard, firstRoaringContainerKey)

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}

	if errB != nil {
		// RoaringTx can return an iterator and an error, so be sure Close it we have it.
		if ait != nil {
			ait.Close()
		}
		if bit != nil {
			bit.Close()
		}
		return nil, bfound, errB
	}
	if errA != nil {
		// RoaringTx can return an iterator and an error, so be sure Close it we have it.
		if ait != nil {
			ait.Close()
		}
	}

	// INVAR: errA == errB == nil
	bgi := NewBlueGreenIterator(c, ait, bit)
	return bgi, bfound, errB
}

func (tx *blueGreenTx) GetFieldSizeBytes(index, field string) (uint64, error) {
	return 0, nil
}

func NewBlueGreenIterator(tx *blueGreenTx, ait, bit roaring.ContainerIterator) *blueGreenIterator {
	return &blueGreenIterator{
		tx:  tx,
		as:  tx.as,
		bs:  tx.bs,
		ait: ait,
		bit: bit,
	}
}

type blueGreenIterator struct {
	tx *blueGreenTx
	as string
	bs string

	ait roaring.ContainerIterator
	bit roaring.ContainerIterator
}

func (bgi *blueGreenIterator) Next() bool {
	na := bgi.ait.Next()
	nb := bgi.bit.Next()
	if na != nb {
		panic(fmt.Sprintf("na=%v(%v) != nb(%v)=%v", na, bgi.as, bgi.bs, nb))
	}
	return nb
}

func (bgi *blueGreenIterator) Value() (uint64, *roaring.Container) {
	ka, ca := bgi.ait.Value()
	kb, cb := bgi.bit.Value()

	if !bgi.tx.o.blueGreenOff {
		if ka != kb {
			panic(fmt.Sprintf("ka=%v != kb=%v", ka, kb))
		}
		err := ca.BitwiseCompare(cb)
		panicOn(err)
	}
	return kb, cb
}
func (bgi *blueGreenIterator) Close() {
	bgi.ait.Close()
	bgi.bit.Close()
}

// ForEach is read-only on the database, and so we only pass through to B.
// Avoids the side-effects of calling fn too many times, which can cause serious false alarms.
func (c *blueGreenTx) ForEach(index, field, view string, shard uint64, fn func(i uint64) error) error {
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see ForEach() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	return c.b.ForEach(index, field, view, shard, fn)

}

// ForEachRange cannot change the database, and we also can't control
// the side effects of the fn() calls. So we only pass through to B, not A.
// No checker.see() is needed as well, because we are read-only.
func (c *blueGreenTx) ForEachRange(index, field, view string, shard uint64, start, end uint64, fn func(uint64) error) error {

	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see ForEachRange() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()

	// calling fn will have side effects; can only call it the right number of times.
	// so can't do this.
	//	errA := c.a.ForEachRange(index, field, view, shard, start, end, fn)
	return c.b.ForEachRange(index, field, view, shard, start, end, fn)
}

func (c *blueGreenTx) Count(index, field, view string, shard uint64) (uint64, error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Count() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	a, errA := c.a.Count(index, field, view, shard)
	_, _ = a, errA
	b, errB := c.b.Count(index, field, view, shard)
	_, _ = b, errB

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}
	return b, errB
}

func (c *blueGreenTx) Max(index, field, view string, shard uint64) (uint64, error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Max() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	a, errA := c.a.Max(index, field, view, shard)
	_, _ = a, errA
	b, errB := c.b.Max(index, field, view, shard)
	_, _ = b, errB

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}
	return b, errB
}

func (c *blueGreenTx) Min(index, field, view string, shard uint64) (uint64, bool, error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see Min() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	amin, afound, errA := c.a.Min(index, field, view, shard)
	_, _, _ = amin, afound, errA
	bmin, bfound, errB := c.b.Min(index, field, view, shard)
	_, _, _ = bmin, bfound, errB

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}
	return bmin, bfound, errB
}

func (c *blueGreenTx) UnionInPlace(index, field, view string, shard uint64, others ...*roaring.Bitmap) error {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see UnionInPlace() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	errA := c.a.UnionInPlace(index, field, view, shard, others...)
	errB := c.b.UnionInPlace(index, field, view, shard, others...)
	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}
	return errB
}

func (c *blueGreenTx) CountRange(index, field, view string, shard uint64, start, end uint64) (n uint64, err error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			c.Dump(c.short, shard)
			AlwaysPrintf("see CountRange() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()
	a, errA := c.a.CountRange(index, field, view, shard, start, end)
	b, errB := c.b.CountRange(index, field, view, shard, start, end)

	if !c.o.blueGreenOff {
		if a != b {
			panic(fmt.Sprintf("a(%v) = %v, but b(%v) = %v", c.as, a, c.bs, b))
		}

		compareErrors(errA, errB)
	}
	return b, errB
}

func (c *blueGreenTx) OffsetRange(index, field, view string, shard, offset, start, end uint64) (other *roaring.Bitmap, err error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			AlwaysPrintf("see OffsetRange() on _sn_ %v, panic '%v' at '%v'", c.Sn(), r, stack())
			panic(r)
		}
	}()
	a, errA := c.a.OffsetRange(index, field, view, shard, offset, start, end)
	b, errB := c.b.OffsetRange(index, field, view, shard, offset, start, end)

	if !c.o.blueGreenOff {

		err = roaringBitmapDiff(a, b)
		if err != nil {
			c.Dump(false, shard)
			panicOn(fmt.Errorf("on _sn_ %v OffsetRange(index='%v', field='%v', view='%v', shard='%v', offset: %v start: %v, end: %v) err: %v", c.Sn(), index, field, view, int(shard), offset, start, end, err))
		}
		compareErrors(errA, errB)
	}

	return b, errB
}

func (c *blueGreenTx) RoaringBitmapReader(index, field, view string, shard uint64, fragmentPathForRoaring string) (r io.ReadCloser, sz int64, err error) {
	c.checker.see(index, field, view, shard)
	defer func() {
		if r := recover(); r != nil {
			c.Dump(c.short, shard)
			AlwaysPrintf("see RoaringBitmapReader() panic '%v' at '%v'", r, stack())
			panic(r)
		}
	}()

	rcA, szA, errA := c.a.RoaringBitmapReader(index, field, view, shard, fragmentPathForRoaring)
	rcB, szB, errB := c.b.RoaringBitmapReader(index, field, view, shard, fragmentPathForRoaring)

	if !c.o.blueGreenOff {
		compareErrors(errA, errB)
	}

	// We are seeing Roaring vs Badger size differences on
	// server/ test TestClusterResize_AddNode/ContinuousShards,
	// so turn off the szA vs szB checks and MutliReaderB use. But keep them if we want to
	// check RBF vs Badger for byte-for-byte compatiblity (we
	// suspect the ops log or optimized bitmaps are accounting for the difference).
	sizeMustMatch := false // !c.hasRoaring
	if c.o.blueGreenOff {
		sizeMustMatch = false
	}
	if sizeMustMatch {
		if szA != szB {
			panic(fmt.Sprintf("szA(%v) = %v, but szB(%v) = %v;  fragmentPathForRoaring='%v'", c.as, szA, c.bs, szB, fragmentPathForRoaring))
		}
		return &MultiReaderB{a: rcA, b: rcB}, szB, errB
	} else {
		// one db won't get data if we do
		//return &MultiReaderB{a: rcA, b: rcB, allowSizeVariation: true}, szB, errB
		_, _ = szA, errA
		rcA.Close()
		return rcB, szB, errB
	}
}

func (c *blueGreenTx) Group() *TxGroup {
	return c.b.Group()
}

func (c *blueGreenTx) Options() Txo {
	return c.b.Options()
}

// Sn retreives the serial number of the Tx.
func (c *blueGreenTx) Sn() int64 {
	asn := c.a.Sn()
	bsn := c.b.Sn()

	if c.useSnA {
		return asn
	}
	return bsn
}

func (c *blueGreenTx) ApplyFilter(index, field, view string, shard uint64, ckey uint64, filter roaring.BitmapFilter) (err error) {
	return GenericApplyFilter(c, index, field, view, shard, ckey, filter)
}

// MultiReaderB is returned by RoaringBitmapReader. It verifies
// that identical byte streams are read from its two members.
type MultiReaderB struct {
	a io.ReadCloser
	b io.ReadCloser

	allowSizeVariation bool
}

// Read implements the standard io.Reader method. It panics
// if "a" and "b" have even one byte different in their reads.
func (m *MultiReaderB) Read(p []byte) (nB int, errB error) {
	nB, errB = m.b.Read(p)
	p2 := make([]byte, nB)

	// read (and discard after comparing for equality) the exact same amount from A.

	// ReadAtLeast reads from r into buf until it has read at least
	// min bytes. It returns the number of bytes copied and an error
	// if fewer bytes were read. The error is EOF only if no bytes
	// were read. If an EOF happens after reading fewer than min bytes,
	// ReadAtLeast returns ErrUnexpectedEOF. If min is greater than
	// the length of buf, ReadAtLeast returns ErrShortBuffer. On
	// return, n >= min if and only if err == nil. If r returns
	// an error having read at least min bytes, the error is dropped.
	nA, errA := io.ReadAtLeast(m.a, p2, nB)

	if !m.allowSizeVariation {
		if errA == io.ErrUnexpectedEOF {
			panic(fmt.Sprintf("MultiReaderB got ErrUnexpectedEOF: read %v bytes from B, but could only read %v bytes for A", nB, nA))
		}
		if nA != nB {
			panic(fmt.Sprintf("MultiReaderB read %v bytes from B, but could only read %v bytes for A", nB, nA))
		}
		cmp := bytes.Compare(p[:nB], p2[:nB])
		if cmp != 0 {
			panic(fmt.Sprintf("MultiReaderB reads p and p2 (cmp= %v) differed.", cmp))
		}
	}
	return
}

func (m *MultiReaderB) Close() error {
	m.a.Close()
	return m.b.Close()
}

// blueGreenChecker is used
type blueGreenChecker struct {
	visited map[string]map[string]map[string]map[uint64]struct{}

	c *blueGreenTx

	// lock mu when using visited.
	// otherwise concurrent map writes on TestAPI_Import/RowIDColumnKey
	mu sync.Mutex

	checkDone bool
}

// see would mark a thing as seen.
func (b *blueGreenChecker) see(index, field, view string, shard uint64) {
	// keep this next Printf. Useful to see the sequence of Tx operations.
	//fmt.Printf("blueGreenTx.%v on index='%v' shard=%v\n", Caller(1), index, shard)

	if !b.c.o.Write {
		return
	}

	b.mu.Lock()
	defer b.mu.Unlock()

	if b.visited == nil {
		b.visited = make(map[string]map[string]map[string]map[uint64]struct{})
	}
	var visitedIdx map[string]map[string]map[uint64]struct{}
	var visitedField map[string]map[uint64]struct{}
	var visitedView map[uint64]struct{}

	if visitedIdx = b.visited[index]; visitedIdx == nil {
		visitedIdx = make(map[string]map[string]map[uint64]struct{})
		b.visited[index] = visitedIdx
	}
	if visitedField = visitedIdx[field]; visitedField == nil {
		visitedField = make(map[string]map[uint64]struct{})
		visitedIdx[field] = visitedField
	}
	if visitedView = visitedField[view]; visitedView == nil {
		visitedView = make(map[uint64]struct{})
		visitedField[view] = visitedView
	}
	visitedView[shard] = struct{}{}
}

// seen reports the things it has seen, exactly once so
// that Rollback can be called after Commit without repeating
// the check.
func (b *blueGreenChecker) seen() map[string]map[string]map[string]map[uint64]struct{} {
	return b.visited
}
