package main

import (
	"io/ioutil"
	"net"
	"net/http"
	_ "net/http/pprof"
	"os"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"webuploader/static"

	"github.com/gin-gonic/gin"
	"github.com/quillaja/logrus-systemd-formatter/systemdfmt"
	log "github.com/sirupsen/logrus"
	"github.com/urfave/cli/v2"
)

var (
	dir    string = getCurrentAbPathByExecutable() + string(filepath.Separator)
	port   string
	group1 *gin.RouterGroup
)

func init() {

	gin.SetMode(gin.ReleaseMode)

	log.SetFormatter(&systemdfmt.Formatter{})
	log.SetOutput(os.Stdout)
	//log.SetLevel(log.WarnLevel)

	//pprof
	go func() {
		_ = http.ListenAndServe(":6066", nil)
	}()

}

// 获取当前执行文件绝对路径
func getCurrentAbPathByExecutable() string {
	exePath, err := os.Executable()
	if err != nil {
		log.Fatal(err)
	}
	res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
	return res
}

func getClientIp() {
	addrs, err := net.InterfaceAddrs()

	if err != nil {
		log.Errorf("Error getClientIp %v", err)
		return
	}

	for _, address := range addrs {
		if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
			if ipnet.IP.To4() != nil {
				//return ipnet.IP.String(), nil
				log.Infof("HTTP://" + ipnet.IP.String() + ":" + port)
			}
		}
	}
}

func walkDir(filestr string) ([]string, error) {
	files, err := ioutil.ReadDir(filestr) // files为当前目录下的所有文件名称【包括文件夹】
	if err != nil {
		return nil, err
	}

	var allfile []string
	for _, v := range files {
		if v.Name() == ".DS_Store" {
			continue
		}
		fullPath := filestr + string(filepath.Separator) + v.Name() // 全路径 + 文件名称
		if v.IsDir() {                                              // 如果是目录
			a, _ := walkDir(fullPath) // 遍历改路径下的所有文件
			allfile = append(allfile, a...)
		} else {
			allfile = append(allfile, fullPath) // 如果不是文件夹,就直接追加到路径下
		}
	}

	return allfile, nil
}

// 验证是否上传过该文件
func check(c *gin.Context) {
	fileMd5 := c.PostForm("md5File")
	path := dir + "wisemodel" + string(filepath.Separator) + "merge" + string(filepath.Separator) + fileMd5
	if _, err := os.Stat(path); os.IsNotExist(err) { //判断是否已经上传过该文件,设置返回code 为 -1 :秒传
		log.Println("mkDir " + path)
		_ = os.MkdirAll(path, os.ModePerm)
		c.JSON(http.StatusOK, gin.H{
			"resultCode": 0, //
		})
		return
	}
	result := 0
	// 获取目录里上传文件数量
	files, err := ioutil.ReadDir(path)
	if err != nil {
		log.Infof("Error ioutil.ReadDir %v", err)
	} else if len(files) == 0 { //文件没有上传过,下标为零
		result = 0
	} else {
		result = 1
	}
	c.JSON(http.StatusOK, gin.H{
		"resultCode": result,
	})
}

// 验证是否上传过该文件
func checkChunk(c *gin.Context) {
	fileMd5 := c.PostForm("md5File")
	chunk := c.PostForm("chunk")
	log.Println(fileMd5)
	path := dir + "wisemodel" + string(filepath.Separator) + fileMd5 + string(filepath.Separator) + strings.Trim(chunk, "")
	log.Println(path)
	if _, err := os.Stat(path); err == nil { //判断是否已经上传过该文件,设置返回code 为 -1 :秒传
		c.JSON(http.StatusOK, gin.H{
			"resultCode": 1, //
		})
		return
	} else {
		c.JSON(http.StatusOK, gin.H{
			"resultCode": 0,
		})
	}
}

// 上传文件分片
func upload(c *gin.Context) {
	fileName := c.PostForm("name")                      // 获取文件名
	chunkIndex := strings.Trim(c.PostForm("chunk"), "") // 获取分片下标

	fileMd5 := c.PostForm("md5File") //获取文件md5
	dst := dir + "wisemodel" + string(filepath.Separator) + fileMd5
	if chunkIndex == "" {
		chunkIndex = "0"
	}

	result := 0
	defer func(subResult *int) {
		c.JSON(http.StatusOK, gin.H{
			"resultCode": *subResult, //
		})
	}(&result)

	if _, err := os.Stat(dst); os.IsNotExist(err) { //判断是否存在
		_ = os.MkdirAll(dst, os.ModePerm)
	}
	if file, err := c.FormFile("file"); err == nil { //获取分片文件
		if chunkIndex == "" {
			err = c.SaveUploadedFile(file, dst+string(filepath.Separator)+fileName)
		} else {
			err = c.SaveUploadedFile(file, dst+string(filepath.Separator)+chunkIndex)
		}
		if err != nil {
			result = 0
			log.Errorf("Error c.SaveUploadedFile %v", err)
		} else {
			result = 1
		}
	} else {
		result = 0
		log.Errorf("Error c.FormFile %v", err)
	}
}

// 合并分片
func merge(c *gin.Context) {
	fileName := c.PostForm("name")
	fileMd5 := c.PostForm("md5File")
	chunks := c.PostForm("chunks")
	path := dir + "wisemodel" + string(filepath.Separator) + fileMd5 //分片存储位置

	result := 0
	defer func(subResult *int) {
		c.JSON(http.StatusOK, gin.H{
			"resultCode": *subResult, //
		})
	}(&result)

	// 获取目录里上传文件分片数量
	files, err := ioutil.ReadDir(path)
	if err != nil || len(files) == 0 || chunks != strconv.Itoa(len(files)) {
		log.Errorf("Error merge ioutil.ReadDir %v", err)
		result = 0
		return
	}

	//按名字排序
	sort.SliceStable(files, func(i, j int) bool {
		numA, _ := strconv.Atoi(files[i].Name())
		numB, _ := strconv.Atoi(files[j].Name())
		return numA < numB
		//return files[i].Name() < files[j].Name()
	})

	realFilePath := dir + "wisemodel" + string(filepath.Separator) + "merge" + string(filepath.Separator) + fileMd5
	_ = os.MkdirAll(realFilePath, os.ModePerm) //创建目录
	// 创建一个需要合并的文件
	realFile, err := os.OpenFile(realFilePath+string(filepath.Separator)+fileName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
	defer realFile.Close()
	if err != nil {
		result = 0
		log.Errorf("Error merge os.OpenFile %v", err)
		return
	}

	for _, v := range files { //
		f, err := os.OpenFile(path+string(filepath.Separator)+v.Name(), os.O_RDONLY, os.ModePerm)
		if err != nil {
			result = 0
			log.Errorf("Error for os.OpenFile %v", err)
			break
		}
		b, err := ioutil.ReadAll(f)
		if err != nil {
			result = 0
			log.Errorf("Error for ioutil.ReadAll %v", err)
			break
		}
		realFile.Write(b)
		// 关闭分片
		f.Close()
		os.Remove(f.Name()) //合并后,删除分片
		result = 1
	}
}

// 文件list
func getFiles(c *gin.Context) {
	pathMerge := dir + "wisemodel" + string(filepath.Separator) + "merge" //上传文件存储位置
	pathLocal := dir + "wisemodel" + string(filepath.Separator) + "local" //本地文件存储位置

	if _, err := os.Stat(pathLocal); os.IsNotExist(err) {
		_ = os.MkdirAll(pathLocal, os.ModePerm)
	}

	mergeFiles, err := walkDir(pathMerge)
	if err != nil {
		log.Errorf("Error walkDir(pathMerge) %v", err)
	}

	localFiles, err := walkDir(pathLocal)
	if err != nil {
		log.Errorf("Error walkDir(pathMerge) %v", err)
	}

	c.JSON(http.StatusOK, gin.H{
		"mergeFiles": mergeFiles,
		"localFiles": localFiles,
	})

}

func doWork() {
	getClientIp()

	r := gin.Default()
	//加载静态文件
	r.StaticFS("/static", http.FS(static.FS))

	// 最大文件大小M
	r.MaxMultipartMemory = 8 << 20

	// 跳转上传页面
	r.GET("/", func(c *gin.Context) {
		c.Redirect(http.StatusMovedPermanently, "/static/upload.html")
	})

	//路由分组
	group1 = r.Group("/bigfile")

	group1.POST("/check", check)
	group1.POST("/checkChunk", checkChunk)
	group1.POST("/upload", upload)
	group1.POST("/merge", merge)
	group1.GET("/getFiles", getFiles)

	group1.GET("/down", func(c *gin.Context) {
		pathName := c.DefaultQuery("path", "")
		fileName := filepath.Base(pathName)
		if pathName != "" {
			c.Header("Content-Type", "application/octet-stream")
			c.Header("Content-Disposition", "attachment; filename="+fileName)
			c.Header("Content-Disposition", "inline;filename="+fileName)
			c.Header("Content-Transfer-Encoding", "binary")
			c.Header("Cache-Control", "no-cache")

			c.File(pathName)
		}
	})

	_ = r.Run(":" + port)
}

func main() {

	app := &cli.App{
		Name:  "webuploader", //app名字
		Usage: "文件服务",        //详细描述该app的用途

		// 设置参数列表
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:        "port",
				Value:       "8888",
				Aliases:     []string{"p"},
				Usage:       "port",
				Destination: &port,
			},
		},

		Action: func(context *cli.Context) error {
			doWork()
			return nil
		},
	}

	if err := app.Run(os.Args); err != nil {
		log.Errorln("app run error: ", err.Error())
		os.Exit(1)
	}
}