/**************************************************************************************
 *
 *     Project:    kvmstoreserver
 *     File name:  nodeContainer.go
 *     Version:    1.0.0
 *     Date:       1970-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 kvmstoreserver.  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 bTreeHandling

import (
	"bytes"
	"encoding/json"
	es "essentials"
	"fmt"
	knh "kvmstoreserver_utils/keyNameHandling"
	sh "kvmstoreserver_utils/serdeHandling"
	"sync"

	log "github.com/judwhite/logrjack"
)

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

// nodeContainer includes all the elements necessary to store
// data and create and maintain the node in the tree
//
// Implement: IHandelWithNodes, IHandelSerialization
type nodeContainer struct {
	NodeID      int        `json:"node_id"`
	OrigKeyName string     `json:"orig_key_name"`
	WriteLock   sync.Mutex `json:"-"`

	BranchLeft  *nodeContainer `json:"-"`
	BranchRight *nodeContainer `json:"-"`

	DataContainer []*DataModel `json:"data_container"`
}

//******************** Methods ************************************************

// member checks if a node exists with the passed key context in the tree.
func (slf *nodeContainer) member(nodeCtx knh.IHandelWithIds) error {

	if slf.NodeID == nodeCtx.GetNodeID() {
		return nil
	}

	// Left Handling
	if nodeCtx.GetNodeID() < slf.NodeID {
		if slf.BranchLeft != nil {
			return slf.BranchLeft.member(nodeCtx)
		}
	} else {
		// Right Handling
		if slf.BranchRight != nil {
			return slf.BranchRight.member(nodeCtx)
		}
	}

	log.Infof("Tried to find a node, but it seems it is not a member -> (Node: %v) - (Origianl Key Name: %v)",
		nodeCtx.GetNodeID(), nodeCtx.GetKeyStrName())

	return fmt.Errorf("Can not find a ressource with key name -> %v",
		nodeCtx.GetKeyStrName())
}

// insertContainer a complete container in the Tree.
// Is it only used by the node loader -> stage0_ext1
func (slf *nodeContainer) insertContainer(ct IHandelWithNodes) {

	// Left Handling
	if ct.GetID() < slf.NodeID {
		if slf.BranchLeft == nil {
			slf.BranchLeft = (ct).(*nodeContainer)
			return
		}
		slf.BranchLeft.insertContainer(ct)
	} else if ct.GetID() > slf.NodeID {
		// Right Handling
		if slf.BranchRight == nil {
			slf.BranchRight = (ct).(*nodeContainer)
			return
		}
		slf.BranchRight.insertContainer(ct)
	}
}

// insert a dataModel in the tree. the position (nodeID) is
// read from the key context.
func (slf *nodeContainer) insert(nodeCtx knh.IHandelWithIds,
	data *DataModel) (int, error) {

	if slf.NodeID == nodeCtx.GetNodeID() {
		return slf.insertData(data)
	}

	// Left branch handling
	if nodeCtx.GetNodeID() < slf.NodeID {
		if slf.BranchLeft == nil {
			slf.BranchLeft = newNode(nodeCtx)

			log.Infof("Create new node with (ID: %v) for (Key name: %v)",
				nodeCtx.GetNodeID(), nodeCtx.GetKeyStrName())

			return slf.BranchLeft.insertData(data)
		}

		return slf.BranchLeft.insert(nodeCtx, data)
	}
	// Right branch handling
	if slf.BranchRight == nil {
		slf.BranchRight = newNode(nodeCtx)

		log.Infof("Create new node with (ID: %v) for (Key name: %v)",
			nodeCtx.GetNodeID(), nodeCtx.GetKeyStrName())

		return slf.BranchRight.insertData(data)
	}

	return slf.BranchRight.insert(nodeCtx, data)
}

// get returns an interface to the node container.
// This can be used to access data in the node contianer
func (slf *nodeContainer) get(nodeCtx knh.IHandelWithIds) IHandelWithNodes {

	if slf.NodeID == nodeCtx.GetNodeID() {
		log.Infof("Node found and returned -> (Node: %v) - (Origianl Key Name: %v)",
			nodeCtx.GetNodeID(), nodeCtx.GetKeyStrName())

		return slf
	}

	// Left Handling
	if nodeCtx.GetNodeID() < slf.NodeID {
		return slf.BranchLeft.get(nodeCtx)
	}

	// Right Handling
	return slf.BranchRight.get(nodeCtx)
}

// del empties the node container and deletes the information on the disk.
// The Contian itself remains intact during the term of the program and
// can be addressed and filled as needed
func (slf *nodeContainer) del(nodeCtx knh.IHandelWithIds) error {

	if slf.NodeID == nodeCtx.GetNodeID() {

		if slf.DataContainer != nil {
			deleteStorage := sh.DriverFactory(es.Delete)
			if delErr := deleteStorage(slf); delErr != nil {

				log.Infof("Internal storage problem, can not delete node with -> (ID: %v) | (Key name: %v)",
					nodeCtx.GetNodeID(), nodeCtx.GetKeyStrName())

				return fmt.Errorf("Internal storage problem, can not delete node with -> (ID: %v) | (Key name: %v)",
					nodeCtx.GetNodeID(), nodeCtx.GetKeyStrName())
			}

			slf.DataContainer = nil

			log.Infof("Deleted all datas of -> (Node: %v) - (Origianl Key Name %v)",
				nodeCtx.GetNodeID(), nodeCtx.GetKeyStrName())

			return nil
		}

		log.Infof("The container with the key \"%V\" is empty, there is nothing to delete in it",
			nodeCtx.GetKeyStrName())

		return fmt.Errorf("The container with the key \"%v\" is empty, there is nothing to delete in it",
			nodeCtx.GetKeyStrName())
	}

	// Left Handling
	if nodeCtx.GetNodeID() < slf.NodeID {
		return slf.BranchLeft.del(nodeCtx)
	}

	// Right Handling
	return slf.BranchRight.del(nodeCtx)

}

// insertData writes the data (DataModel) into the slice of the container.
// The container is locked during this operation (RW)
func (slf *nodeContainer) insertData(data *DataModel) (int, error) {

	defer slf.WriteLock.Unlock()
	slf.WriteLock.Lock()

	lastRevNbr := len(slf.DataContainer)
	data.RevNbr = lastRevNbr
	slf.DataContainer = append(slf.DataContainer, data)

	saveStorage := sh.DriverFactory(es.Save)

	if saveErr := saveStorage(slf); saveErr != nil {
		slf.DataContainer[lastRevNbr] = nil

		log.Infof("A storage medium error prevented the creation of the new revision -> (Rev: %v) -> (Current: %v)",
			lastRevNbr, lastRevNbr-1)

		return 0, fmt.Errorf("A storage medium error prevented the creation of the new revision -> (Rev: %v) -> (Current: %v)",
			lastRevNbr, lastRevNbr-1)

	}

	log.Infof("Inserted new content into -> (Node: %v) - (Origianl Key Name: %v) | (Current RevNbr.: %v)",
		slf.NodeID, slf.OrigKeyName, lastRevNbr)

	return lastRevNbr, nil
}

//******************** Interface: IHandelWithNodes ****************************

// GetID returns the node container ID
func (slf *nodeContainer) GetID() int { return slf.NodeID }

// GetOrigKeyName returns the origianl Key name
func (slf *nodeContainer) GetOrigKeyName() string { return slf.OrigKeyName }

// GetModelOrDefault provides the required revision of the
// DataModel. Default is the last and most recent
func (slf *nodeContainer) GetModelOrDefault(revNbr *int) (*DataModel, error) {

	if slf.DataContainer != nil {
		sliceLenght := len(slf.DataContainer)
		if revNbr != nil {
			if sliceLenght > *revNbr && *revNbr >= 0 {
				return slf.DataContainer[*revNbr], nil
			}

			return nil, fmt.Errorf(
				"Can not find the revision number - Valid range for this key -> 0 - %v",
				sliceLenght-1)
		}

		return slf.DataContainer[sliceLenght-1], nil
	}

	return nil, fmt.Errorf(
		`The container with the name "%v" existed once, but its contents were deleted`,
		slf.OrigKeyName)
}

//******************** Interface: IHandelSerialization ************************

// GetNodeID redirect to GetID() and retruns the node container ID
func (slf *nodeContainer) GetNodeID() int {
	return slf.NodeID
}

// Marshal generates a JSON document form its self
func (slf *nodeContainer) Marshal() []byte {

	jsonOutput, _ := json.Marshal(slf)

	return jsonOutput
}

//*****************************************************************************
//*
//*      Private Functions
//*
//*****************************************************************************

// newNode generates a new empty node with the ID from key context
func newNode(nodeCtx knh.IHandelWithIds) *nodeContainer {

	return &nodeContainer{
		NodeID:      nodeCtx.GetNodeID(),
		OrigKeyName: nodeCtx.GetKeyStrName(),
	}
}

//*****************************************************************************
//*
//*      Public Functions
//*
//*****************************************************************************

// CreateContainerFromSource creates a complete new node container, which was
// picked up by the filessystem loader and converted into a reader
func CreateContainerFromSource(r *bytes.Reader) IHandelWithNodes {

	var newContainer nodeContainer
	jsonDecoder := json.NewDecoder(r)

	decodeErr := jsonDecoder.Decode(&newContainer)
	if decodeErr != nil {
		errMsg := fmt.Sprintf("Apparently, manipulations have been made to a file. Abort (OrigMsg)-> %v",
			decodeErr.Error())
		panic(errMsg)
	}

	return &newContainer
}
