package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"strconv"

	"./http_client"
	"./log"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var (
	usage = `
	-s <Command>
	[command]:
		start : serve and listen
		reload : reload config file
		stop : shutdown
`
	Command = []string{
		"reload",
		"start",
		"stop",
	}
)

type ProxyConfig struct {
	ListenAddr string `json:"addr"`
	NewPacket  bool   `json:"new_packet"`
	PrivateKey string `json:"private_key"`
}

type IPContainerConfig struct {
	IpLimit      int32 `json:"single_ip_limit"`
	IpExpireTime int   `json:"black_expire_time"` //秒
	CacheSize    int   `json:"cache_size"`
}

type GlobalConfig struct {
	Listen    *ProxyConfig       `json:"listen"`
	Proxy     []string           `json:"proxy"`
	HttpPort  int                `json:"http_port"`
	LimitInfo *IPContainerConfig `json:"limit_info"`
}

func ReloadConfig(path string) (*GlobalConfig, bool) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		fmt.Println("open config failed", path, err)
		return nil, false
	}
	c := &GlobalConfig{}
	err = json.Unmarshal(data, c)
	if err != nil {
		fmt.Println("config parse failed", path, err)
		return nil, false
	}

	if c.Proxy == nil || len(c.Proxy) == 0 {
		fmt.Println("proxy addr can not empty")
		return nil, false
	}

	if c.Listen == nil {
		log.Error("no listen addr")
		return nil, false
	}
	if c.HttpPort <= 1024 {
		log.Error("error http port", zap.Int("port", c.HttpPort))
		return nil, false
	}

	if c.LimitInfo == nil {
		c.LimitInfo = &IPContainerConfig{
			IpLimit:      0,
			IpExpireTime: 60 * 60 * 24,
			CacheSize:    1024 * 1024 * 100,
		}
	}

	if c.LimitInfo.CacheSize < 1024*500 {
		c.LimitInfo.CacheSize = 1024 * 1024 * 100
	}
	if c.LimitInfo.IpExpireTime < 60*60 {
		c.LimitInfo.IpExpireTime = 60 * 60
	}

	g_http_address = "127.0.0.1:" + strconv.FormatInt(int64(c.HttpPort), 10)
	return c, true
}

func checkCommand(command string) bool {
	if command == "" {
		return false
	}
	for _, v := range Command {
		if v == command {
			return true
		}
	}
	return false
}

func main() {
	var command string
	flag.StringVar(&command, "s", "", usage)
	flag.Parse()
	if command == "" {
		flag.Usage()
		return
	}
	if !checkCommand(command) {
		flag.Usage()
		return
	}
	logger := log.NewLogger(true, "./log_file", zapcore.InfoLevel)
	log.SetDefaultLog(logger)
	doCommand(command)

}

func checkRunning() (string, bool) {
	data, status, err := httpclient.HTTPPost("http://"+g_http_address+"/", map[string]string{"ping": "ping"})
	if err != nil || status != http.StatusOK {
		return "", false
	}
	return string(data), true
}

func doCommand(command string) {
	switch command {
	case "reload":
		listenAddr, ok := checkRunning()
		if !ok {
			log.Error("proxy_gate is not running")
			return
		}
		c, ok := ReloadConfig(CONF_PATH)
		if !ok {
			return
		}
		if c.Listen.ListenAddr != listenAddr {
			if !ListenTest(c.Listen.ListenAddr) {
				return
			}
		}
		data, status, err := httpclient.HTTPPost("http://"+g_http_address+"/reload", map[string]string{"command": command})
		if err != nil || status != http.StatusOK {
			return
		}
		fmt.Println(string(data))
	case "start":
		_, ok := checkRunning()
		if ok {
			log.Error("proxy_gate is running")
			return
		}
		c, ok := ReloadConfig(CONF_PATH)
		if !ok {
			return
		}

		if !ListenTest(c.Listen.ListenAddr) {
			return
		}

		start(c)
	case "stop":
		_, ok := checkRunning()
		if !ok {
			log.Error("proxy_gate is not running")
			return
		}
		data, status, err := httpclient.HTTPPost("http://"+g_http_address+"/stop", map[string]string{"command": command})
		if err != nil || status != http.StatusOK {
			return
		}
		fmt.Println(string(data))
	}
}

func start(c *GlobalConfig) {
	ipContainer := NewIPContainer(c.LimitInfo)
	proxyInfo := newProxyInfo(c.Proxy)
	p := newProxy(c.Listen.NewPacket, c.Listen.ListenAddr, c.Listen.PrivateKey, ipContainer, proxyInfo)
	go p.Run()

	http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
		writer.Write([]byte(c.Listen.ListenAddr))
	})
	http.HandleFunc("/reload", func(writer http.ResponseWriter, request *http.Request) {
		newConfig, ok := ReloadConfig(CONF_PATH)
		if !ok {
			writer.Write([]byte("reload config failed,please look on error.log"))
			return
		}
		ipContainer.ResetConfig(newConfig.LimitInfo)
		proxyInfo.ResetAddr(newConfig.Proxy)
		if c.Listen.ListenAddr != newConfig.Listen.ListenAddr {
			if !ListenTest(newConfig.Listen.ListenAddr) {
				writer.Write([]byte("listen failed of " + newConfig.Listen.ListenAddr))
				return
			}
			p.ReListen(newConfig.Listen.ListenAddr)
		}
		p.SetNewPacket(newConfig.Listen.NewPacket)
		c = newConfig
		writer.Write([]byte("reload success"))
		log.Info("reload success", zap.Any("config", newConfig))
		return
	})
	http.HandleFunc("/stop", func(writer http.ResponseWriter, request *http.Request) {
		p.acceptor.Close()
		writer.Write([]byte("stop success"))
		log.Info("proxy exit by command stop")
		log.CustomFlush()
		os.Exit(1)
	})
	fmt.Println(http.ListenAndServe(g_http_address, nil))
}
