/*
 * EliasDB
 *
 * Copyright 2016 Matthias Ladkau. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

package cluster

import (
	"errors"
	"math"
	"testing"

	"devt.de/krotik/eliasdb/hash"
	"devt.de/krotik/eliasdb/storage"
)

func TestAddressTableClusterLoc(t *testing.T) {

	// Set a low distribution range

	defaultDistributionRange = 3
	defer func() { defaultDistributionRange = math.MaxUint64 }()

	// Test an inoperable cluster

	cluster1, ms1 := createCluster(1, 1)
	cluster1[0].distributionTable = nil
	cluster1[0].distributionTableError = errors.New("testerror")

	_, err := ms1[0].at.NewClusterLoc("test1")
	if err.Error() != "Storage is currently disabled on member: TestClusterMember-0 (testerror)" {
		t.Error("Unexpected result:", err)
		return
	}

	// Test normal number sequence

	cluster1, ms1 = createCluster(1, 1)

	loc, err := ms1[0].at.NewClusterLoc("test1")
	if loc != 1 || err != nil {
		t.Error("Unexpected result:", loc, err)
		return
	}
	ms1[0].at.SetTransClusterLoc("test1", loc, 123, 1)

	// Starting an unrelated counter should have no effect

	loc, err = ms1[0].at.NewClusterLoc("test2")
	if loc != 1 || err != nil {
		t.Error("Unexpected result:", loc, err)
		return
	}

	delete(ms1[0].at.newlocCounters, "test1")

	// Advance the counter

	loc, err = ms1[0].at.NewClusterLoc("test1")
	if loc != 2 || err != nil {
		t.Error("Unexpected result:", loc, err)
		return
	}
	ms1[0].at.SetTransClusterLoc("test1", loc, 123, 1)

	loc, err = ms1[0].at.NewClusterLoc("test1")
	if loc != 3 || err != nil {
		t.Error("Unexpected result:", loc, err)
		return
	}

	// Not filling the location 3

	// The next call should find the free location 3

	loc, err = ms1[0].at.NewClusterLoc("test1")
	if loc != 3 || err != nil {
		t.Error("Unexpected result:", loc, err)
		return
	}
	ms1[0].at.SetTransClusterLoc("test1", loc, 123, 1)

	// Now we are full the next call should error

	loc, err = ms1[0].at.NewClusterLoc("test1")
	if err.Error() != "Could not find any free storage location on this member" {
		t.Error("Unexpected result:", loc, err)
		return
	}

	// Change distribution table - test member which is in the middle of the cluster

	defaultDistributionRange = 120
	dd, _ := NewDistributionTable([]string{"aa", cluster1[0].MemberManager.Name(), "bb"}, 1)
	cluster1[0].distributionTable = dd
	cluster1[0].distributionTableError = nil

	start, end := cluster1[0].distributionTable.MemberRange(cluster1[0].MemberManager.Name())
	if start != 40 || end != 79 {
		t.Error("Unexpected range:", start, end)
		return
	}

	loc, err = ms1[0].at.NewClusterLoc("test1")
	if loc != 40 || err != nil {
		t.Error("Unexpected result:", loc, err)
		return
	}
	ms1[0].at.SetTransClusterLoc("test1", loc, 123, 1)

	// Simulate a lookup failure

	for i := 40; i < 70; i++ {
		ms1[0].at.SetTransClusterLoc("test1", uint64(i), 123, 1)
	}

	// Get the translation location for cluster location 6

	_, loc, _ = ms1[0].at.translation.GetValueAndLocation(transKey("test1", 69))

	msm := ms1[0].at.sm.(*storage.MemoryStorageManager)
	msm.AccessMap[loc] = storage.AccessCacheAndFetchSeriousError

	loc, err = ms1[0].at.NewClusterLoc("test1")
	if err.Error() != "Record is already in-use (<memory> - )" {
		t.Error("Unexpected result:", loc, err)
		return
	}

	delete(msm.AccessMap, loc)

	// Recreate the member address table

	ms1[0].at, err = newMemberAddressTable(cluster1[0], ms1[0].at.sm)
	if err != nil {
		t.Error("Could not recreate MemberAddressTable:", err)
		return
	}

	// Now check the translation lookup

	if tr, ok, err := ms1[0].at.TransClusterLoc("test1", 50); tr.Loc != 123 || tr.Ver != 1 || !ok || err != nil {
		t.Error("Unexpected translation:", tr, ok, err)
		return
	}

	if tr, ok, err := ms1[0].at.TransClusterLoc("test1", 150); tr != nil || ok || err != nil {
		t.Error("Unexpected translation:", tr, ok, err)
		return
	}

	if tr, ok, err := ms1[0].at.SetTransClusterLoc("test1", 50, 555, 2); tr.Loc != 123 || tr.Ver != 1 || !ok || err != nil {
		t.Error("Unexpected translation:", tr, ok, err)
		return
	}

	if tr, ok, err := ms1[0].at.TransClusterLoc("test1", 50); tr.Loc != 555 || tr.Ver != 2 || !ok || err != nil {
		t.Error("Unexpected translation:", tr, ok, err)
		return
	}

	if tr, ok, err := ms1[0].at.RemoveTransClusterLoc("test1", 50); tr.Loc != 555 || tr.Ver != 2 || !ok || err != nil {
		t.Error("Unexpected translation:", tr, ok, err)
		return
	}

	if tr, ok, err := ms1[0].at.TransClusterLoc("test1", 50); tr != nil || ok || err != nil {
		t.Error("Unexpected translation:", tr, ok, err)
		return
	}

	if tr, ok, err := ms1[0].at.RemoveTransClusterLoc("test1", 50); tr != nil || ok || err != nil {
		t.Error("Unexpected translation:", tr, ok, err)
		return
	}
}

func TestAddressTableTransfer(t *testing.T) {

	_, ms1 := createCluster(1, 1)

	// Test storing transfer requests

	ms1[0].at.AddTransferRequest([]string{"a,b"}, nil)
	ms1[0].at.AddTransferRequest([]string{"c,d"}, nil)
	ms1[0].at.AddTransferRequest([]string{"e,f"}, nil)

	counter := 0

	it := hash.NewHTreeIterator(ms1[0].at.transfer)
	for ; it.HasNext(); it.Next() {
		counter++
	}

	// Check that we have 3 entries

	if counter != 3 {
		t.Error("Unexpected counter value:", counter)
		return
	}
}
