/**************************************************************************************
 *
 *     Project:    General use
 *     File name:  configMap.go
 *     Version:    1.0.0
 *     Date:       2017-08-19
 *
 *     Autor:      Bogdanovic Theodor
 *     Contact:    t.bogdanovic@hotmail.com
 *
 *     License:    GPLv3
 *
 **************************************************************************************/
/**************************************************************************************
 * Copyright 2018 Bogdanovic Theodor
 *
 * This file is part of configMap.
 *
 * configMap 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.
 *
 * configMap 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 configMap.  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 configMap includes the logic to read in a config file and
// save it in a folder
//
// Configuration file pattern -> param name = value
package configMap

// nolint
import (
	"crypto/md5"
	"fmt"
	"io/ioutil"
	"strconv"
	"strings"
	"sync"
	"utils/configPoller"

	log "github.com/judwhite/logrjack"
)

//*****************************************************************************
//*
//*      Declarations
//*
//*****************************************************************************

// configurationFile is always transferred to the outside by
// reference. Definition takes place in this package.
// Implementation of the -Singelton Patterns-
var configurations settings

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

// settings includes all necessary information from this
// package to a config file
//
// Implement: IUpdateConfig
type settings struct {
	synchronous  sync.Mutex
	isLoaded     bool
	content      map[string]string
	fullFilePath string
}

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

// loadContent loads a config file as a byte slice and
// restructures it as map[string]string
func (slf *settings) loadContent() {

	// The loading process is synchronized and all reading
	// processes have to wait in case of an update
	slf.synchronous.Lock()
	defer slf.synchronous.Unlock()

	rawConfFileContent, err := ioutil.ReadFile(slf.fullFilePath)
	if err != nil {
		panic("Cannot read file")
	}

	// Additional line break. Is necessary because of the parser
	// below. Otherwise the last value of the config file will not be read
	rawConfFileContent = append(rawConfFileContent, byte('\n'))

	// Reading the byte slice and converting to string slice (raw format).
	// Separator is "="
	var newLine string
	var confFileArray []string
	for idx := 0; idx < len(rawConfFileContent); idx++ {
		if rawConfFileContent[idx] != byte('\n') &&
			rawConfFileContent[idx] != byte('\r') {
			newLine += string(rawConfFileContent[idx])
		} else {
			if strings.Contains(newLine, "\t") {
				newLine = strings.Replace(newLine, "\t", "", -1)
			}
			if strings.Contains(strings.TrimRight(strings.
				TrimLeft(newLine, " "), " "), "=") {
				if !strings.HasPrefix(newLine, "#") {
					newLine = strings.Split(newLine, "#")[0]
					confFileArray = append(confFileArray, newLine)
					newLine = ""
				} else {
					newLine = ""
					continue
				}
			} else {
				newLine = ""
				continue
			}
		}
	}

	// Converting string slice to string-string map and cut all spaces
	// with function trimAndConvToSting(string, int)
	slf.content = make(map[string]string)
	for _, line := range confFileArray {
		slf.content[trimAndConvToSting(line, 0)] = trimAndConvToSting(line, 1)
	}
}

/********************* Interface: IUpdateConfig *******************************/

// GetMd5Sum returns the caller the CheckSum of the config file in
// in byte slice shape
// nolint
func (slf *settings) GetMd5Sum() [16]byte {

	rawConfFileContent, err := ioutil.ReadFile(slf.fullFilePath)
	if err != nil {
		panic("Cannot read file")
	}

	return md5.Sum(rawConfFileContent)
}

// UpdateConfig initialize the update process of this package
func (slf *settings) UpdateConfig() error {

	slf.loadContent()

	log.Infof("The configuration has been updated for the following config file -> (%v)",
		slf.fullFilePath)

	return nil
}

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

// Settings generates one instance of the structure ConfigFile
// and return it. Every further attempt provides a reference to the
// already created instance -Singelton Pattern-
func Settings(path string) {

	if !configurations.isLoaded {
		if path == "" {
			panic("Need a path to a configuration file")
		}
		configurations.fullFilePath = path
		configurations.loadContent()
		configurations.isLoaded = true
	}
}

// GetConfigMapRef ...
func GetConfigMapRef() configPoller.IUpdateConfig { return &configurations }

// GetStringOf returns the search value as string
func GetStringOf(search string) string { return configurations.content[search] }

// GetInt32Of tries to cast the search value into a
// int32 and return the result
func GetInt32Of(search string) int32 {

	newValue, err := strconv.ParseInt(configurations.content[search], 10, 32)
	if err != nil {
		panicMsg := fmt.Sprintf("Cannot convert to INT32 -> %v", configurations.content[search])
		panic(panicMsg)
	}

	return (int32)(newValue)
}

// GetFloat32Of tries to cast the search value into a
// float32 and return the result
func GetFloat32Of(search string) float32 {

	newValue, err := strconv.ParseFloat(configurations.content[search], 32)
	if err != nil {
		panicMsg := fmt.Sprintf("Cannot convert to FLOAT32 -> %v", configurations.content[search])
		panic(panicMsg)
	}

	return (float32)(newValue)
}

// GetBoolOf tries to cast the search value into a
// boolen and return the result
func GetBoolOf(search string) bool {

	newValue, err := strconv.ParseBool(configurations.content[search])
	if err != nil {
		panicMsg := fmt.Sprintf("Cannot convert to Bool -> %v", configurations.content[search])
		panic(panicMsg)
	}

	return newValue
}

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

// trimAndConvToSting trims the input string from the right and
// left side only
func trimAndConvToSting(name string, pos int) string {

	name = strings.Split(name, "=")[pos]
	name = strings.TrimLeft(name, " ")
	name = strings.TrimRight(name, " ")

	return name
}
