/**************************************************************************************
 *
 *     Project:    kvmstoreserver
 *     File name:  stage0_ext1.go
 *     Version:    1.0.0
 *     Date:       2018-01-14
 *
 *     Autor:      Bogdanovic Theodor
 *     Contact:    t.bogdanovic@hotmail.com
 *
 *     License:    GPLv3
 *
 **************************************************************************************/
/**************************************************************************************
 * Copyright 2018 Bogdanovic Theodor
 *
 * This file is part of kvmstoreserver.
 *
 * kvmstoreserver is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * kvmstoreserver 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Foobar.  If not, see <http://www.gnu.org/licenses/>.
 **************************************************************************************/
/**************************************************************************************
 *  Further explanations:  This source code is under the license specified by the
 *                         Autor above. Core components or other packages are still
 *                         in source code form, under the license specified by the
 *                         respective author.
 *
 **************************************************************************************/

package main

import (
	"fmt"
	"io/ioutil"
	bth "kvmstoreserver_utils/bTreeHandling"
	sh "kvmstoreserver_utils/serdeHandling"
	"sort"
	"strconv"
	"strings"
	"sync"
	conf "utils/configMap"

	log "github.com/judwhite/logrjack"
)

//*****************************************************************************
//*
//*      Private Functions: Loader Functions
//*
//*****************************************************************************

// initializeBtree initialize the Btree loader
func initializeBtree() {

	newNodeLoaderController().fillTree()
}

//*****************************************************************************
//*
//*      Structure:
//*
//*****************************************************************************

// nodeLoaderController This structure serves as a node deserializer and
// loader. It synchronizes its own structure with an internal lock mechanism
type nodeLoaderController struct {
	initBtree      bth.IBtreeController
	rootNodeID     int
	nodeListSlice  []int
	nodeListLenght int
	nodeCounter    int
	lockController sync.Mutex
	wg             sync.WaitGroup
}

//******************** Load Methods *******************************************

// fillTree this structure takes over the actual loading of the nodes into
// the tree. It executes the loading of the nodes in parallel (uncontrolled
// with up to 10000 goroutines or mapped 1:1 OS threads)
func (slf *nodeLoaderController) fillTree() {

	backend := sh.LoaderFactory()

	if slf.nodeListLenght == 0 && slf.rootNodeID == 0 {
		log.Info("The backend storage was empty - Nothing loaded")
	} else {

		newRootNode := bth.CreateContainerFromSource(backend(slf.rootNodeID))
		slf.initBtree.InsertContainer(newRootNode)

		log.Infof("The following node was loaded as root -> (ID: %v) | (Orig Key Name: %v)",
			newRootNode.GetID(), newRootNode.GetOrigKeyName())

		// Here is the parallel load of nodes. The WaitGroup is prepared in
		// the Cotroller, but there were problems with using it in a FOR loop.
		// On a file system level, this is not necessary. For future use with
		// a DB, a chunk-based method must be implemented
		for idx := 0; idx < slf.nodeListLenght; idx++ {
			slf.wg.Add(1)
			go deserialNode(slf, slf.nodeListSlice[idx])
		}

		// Wait until all routines are done
		slf.wg.Wait()

		if slf.nodeCounter != slf.nodeListLenght {
			log.Fatalf("Not all nodes could be deserialized (only: %v) - Start aborted",
				slf.nodeCounter)
		}
		log.Infof("All nodes could be loaded | Loaded -> %v",
			slf.nodeListLenght)
	}

}

//*****************************************************************************

//*****************************************************************************
//*
//*      Public Fnctions
//*
//*****************************************************************************

// newNodeLoaderController returns a prepared controller back
func newNodeLoaderController() *nodeLoaderController {

	var nodeLoader nodeLoaderController

	nodeLoader.initBtree = bth.NewBtree()
	nodeLoader.nodeListSlice, nodeLoader.rootNodeID = getNodeListWithRoot()
	nodeLoader.nodeListLenght = len(nodeLoader.nodeListSlice)

	return &nodeLoader
}

//*****************************************************************************
//*
//*      Private Functions: Helper Functions
//*
//*****************************************************************************

// getSortedNodeList This function reads in the file names of the nodes and
// saves them in a slice. It also extracts the most suitable node as root node
func getNodeListWithRoot() ([]int, int) {

	var intList []int
	var copyOfIntList []int
	var rootNode int

	rawObjList, loadErr := ioutil.ReadDir(conf.GetStringOf("storage_target"))
	if loadErr != nil {
		panic("Can not load node files!")
	}

	if len(rawObjList) > 0 {
		for idx := 0; idx < len(rawObjList); idx++ {
			rawName := strings.Split(rawObjList[idx].Name(), ".json")[0]
			newInt64, err := strconv.ParseInt(rawName, 10, 64)
			if err != nil {
				errMsg := fmt.Sprintf("Corrupt file name -> (%v)",
					rawObjList[idx].Name())
				panic(errMsg)
			}

			newInt := int(newInt64)
			intList = append(intList, newInt)

		}
		copyOfIntList = make([]int, len(intList))

		// Make a copy of the slice and sort it. This identifies the middle
		// node, which serves as the root node. The list was then discarded
		copy(copyOfIntList, intList)
		sort.Ints(copyOfIntList)

		rootNodeIdx := len(copyOfIntList) / 2
		rootNode = copyOfIntList[rootNodeIdx]

		if rootNodeIdx == 0 && len(rawObjList) == 1 {
			return make([]int, 0), rootNode
		}

		var rootDelIdx int

		if rootNodeIdx > 0 {
			for index, id := range intList {
				if id == rootNode {
					rootDelIdx = index
				}
			}

			intList = append(intList[:rootDelIdx], intList[rootDelIdx+1:]...)
		}
	} else {
		return make([]int, 0), rootNode
	}

	return intList, rootNode
}

// deserialNode function is often executed as a standalone goutout
func deserialNode(ncl *nodeLoaderController, curruentListIdx int) {

	defer func() {
		err := recover()
		if err != nil {
			log.Warnf("Problem by parallel deserialization -> (ID: %v) | (Orig Err: %v)",
				curruentListIdx, err)
		}
	}()

	backend := sh.LoaderFactory()

	ncl.lockController.Lock()

	ncl.initBtree.InsertContainer(bth.CreateContainerFromSource(backend(curruentListIdx)))
	ncl.nodeCounter++
	ncl.wg.Add(-1)

	ncl.lockController.Unlock()
}
