/*
 * Copyright (C) 2017-2018 "IoT.bzh"
 * Author Sebastien Douheret <sebastien@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 xdsconfig

import (
	"fmt"
	"io"
	"os"
	"os/user"
	"path"
	"path/filepath"

	common "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib"
	"gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
)

// Config parameters (json format) of /config command
type Config struct {
	// Public APIConfig fields
	xsapiv1.APIConfig

	// Private (un-exported fields in REST GET /config route)
	Options       Options        `json:"-"`
	FileConf      FileConfig     `json:"-"`
	Log           *logrus.Logger `json:"-"`
	LogVerboseOut io.Writer      `json:"-"`
}

// Options set at the command line
type Options struct {
	ConfigFile     string
	LogLevel       string
	LogFile        string
	NoFolderConfig bool
}

// Config default values
const (
	DefaultAPIVersion         = "1"
	DefaultPort               = "8000"
	DefaultShareDir           = "projects"
	DefaultSTHomeDir          = "syncthing-config"
	DefaultSdkScriptsDir      = "${EXEPATH}/sdks"
	DefaultXdsUtilsScriptsDir = "${EXEPATH}/xds-utils"
	DefaultSdkDbUpdate        = "startup"
	DefaultXdsSrvUpdateTime   = "24h"
)

// Init loads the configuration on start-up
func Init(cliCtx *cli.Context, log *logrus.Logger) (*Config, error) {
	var err error

	dfltShareDir := path.Join(ConfigRootDir(), DefaultShareDir)
	dfltSTHomeDir := path.Join(ConfigRootDir(), DefaultSTHomeDir)
	if resDir, err := common.ResolveEnvVar(dfltShareDir); err == nil {
		dfltShareDir = resDir
	}
	if resDir, err := common.ResolveEnvVar(dfltSTHomeDir); err == nil {
		dfltSTHomeDir = resDir
	}

	// Retrieve Server ID (or create one the first time)
	uuid, err := ServerIDGet()
	if err != nil {
		return nil, err
	}

	// Define default configuration
	c := Config{
		APIConfig: xsapiv1.APIConfig{
			ServerUID:        uuid,
			Version:          cliCtx.App.Metadata["version"].(string),
			APIVersion:       DefaultAPIVersion,
			VersionGitTag:    cliCtx.App.Metadata["git-tag"].(string),
			Builder:          xsapiv1.BuilderConfig{},
			SupportedSharing: map[string]bool{xsapiv1.TypePathMap: true},
		},

		Options: Options{
			ConfigFile:     cliCtx.GlobalString("config"),
			LogLevel:       cliCtx.GlobalString("log"),
			LogFile:        cliCtx.GlobalString("logfile"),
			NoFolderConfig: cliCtx.GlobalBool("no-folderconfig"),
		},
		FileConf: FileConfig{
			WebAppDir:          "www",
			ShareRootDir:       dfltShareDir,
			SdkScriptsDir:      DefaultSdkScriptsDir,
			XdsUtilsScriptsDir: DefaultXdsUtilsScriptsDir,
			SdkDbUpdate:        DefaultSdkDbUpdate,
			HTTPPort:           DefaultPort,
			SThgConf:           &SyncThingConf{Home: dfltSTHomeDir},
			LogsDir:            "",
			XdsSrvUpdateTime:   DefaultXdsSrvUpdateTime,
		},
		Log: log,
	}

	c.Log.Infoln("Server UUID:          ", uuid)

	// config file settings overwrite default config
	err = readGlobalConfig(&c, c.Options.ConfigFile)
	if err != nil {
		return nil, err
	}

	// Update location of shared dir if needed
	if !common.Exists(c.FileConf.ShareRootDir) {
		if err := os.MkdirAll(c.FileConf.ShareRootDir, 0770); err != nil {
			return nil, fmt.Errorf("No valid shared directory found: %v", err)
		}
	}
	c.Log.Infoln("Share root directory: ", c.FileConf.ShareRootDir)

	// Where Logs are redirected:
	//  default 'stdout' (logfile option default value)
	//  else use file (or filepath) set by --logfile option
	//  that may be overwritten by LogsDir field of config file
	logF := c.Options.LogFile
	logD := c.FileConf.LogsDir
	if logF != "stdout" {
		if logD != "" {
			lf := filepath.Base(logF)
			if lf == "" || lf == "." {
				lf = "xds-server.log"
			}
			logF = filepath.Join(logD, lf)
		} else {
			logD = filepath.Dir(logF)
		}
	}
	if logD == "" || logD == "." {
		logD = "/tmp/xds/logs"
	}
	c.Options.LogFile = logF
	c.FileConf.LogsDir = logD

	if c.FileConf.LogsDir != "" && !common.Exists(c.FileConf.LogsDir) {
		if err := os.MkdirAll(c.FileConf.LogsDir, 0770); err != nil {
			return nil, fmt.Errorf("Cannot create logs dir: %v", err)
		}
	}

	c.Log.Infoln("Logs file:            ", c.Options.LogFile)
	c.Log.Infoln("Logs directory:       ", c.FileConf.LogsDir)

	return &c, nil
}

// ConfigRootDir return the root directory where xds server save all config files
func ConfigRootDir() string {
	root := "$HOME"
	if usr, err := user.Current(); err == nil {
		root = usr.HomeDir
	}

	// Default $HOME/.xds/server but may be changed by an env variable
	if envVar, envDef := os.LookupEnv("XDS_SERVER_ROOT_CFG_DIR"); envDef {
		root = envVar
	}

	return path.Join(root, "/.xds/server")
}

// WorkspaceRootDir return the path on server side where user xds-workspace dir is accessible
func WorkspaceRootDir() string {
	// May be overloaded by an env variable
	if envVar, envDef := os.LookupEnv("XDS_SERVER_WORKSPACE_DIR"); envDef {
		return envVar
	}

	home := "${HOME}"
	if usr, err := user.Current(); err == nil {
		home = usr.HomeDir
	}

	// Default value $HOME/xds-workspace
	return path.Join(home, "xds-workspace")
}