// Copyright 2016 fatedier, fatedier@gmail.com
//
// 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 config

import (
	"fmt"
	"strconv"
	"strings"

	ini "github.com/vaughan0/go-ini"
)

var ServerCommonCfg *ServerCommonConf

// common config
type ServerCommonConf struct {
	ConfigFile string
	BindAddr   string
	BindPort   int64

	// If VhostHttpPort equals 0, don't listen a public port for http protocol.
	VhostHttpPort int64

	// if VhostHttpsPort equals 0, don't listen a public port for https protocol
	VhostHttpsPort int64

	// if DashboardPort equals 0, dashboard is not available
	DashboardPort  int64
	DashboardUser  string
	DashboardPwd   string
	AssetsDir      string
	LogFile        string
	LogWay         string // console or file
	LogLevel       string
	LogMaxDays     int64
	PrivilegeMode  bool
	PrivilegeToken string
	AuthTimeout    int64
	SubDomainHost  string

	// if PrivilegeAllowPorts is not nil, tcp proxies which remote port exist in this map can be connected
	PrivilegeAllowPorts map[int64]struct{}
	MaxPoolCount        int64
	HeartBeatTimeout    int64
	UserConnTimeout     int64
}

func GetDefaultServerCommonConf() *ServerCommonConf {
	return &ServerCommonConf{
		ConfigFile:       "./frps.ini",
		BindAddr:         "0.0.0.0",
		BindPort:         7000,
		VhostHttpPort:    0,
		VhostHttpsPort:   0,
		DashboardPort:    0,
		DashboardUser:    "admin",
		DashboardPwd:     "admin",
		AssetsDir:        "",
		LogFile:          "console",
		LogWay:           "console",
		LogLevel:         "info",
		LogMaxDays:       3,
		PrivilegeMode:    true,
		PrivilegeToken:   "",
		AuthTimeout:      900,
		SubDomainHost:    "",
		MaxPoolCount:     10,
		HeartBeatTimeout: 30,
		UserConnTimeout:  10,
	}
}

// Load server common configure.
func LoadServerCommonConf(conf ini.File) (cfg *ServerCommonConf, err error) {
	var (
		tmpStr string
		ok     bool
		v      int64
	)
	cfg = GetDefaultServerCommonConf()

	tmpStr, ok = conf.Get("common", "bind_addr")
	if ok {
		cfg.BindAddr = tmpStr
	}

	tmpStr, ok = conf.Get("common", "bind_port")
	if ok {
		v, err = strconv.ParseInt(tmpStr, 10, 64)
		if err == nil {
			cfg.BindPort = v
		}
	}

	tmpStr, ok = conf.Get("common", "vhost_http_port")
	if ok {
		cfg.VhostHttpPort, err = strconv.ParseInt(tmpStr, 10, 64)
		if err != nil {
			err = fmt.Errorf("Parse conf error: vhost_http_port is incorrect")
			return
		}
	} else {
		cfg.VhostHttpPort = 0
	}

	tmpStr, ok = conf.Get("common", "vhost_https_port")
	if ok {
		cfg.VhostHttpsPort, err = strconv.ParseInt(tmpStr, 10, 64)
		if err != nil {
			err = fmt.Errorf("Parse conf error: vhost_https_port is incorrect")
			return
		}
	} else {
		cfg.VhostHttpsPort = 0
	}

	tmpStr, ok = conf.Get("common", "dashboard_port")
	if ok {
		cfg.DashboardPort, err = strconv.ParseInt(tmpStr, 10, 64)
		if err != nil {
			err = fmt.Errorf("Parse conf error: dashboard_port is incorrect")
			return
		}
	} else {
		cfg.DashboardPort = 0
	}

	tmpStr, ok = conf.Get("common", "dashboard_user")
	if ok {
		cfg.DashboardUser = tmpStr
	}

	tmpStr, ok = conf.Get("common", "dashboard_pwd")
	if ok {
		cfg.DashboardPwd = tmpStr
	}

	tmpStr, ok = conf.Get("common", "assets_dir")
	if ok {
		cfg.AssetsDir = tmpStr
	}

	tmpStr, ok = conf.Get("common", "log_file")
	if ok {
		cfg.LogFile = tmpStr
		if cfg.LogFile == "console" {
			cfg.LogWay = "console"
		} else {
			cfg.LogWay = "file"
		}
	}

	tmpStr, ok = conf.Get("common", "log_level")
	if ok {
		cfg.LogLevel = tmpStr
	}

	tmpStr, ok = conf.Get("common", "log_max_days")
	if ok {
		v, err = strconv.ParseInt(tmpStr, 10, 64)
		if err == nil {
			cfg.LogMaxDays = v
		}
	}

	tmpStr, ok = conf.Get("common", "privilege_mode")
	if ok {
		if tmpStr == "true" {
			cfg.PrivilegeMode = true
		}
	}

	// PrivilegeMode configure
	if cfg.PrivilegeMode == true {
		tmpStr, ok = conf.Get("common", "privilege_token")
		if ok {
			if tmpStr == "" {
				err = fmt.Errorf("Parse conf error: privilege_token can not be empty")
				return
			}
			cfg.PrivilegeToken = tmpStr
		} else {
			err = fmt.Errorf("Parse conf error: privilege_token must be set if privilege_mode is enabled")
			return
		}

		cfg.PrivilegeAllowPorts = make(map[int64]struct{})
		tmpStr, ok = conf.Get("common", "privilege_allow_ports")
		if ok {
			// e.g. 1000-2000,2001,2002,3000-4000
			portRanges := strings.Split(tmpStr, ",")
			for _, portRangeStr := range portRanges {
				// 1000-2000 or 2001
				portArray := strings.Split(portRangeStr, "-")
				// length: only 1 or 2 is correct
				rangeType := len(portArray)
				if rangeType == 1 {
					// single port
					singlePort, errRet := strconv.ParseInt(portArray[0], 10, 64)
					if errRet != nil {
						err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
						return
					}
					ServerCommonCfg.PrivilegeAllowPorts[singlePort] = struct{}{}
				} else if rangeType == 2 {
					// range ports
					min, errRet := strconv.ParseInt(portArray[0], 10, 64)
					if errRet != nil {
						err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
						return
					}
					max, errRet := strconv.ParseInt(portArray[1], 10, 64)
					if errRet != nil {
						err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect, %v", errRet)
						return
					}
					if max < min {
						err = fmt.Errorf("Parse conf error: privilege_allow_ports range incorrect")
						return
					}
					for i := min; i <= max; i++ {
						cfg.PrivilegeAllowPorts[i] = struct{}{}
					}
				} else {
					err = fmt.Errorf("Parse conf error: privilege_allow_ports is incorrect")
					return
				}
			}
		}
	}

	tmpStr, ok = conf.Get("common", "max_pool_count")
	if ok {
		v, err = strconv.ParseInt(tmpStr, 10, 64)
		if err == nil && v >= 0 {
			cfg.MaxPoolCount = v
		}
	}

	tmpStr, ok = conf.Get("common", "authentication_timeout")
	if ok {
		v, errRet := strconv.ParseInt(tmpStr, 10, 64)
		if errRet != nil {
			err = fmt.Errorf("Parse conf error: authentication_timeout is incorrect")
			return
		} else {
			cfg.AuthTimeout = v
		}
	}

	tmpStr, ok = conf.Get("common", "subdomain_host")
	if ok {
		cfg.SubDomainHost = strings.ToLower(strings.TrimSpace(tmpStr))
	}

	tmpStr, ok = conf.Get("common", "heartbeat_timeout")
	if ok {
		v, errRet := strconv.ParseInt(tmpStr, 10, 64)
		if errRet != nil {
			err = fmt.Errorf("Parse conf error: heartbeat_timeout is incorrect")
			return
		} else {
			cfg.HeartBeatTimeout = v
		}
	}
	return
}