package main

import (
	"fmt"
	"github.com/gobs/args"
	"github.com/iikira/BaiduPCS-Go/command"
	"github.com/iikira/BaiduPCS-Go/config"
	"github.com/kardianos/osext"
	"github.com/peterh/liner"
	"github.com/urfave/cli"
	"io"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
)

var (
	historyFile = "pcs_command_history.txt"

	reloadFn = func(c *cli.Context) error {
		baidupcscmd.ReloadIfInConsole()
		return nil
	}
)

func init() {
	// change work directory
	folderPath, err := osext.ExecutableFolder()
	if err != nil {
		folderPath, err = filepath.Abs(filepath.Dir(os.Args[0]))
		if err != nil {
			folderPath = filepath.Dir(os.Args[0])
		}
	}
	os.Chdir(folderPath)

	pcsconfig.Init()
}

func main() {
	app := cli.NewApp()
	app.Name = "BaiduPCS-Go"
	app.Version = "beta-v2"
	app.Author = "iikira/BaiduPCS-Go: https://github.com/iikira/BaiduPCS-Go"
	app.Usage = "百度网盘工具箱 for " + runtime.GOOS + "/" + runtime.GOARCH
	app.Description = `BaiduPCS-Go 使用 Go语言编写, 为操作百度网盘, 提供实用功能.
	具体功能, 参见 COMMANDS 列表

	特色:
		网盘内列出文件和目录, 支持通配符匹配路径;
		下载网盘内文件, 支持网盘内目录 (文件夹) 下载, 支持多个文件或目录下载, 支持断点续传和高并发高速下载.

	程序目前处于测试版, 后续会添加更多的实用功能.`
	app.Action = func(c *cli.Context) {
		if c.NArg() == 0 {
			cli.ShowAppHelp(c)

			line := newLiner()
			defer closeLiner(line)

			for {
				if commandLine, err := line.Prompt("BaiduPCS-Go > "); err == nil {
					line.AppendHistory(commandLine)

					cmdArgs := args.GetArgs(commandLine)
					if len(cmdArgs) == 0 {
						continue
					}

					s := []string{os.Args[0]}
					s = append(s, cmdArgs...)

					closeLiner(line)

					c.App.Run(s)

					line = newLiner()

				} else if err == liner.ErrPromptAborted || err == io.EOF {
					break
				} else {
					log.Print("Error reading line: ", err)
					continue
				}
			}
		} else {
			fmt.Printf("未找到命令: %s\n运行命令 %s help 获取帮助\n", c.Args().Get(0), app.Name)
		}
	}

	app.Commands = []cli.Command{
		{
			Name:  "login",
			Usage: "使用百度BDUSS登录百度账号",
			Description: fmt.Sprintf("\n   示例: \n\n      %s\n\n      %s\n\n   %s\n\n      %s\n\n      %s\n",
				filepath.Base(os.Args[0])+" login --bduss=123456789",
				filepath.Base(os.Args[0])+" login",
				"百度BDUSS获取方法: ",
				"参考这篇 Wiki: https://github.com/iikira/BaiduPCS-Go/wiki/关于-获取百度-BDUSS",
				"或者百度搜索: 获取百度BDUSS",
			),
			Category: "百度帐号操作",
			Before:   reloadFn,
			After:    reloadFn,
			Action: func(c *cli.Context) error {
				bduss := ""
				if c.IsSet("bduss") {
					bduss = c.String("bduss")
				} else if c.NArg() == 0 {
					cli.ShowCommandHelp(c, c.Command.Name)
					line := liner.NewLiner()
					line.SetCtrlCAborts(true)
					defer line.Close()
					bduss, _ = line.Prompt("请输入百度BDUSS值, 回车键提交 > ")
				} else {
					cli.ShowCommandHelp(c, c.Command.Name)
					return nil
				}

				username, err := pcsconfig.Config.SetBDUSS(bduss)
				if err != nil {
					fmt.Println(err)
					return nil
				}

				fmt.Println("百度帐号登录成功:", username)
				return nil
			},
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "bduss",
					Usage: "百度BDUSS",
				},
			},
		},
		{
			Name:  "chuser",
			Usage: "切换已登录的百度帐号",
			Description: fmt.Sprintf("%s\n   示例:\n\n      %s\n      %s\n",
				"如果运行该条命令没有提供参数, 程序将会列出所有的百度帐号, 供选择切换",
				filepath.Base(os.Args[0])+" chuser --uid=123456789",
				filepath.Base(os.Args[0])+" chuser",
			),
			Category: "百度帐号操作",
			Before:   reloadFn,
			After:    reloadFn,
			Action: func(c *cli.Context) error {
				if len(pcsconfig.Config.BaiduUserList) == 0 {
					fmt.Println("未设置任何百度帐号, 不能切换")
					return nil
				}

				var uid uint64
				if c.IsSet("uid") {
					if pcsconfig.Config.CheckUIDExist(c.Uint64("uid")) {
						uid = c.Uint64("uid")
					} else {
						fmt.Println("切换用户失败, uid 不存在")
					}
				} else if c.NArg() == 0 {
					cli.HandleAction(app.Command("loglist").Action, c)

					line := liner.NewLiner()
					line.SetCtrlCAborts(true)
					defer line.Close()
					nLine, _ := line.Prompt("请输入要切换帐号的 index 值 > ")

					if n, err := strconv.Atoi(nLine); err == nil && n >= 0 && n < len(pcsconfig.Config.BaiduUserList) {
						uid = pcsconfig.Config.BaiduUserList[n].UID
					} else {
						fmt.Println("切换用户失败, 请检查 index 值是否正确")
					}
				} else {
					cli.ShowCommandHelp(c, c.Command.Name)
				}

				if uid == 0 {
					return nil
				}

				pcsconfig.Config.BaiduActiveUID = uid
				if err := pcsconfig.Config.Save(); err != nil {
					fmt.Println(err)
					return nil
				}

				fmt.Printf("切换用户成功, %v\n", pcsconfig.ActiveBaiduUser.Name)
				return nil

			},
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "uid",
					Usage: "百度帐号 uid 值",
				},
			},
		},
		{
			Name:  "logout",
			Usage: "退出已登录的百度帐号",
			Description: fmt.Sprintf("%s\n   示例:\n\n      %s\n      %s\n",
				"如果运行该条命令没有提供参数, 程序将会列出所有的百度帐号, 供选择退出",
				filepath.Base(os.Args[0])+" logout --uid=123456789",
				filepath.Base(os.Args[0])+" logout",
			),
			Category: "百度帐号操作",
			Before:   reloadFn,
			After:    reloadFn,
			Action: func(c *cli.Context) error {
				if len(pcsconfig.Config.BaiduUserList) == 0 {
					fmt.Println("未设置任何百度帐号, 不能退出")
					return nil
				}

				var uid uint64
				if c.IsSet("uid") {
					if pcsconfig.Config.CheckUIDExist(c.Uint64("uid")) {
						uid = c.Uint64("uid")
					} else {
						fmt.Println("退出用户失败, uid 不存在")
					}
				} else if c.NArg() == 0 {
					cli.HandleAction(app.Command("loglist").Action, c)

					line := liner.NewLiner()
					line.SetCtrlCAborts(true)
					defer line.Close()
					nLine, _ := line.Prompt("请输入要退出帐号的 index 值 > ")

					if n, err := strconv.Atoi(nLine); err == nil && n >= 0 && n < len(pcsconfig.Config.BaiduUserList) {
						uid = pcsconfig.Config.BaiduUserList[n].UID
					} else {
						fmt.Println("退出用户失败, 请检查 index 值是否正确")
					}
				} else {
					cli.ShowCommandHelp(c, c.Command.Name)
				}

				if uid == 0 {
					return nil
				}

				// 删除之前先获取被删除的数据, 用于下文输出日志
				baidu, err := pcsconfig.Config.GetBaiduUserByUID(uid)
				if err != nil {
					fmt.Println(err)
					return nil
				}

				if !pcsconfig.Config.DeleteBaiduUserByUID(uid) {
					fmt.Printf("退出用户失败, %s\n", baidu.Name)
				}

				fmt.Printf("退出用户成功, %v\n", baidu.Name)
				return nil
			},
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "uid",
					Usage: "百度帐号 uid 值",
				},
			},
		},
		{
			Name:     "loglist",
			Usage:    "获取当前帐号, 和所有已登录的百度帐号",
			Category: "百度帐号操作",
			Before:   reloadFn,
			Action: func(c *cli.Context) error {
				fmt.Printf("\n当前帐号 uid: %d, 用户名: %s\n", pcsconfig.ActiveBaiduUser.UID, pcsconfig.ActiveBaiduUser.Name)
				fmt.Println(pcsconfig.Config.GetAllBaiduUser())
				return nil
			},
		},
		{
			Name:     "quota",
			Usage:    "获取配额, 即获取网盘总空间, 和已使用空间",
			Category: "网盘操作",
			Before:   reloadFn,
			Action: func(c *cli.Context) error {
				baidupcscmd.RunGetQuota()
				return nil
			},
		},
		{
			Name:      "cd",
			Usage:     "切换工作目录",
			UsageText: fmt.Sprintf("%s cd <目录 绝对路径或相对路径>", filepath.Base(os.Args[0])),
			Category:  "网盘操作",
			Before:    reloadFn,
			After:     reloadFn,
			Action: func(c *cli.Context) error {
				if c.NArg() == 0 {
					cli.ShowCommandHelp(c, c.Command.Name)
					return nil
				}
				baidupcscmd.RunChangeDirectory(c.Args().Get(0))
				return nil
			},
		},
		{
			Name:      "ls",
			Aliases:   []string{"l", "ll"},
			Usage:     "列出当前工作目录内的文件和目录 或 指定目录内的文件和目录",
			UsageText: fmt.Sprintf("%s ls <目录 绝对路径或相对路径>", filepath.Base(os.Args[0])),
			Category:  "网盘操作",
			Before:    reloadFn,
			Action: func(c *cli.Context) error {
				baidupcscmd.RunLs(c.Args().Get(0))
				return nil
			},
		},
		{
			Name:      "pwd",
			Usage:     "输出当前所在目录 (工作目录)",
			UsageText: fmt.Sprintf("%s pwd", filepath.Base(os.Args[0])),
			Category:  "网盘操作",
			Before:    reloadFn,
			Action: func(c *cli.Context) error {
				fmt.Println(pcsconfig.ActiveBaiduUser.Workdir)
				return nil
			},
		},
		{
			Name:      "meta",
			Usage:     "获取单个文件/目录的元信息 (详细信息)",
			UsageText: fmt.Sprintf("%s meta <文件/目录 绝对路径或相对路径>", filepath.Base(os.Args[0])),
			Category:  "网盘操作",
			Before:    reloadFn,
			Action: func(c *cli.Context) error {
				baidupcscmd.RunGetMeta(c.Args().Get(0))
				return nil
			},
		},
		{
			Name:      "rm",
			Usage:     "删除 单个/多个 文件/目录",
			UsageText: fmt.Sprintf("%s rm <网盘文件或目录的路径1> <文件或目录2> <文件或目录3> ...", filepath.Base(os.Args[0])),
			Description: fmt.Sprintf("\n   %s\n",
				"注意: 删除多个文件和目录时, 请确保每一个文件和目录都存在, 否则删除操作会失败.",
			),
			Category: "网盘操作",
			Before:   reloadFn,
			Action: func(c *cli.Context) error {
				if c.NArg() == 0 {
					cli.ShowCommandHelp(c, c.Command.Name)
					return nil
				}

				var dargs []string
				for i := 0; c.Args().Get(i) != ""; i++ {
					dargs = append(dargs, c.Args().Get(i))
				}
				baidupcscmd.RunRemove(dargs...)
				return nil
			},
		},
		{
			Name:      "mkdir",
			Usage:     "创建目录",
			UsageText: fmt.Sprintf("%s mkdir <目录 绝对路径或相对路径> ...", filepath.Base(os.Args[0])),
			Category:  "网盘操作",
			Before:    reloadFn,
			Action: func(c *cli.Context) error {
				if c.NArg() == 0 {
					cli.ShowCommandHelp(c, c.Command.Name)
					return nil
				}

				baidupcscmd.RunMkdir(c.Args().Get(0))
				return nil
			},
		},
		{
			Name:        "download",
			Aliases:     []string{"d"},
			Usage:       "下载文件或目录",
			UsageText:   fmt.Sprintf("%s download <网盘文件或目录的路径1> <文件或目录2> <文件或目录3> ...", filepath.Base(os.Args[0])),
			Description: "下载的文件将会保存到, 程序所在目录的 download/ 目录 (文件夹).\n   已支持目录下载.\n   已支持多个文件或目录下载.\n   自动跳过下载重名的文件! \n",
			Category:    "网盘操作",
			Before:      reloadFn,
			Action: func(c *cli.Context) error {
				if c.NArg() == 0 {
					cli.ShowCommandHelp(c, c.Command.Name)
					return nil
				}

				var dargs []string
				for i := 0; c.Args().Get(i) != ""; i++ {
					dargs = append(dargs, c.Args().Get(i))
				}
				baidupcscmd.RunDownload(dargs...)
				return nil
			},
		},
		{
			Name:      "set",
			Usage:     "设置配置",
			UsageText: fmt.Sprintf("%s set OptionName Value", filepath.Base(os.Args[0])),
			Description: `
可设置的值:

	OptionName		Value
	------------------
	max_parallel	下载最大线程 (并发量) - 建议值 ( 100 ~ 500 )

例子:

	set max_parallel 250
`,
			Category: "配置",
			Before:   reloadFn,
			After:    reloadFn,
			Action: func(c *cli.Context) error {
				if c.NArg() < 2 {
					cli.ShowCommandHelp(c, c.Command.Name)
					return nil
				}

				switch c.Args().Get(0) {
				case "max_parallel":
					parallel, err := strconv.Atoi(c.Args().Get(1))
					if err != nil {
						return cli.NewExitError(fmt.Errorf("max_parallel 设置值不合法, 错误: %s", err), 1)
					}

					pcsconfig.Config.MaxParallel = parallel
					err = pcsconfig.Config.Save()
					if err != nil {
						fmt.Println("设置失败, 错误:", err)
						return nil
					}
					fmt.Printf("设置成功, %s -> %v\n", c.Args().Get(0), c.Args().Get(1))

				default:
					fmt.Printf("未知设定值: %s\n\n", c.Args().Get(0))
					cli.ShowCommandHelp(c, "set")
				}
				return nil
			},
		},
		{
			Name:    "quit",
			Aliases: []string{"exit"},
			Usage:   "退出程序",
			Action: func(c *cli.Context) error {
				return cli.NewExitError("", 0)
			},
			Hidden:   true,
			HideHelp: true,
		},
	}

	sort.Sort(cli.FlagsByName(app.Flags))
	sort.Sort(cli.CommandsByName(app.Commands))

	app.Run(os.Args)
}

func newLiner() *liner.State {
	line := liner.NewLiner()

	line.SetCtrlCAborts(true)

	if f, err := os.Open(historyFile); err == nil {
		line.ReadHistory(f)
		f.Close()
	}

	return line
}

func closeLiner(line *liner.State) {
	if f, err := os.Create(historyFile); err != nil {
		log.Print("Error writing history file: ", err)
	} else {
		line.WriteHistory(f)
		f.Close()
	}
	line.Close()
}
