/*
 * Copyright (C) 2018 "IoT.bzh"
 * Author Clément Bénier <clement.benier@iot.bzh>
 *
 * 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 xdsserver

import (
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	sc "sync"
	"time"

	common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
	"gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xdsconfig"
	"gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
)

const (
	scriptXdsSrvUpdate    = "updateXdsServerPackage"
	scriptGetXdsSrvUpdate = "getXdsServerPackage"
	scriptXdsSrvRestart   = "restartXdsServer"
)

//LockXdsUpdate allows to lock xds-server avoiding restart
type LockXdsUpdate struct {
	sc.Mutex
	LockCpt int
}

//LockXdsUpdateCounter Inc/decrement lock counter
func LockXdsUpdateCounter(ctx *Context, inc bool) {
	if inc {
		//paranoia lock count > 10
		if ctx.lockXdsSrvUpdate.LockCpt > 10 {
			ctx.Log.Errorf("lock counter value is abnormally high: LockCpt=%v", ctx.lockXdsSrvUpdate.LockCpt)
		}
		ctx.lockXdsSrvUpdate.Lock()
		ctx.lockXdsSrvUpdate.LockCpt++
		ctx.lockXdsSrvUpdate.Unlock()
	} else {
		//paranoia lock count < 0
		if ctx.lockXdsSrvUpdate.LockCpt <= 0 {
			ctx.Log.Errorf("lock counter value is lower than 0: LockCpt=%v", ctx.lockXdsSrvUpdate.LockCpt)
			return
		}
		ctx.lockXdsSrvUpdate.Lock()
		ctx.lockXdsSrvUpdate.LockCpt--
		ctx.lockXdsSrvUpdate.Unlock()
	}
}

//MonitorUpdates try to update xds-server package at first
// then monitor updates
func MonitorUpdates(ctx *Context) {
	UpdateXdsServer(ctx) //try to update at startup

	updateTime, err := time.ParseDuration(ctx.Config.FileConf.XdsSrvUpdateTime)
	if err != nil {
		ctx.Log.Errorf("Wrong format type of XdsSrvUpdateTime\n"+
			"err=%v \n"+
			"Valid time units are ns, us, ms, s, m, h\n"+
			"Here an example: 1h10m10s\n"+
			"So, default value is set %v", err, xdsconfig.DefaultXdsSrvUpdateTime)
		updateTime, _ = time.ParseDuration(xdsconfig.DefaultXdsSrvUpdateTime)
	}
	ctx.Log.Infof("Update time for package xds-server is %v", updateTime)
	go func(ctx *Context) {
		for {
			currentUpdateTime := updateTime
			for currentUpdateTime > 0 {
				time.Sleep(currentUpdateTime)
				currentUpdateTime = UpdateXdsServer(ctx)
			}
		}
	}(ctx)
}

func getScriptsDir(ctx *Context) string {
	scriptsDir := ctx.Config.FileConf.XdsUtilsScriptsDir
	if !common.Exists(scriptsDir) {
		// allow to use scripts/xds-utils when debugging with vscode(EXEPATH=WORKSPACE)
		scriptsDir = filepath.Join(filepath.Dir(ctx.Config.FileConf.XdsUtilsScriptsDir), "scripts", "xds-utils")
		if !common.Exists(scriptsDir) {
			ctx.Log.Errorf("scripts directory doesn't exist (%v)", scriptsDir)
		}
	}
	return scriptsDir
}

// UpdateXdsServer launches update package xds-server script
func UpdateXdsServer(ctx *Context) time.Duration {
	timeToRestartIfBusy := 0 * time.Minute
	scriptsDir := getScriptsDir(ctx)

	ctx.Log.Infof("Trying to update xds-server package, "+
		"xds-utils scripts dir: %s", scriptsDir)

	//launch xds-server update package script
	cmd := exec.Command(path.Join(scriptsDir, scriptXdsSrvUpdate))
	_, err := cmd.CombinedOutput()
	if err != nil {
		ctx.Log.Errorf("Cannot update xds-server package err=%v", err)
		return 0
	}

	//launch xds-server restart script
	cmd = exec.Command(path.Join(scriptsDir, scriptXdsSrvRestart))
	ctx.lockXdsSrvUpdate.Lock()
	if ctx.lockXdsSrvUpdate.LockCpt == 0 { //no action in progress
		_, err = cmd.CombinedOutput()
		if err != nil {
			ctx.Log.Errorf("Cannot restart xds-server service err=%v", err)
			return 0
		}
	} else {
		timeToRestartIfBusy = 1 * time.Minute
		ctx.Log.Infof("Cannot restart xds-server service because "+
			"xds-server has an action in progress, trying to restart in a %v", timeToRestartIfBusy)
	}
	ctx.lockXdsSrvUpdate.Unlock()

	return timeToRestartIfBusy
}

// GetXdsSrvUpdate gets information about package
func GetXdsSrvUpdate(ctx *Context) xsapiv1.XdsSrvUpdate {
	var xdsSrvUpdate xsapiv1.XdsSrvUpdate
	scriptsDir := getScriptsDir(ctx)

	//exec getXdsSrvUpdate script
	cmd := exec.Command(path.Join(scriptsDir, scriptGetXdsSrvUpdate))
	stdout, err := cmd.CombinedOutput()
	if err != nil {
		ctx.Log.Errorf("Cannot get xds-server package information err=%v", err)
		return xdsSrvUpdate
	}

	//stdout is formatting with 'version: xxxxx'
	outputs := strings.Split(string(stdout[:]), "\n")
	installedVersion := strings.Split(outputs[0], ": ")[1]
	candidateVersion := strings.Split(outputs[1], ": ")[1]
	ctx.Log.Infof("XdsSrvUpdate: candidateVersion:%v installedVersion:%v", candidateVersion, installedVersion)
	xdsSrvUpdate = xsapiv1.XdsSrvUpdate{
		CurrentVersion: installedVersion,
		NewerVersion:   candidateVersion,
		UpdateTime:     ctx.Config.FileConf.XdsSrvUpdateTime,
	}
	return xdsSrvUpdate
}