/*
 * Copyright (c) 2020 Huawei Technologies Co., Ltd.
 * isula-transform is licensed under the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v2 for more details.
 * Create: 2020-04-24
 */

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"
	"github.com/urfave/cli"
	"gopkg.in/natefinch/lumberjack.v2"
	"isula.org/isula-transform/pkg/isulad"
	"isula.org/isula-transform/transform"
	_ "isula.org/isula-transform/transform/register"
	"isula.org/isula-transform/utils"
)

const (
	exitNormal = iota
	exitInitErr
	exitTransformErr

	maxConcurrentTransform = 128
	maxPerLogFileSize      = 10 // megabytes

	isuladConfFIle = "/etc/isulad/daemon.json"
)

var (
	version   string
	gitCommit string
)

func genVersion() string {
	versions := []string{
		"version:" + version,
		"commit:" + gitCommit,
	}
	return strings.Join(versions, "\t")
}

func main() {
	app := &cli.App{
		Name:      "isula-transform",
		Usage:     "transform specify docker container type configuration to iSulad type",
		UsageText: "[global options] --all|container_id[ container_id...]",
		Version:   genVersion(),
		Action:    start,
	}
	for _, v := range transformFlags {
		app.Flags = append(app.Flags, v...)
	}
	if err := app.Run(os.Args); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(exitInitErr)
	}
	os.Exit(exitNormal)
}

func start(ctx *cli.Context) error {
	logInit(ctx)
	if err := transformInit(); err != nil {
		return cli.NewExitError(err.Error(), exitInitErr)
	}
	return doTransform(ctx)
}

func logInit(ctx *cli.Context) {
	logPath := ctx.GlobalString("log")
	logRoot := filepath.Dir(logPath)
	if err := os.MkdirAll(logRoot, 0660); err != nil {
		logrus.SetOutput(os.Stdout)
		logrus.Infof("create the log directory %s failed: %v, using STDOUT", logRoot, err)
	} else {
		logrus.SetOutput(&lumberjack.Logger{
			Filename: logPath,
			MaxSize:  maxPerLogFileSize,
			Compress: true,
		})
	}

	logrus.SetLevel(transLogLevel(ctx.GlobalString("log-level")))
}

func transLogLevel(lvl string) logrus.Level {
	switch strings.ToLower(lvl) {
	case "error":
		return logrus.ErrorLevel
	case "warn":
		return logrus.WarnLevel
	case "debug":
		return logrus.DebugLevel
	default:
	}
	return logrus.InfoLevel
}

func transformInit() error {
	var conf = isulad.DaemonConfig{}
	if err := utils.CheckFileValid(isuladConfFIle); err != nil {
		return errors.Wrapf(err, "check isulad daemon config failed")
	}
	confData, err := ioutil.ReadFile(isuladConfFIle)
	if err != nil {
		logrus.Errorf("read isulad daemon config failed: %v, file path: %s", err, isuladConfFIle)
		return errors.Wrapf(err, "read isulad daemon config failed")
	}
	err = json.Unmarshal(confData, &conf)
	if err != nil {
		logrus.Errorf("unmarshal isulad daemon config failed: %v, file path: %s", err, isuladConfFIle)
		return errors.Wrapf(err, "unmarshal isulad daemon config failed")
	}

	logrus.Debugf("isulad daemon config: %+v", conf)
	err = isulad.InitIsuladTool(&conf)
	if err != nil {
		return errors.Wrapf(err, "transform init failed")
	}
	if conf.LogDriver != "file" {
		logrus.Infof("isula daemon log driver is %s, can't redirect to file", conf.LogDriver)
	} else {
		isulad.LcrLogInit(conf.State, conf.Runtime, conf.LogLevel)
	}
	return nil
}

func doTransform(ctx *cli.Context) error {
	e := transform.GetTransformer(ctx)
	if e == nil {
		return cli.NewExitError("get transform engine failed", exitInitErr)
	}
	if err := e.Init(); err != nil {
		return cli.NewExitError("transform engine init failed", exitInitErr)
	}

	var ids []string
	all := ctx.GlobalBool("all")
	if !all {
		if ctx.Args().Present() {
			ids = append(ctx.Args().Tail(), ctx.Args().First())
		} else {
			exitMsg := "isula-transform requires at least one container id as an input or setting the --all flag"
			return cli.NewExitError(exitMsg, exitInitErr)
		}
	}

	exitCode := exitNormal
	retCh := make(chan transform.Result, maxConcurrentTransform)
	go e.Transform(ids, all, retCh)
	for ret := range retCh {
		if !ret.Ok {
			exitCode = exitTransformErr
			fmt.Fprintln(os.Stderr, ret.Msg)
		} else {
			fmt.Fprintln(os.Stdout, ret.Msg)
		}
	}
	if exitCode != exitNormal {
		return cli.NewExitError("The transformation has been completed, but at least one failed", exitTransformErr)
	}
	return nil
}
