package config

import (
	"flag"
	"fmt"
	"strings"

	"github.com/BurntSushi/toml"
	"github.com/juju/errors"
)

// CmdName defines different commands.
type CmdName string

const (
	// CmdPortal represents portal
	CmdPortal CmdName = "portal"
	// CmdStargate represents stargate
	CmdStargate = "stargate"
)

func (c CmdName) String() string {
	switch c {
	case CmdPortal:
		return "portal"
	case CmdStargate:
		return "stargate"
	default:
		return "unknown cmd"
	}
}

const (
	defaultEtherUrls = "http://127.0.0.1:2379"
	defaultZkUrls    = "127.0.0.1:2181"
)

//Config contains configuration options.
type Config struct {
	*flag.FlagSet `json:"-"`
	Version       bool `json:"-"`

	EtherEndpoints []string
	KafkaBrokers   []string

	EtherUrls     string `toml:"ether-urls" json:"ether-urls"`
	ZookeeperUrls string `toml:"zookeeper-urls" json:"zookeeper-urls"`
	LogLevel      string `toml:"log-level" json:"log-level"`
	LogFile       string `toml:"log-file" json:"log-file"`
	LogRotate     string `toml:"log-rotate" json:"log-rotate"`

	RoutineCnt    int    `toml:"routine-cnt" json:"routine-cnt"`
	ChunkSize     uint64 `toml:"rows-per-chunk" json:"rows-per-chunk"`
	MySQLServerID uint64 `toml:"mysql-serverid" json:"mysql-serverid"`

	ContractLease  uint64 `toml:"contract-lease" json:"contract-lease"`
	HeartbeatLease uint64 `toml:"heartbeat-lease" json:"heartbeat-lease"`
	StatusPort     uint64 `toml:"status-port" json:"status-port"`
	WorkerIDFile   string `toml:"workerid_file" json:"workerid_file"`
	Batch          int64  `toml:"batch" json:"batch"`
	configFile     string
}

//NewConfig initializes Config
func NewConfig(name CmdName) *Config {
	cfg := &Config{}
	cfg.FlagSet = flag.NewFlagSet(name.String(), flag.ContinueOnError)
	fs := cfg.FlagSet

	// common flags
	fs.StringVar(&cfg.configFile, "config", "", "Config file")
	fs.BoolVar(&cfg.Version, "V", false, "print version information and exit")
	fs.BoolVar(&cfg.Version, "version", false, "print version information and exit")
	fs.StringVar(&cfg.LogLevel, "L", "info", "log level: debug, info, warn, error, fatal")
	fs.StringVar(&cfg.LogFile, "log-file", "", "log file path")
	fs.StringVar(&cfg.LogRotate, "log-rotate", "day", "log file rotate type, hour/day")
	fs.StringVar(&cfg.EtherUrls, "ether-urls", defaultEtherUrls, "ether client urls, separated by comma")
	fs.StringVar(&cfg.WorkerIDFile, "workerid-file", fmt.Sprintf(".%s_workerid", name), fmt.Sprintf("%s worker id file", name))
	fs.Uint64Var(&cfg.ContractLease, "contract-lease", 80, "contract lease for renew job")
	fs.Uint64Var(&cfg.HeartbeatLease, "heartbeat-lease", 15, "heartbeat lease for contact ether-client")
	fs.StringVar(&cfg.ZookeeperUrls, "zookeeper-urls", defaultZkUrls, "REQUIRED: zookeeper server urls, separated by comma")

	// customized flags
	switch name {
	case CmdPortal:
		fs.IntVar(&cfg.RoutineCnt, "routine-cnt", 4, "routine cnt for dump")
		fs.Uint64Var(&cfg.ChunkSize, "chunk-size", 1000, "rows per chunk")
		fs.Uint64Var(&cfg.MySQLServerID, "mysql-serverid", 1001, "mysql-serverid for mysql slave, must be unique.")
		fs.Uint64Var(&cfg.StatusPort, "status-port", 10086, "port for program status, like pprof")
	case CmdStargate:
		fs.Uint64Var(&cfg.StatusPort, "status-port", 10087, "port for program status, like pprof")
		fs.Int64Var(&cfg.Batch, "batch", 10, "batch numbers of sql to execute")
	}
	return cfg
}

// Parse parses flag definitions from the argument list.
func (c *Config) Parse(arguments []string) error {
	// Parse first to get config file.
	err := c.FlagSet.Parse(arguments)
	if err != nil {
		return errors.Trace(err)
	}

	// Load config file if specified.
	if c.configFile != "" {
		if err1 := c.configFromFile(c.configFile); err1 != nil {
			return errors.Trace(err)
		}
	}

	// Parse again to replace with command line options.
	err = c.FlagSet.Parse(arguments)
	if err != nil {
		return errors.Trace(err)
	}

	if len(c.FlagSet.Args()) != 0 {
		return errors.Errorf("'%s' is an invalid flag", c.FlagSet.Arg(0))
	}

	c.EtherEndpoints = strings.Split(c.EtherUrls, ",")
	return errors.Trace(err)
}

// configFromFile loads config from file.
func (c *Config) configFromFile(path string) error {
	_, err := toml.DecodeFile(path, c)
	return errors.Trace(err)
}
