// 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      int
	BindUdpPort   int
	KcpBindPort   int
	ProxyBindAddr string

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

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

	// if DashboardPort equals 0, dashboard is not available
	DashboardPort  int
	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
	TcpMux         bool

	PrivilegeAllowPorts map[int]struct{}
	MaxPoolCount        int64
	HeartBeatTimeout    int64
	UserConnTimeout     int64
}

func GetDefaultServerCommonConf() *ServerCommonConf {
	return &ServerCommonConf{
		ConfigFile:          "./frps.ini",
		BindAddr:            "0.0.0.0",
		BindPort:            7000,
		BindUdpPort:         0,
		KcpBindPort:         0,
		ProxyBindAddr:       "0.0.0.0",
		VhostHttpPort:       0,
		VhostHttpsPort:      0,
		DashboardAddr:       "0.0.0.0",
		DashboardPort:       0,
		DashboardUser:       "admin",
		DashboardPwd:        "admin",
		AssetsDir:           "",
		LogFile:             "console",
		LogWay:              "console",
		LogLevel:            "info",
		LogMaxDays:          3,
		PrivilegeMode:       true,
		PrivilegeToken:      "",
		AuthTimeout:         900,
		SubDomainHost:       "",
		TcpMux:              true,
		PrivilegeAllowPorts: make(map[int]struct{}),
		MaxPoolCount:        5,
		HeartBeatTimeout:    90,
		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 {
		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
			err = fmt.Errorf("Parse conf error: invalid bind_port")
			return
		} else {
			cfg.BindPort = int(v)
		}
	}

	tmpStr, ok = conf.Get("common", "bind_udp_port")
	if ok {
		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
			err = fmt.Errorf("Parse conf error: invalid bind_udp_port")
			return
		} else {
			cfg.BindUdpPort = int(v)
		}
	}

	tmpStr, ok = conf.Get("common", "kcp_bind_port")
	if ok {
		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
			err = fmt.Errorf("Parse conf error: invalid kcp_bind_port")
			return
		} else {
			cfg.KcpBindPort = int(v)
		}
	}

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

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

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

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

	tmpStr, ok = conf.Get("common", "dashboard_port")
	if ok {
		if v, err = strconv.ParseInt(tmpStr, 10, 64); err != nil {
			err = fmt.Errorf("Parse conf error: invalid dashboard_port")
			return
		} else {
			cfg.DashboardPort = int(v)
		}
	} 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 {
		cfg.PrivilegeToken, _ = conf.Get("common", "privilege_token")

		allowPortsStr, ok := conf.Get("common", "privilege_allow_ports")
		if ok {
			// e.g. 1000-2000,2001,2002,3000-4000
			portRanges := strings.Split(allowPortsStr, ",")
			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
					}
					cfg.PrivilegeAllowPorts[int(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[int(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", "tcp_mux")
	if ok && tmpStr == "false" {
		cfg.TcpMux = false
	} else {
		cfg.TcpMux = true
	}

	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
}