package fileutils

import (
	"archive/zip"
	"bytes"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
)

// PathExists 检查对应路径的文件或目录是否存在
// 参数:
// path: 文件或目录的路径
// 返回值:
// 对应路径的文件或目录是否存在
func PathExists(path string) bool {
	_, err := os.Stat(path)
	if err == nil {
		return true
	}

	if os.IsNotExist(err) {
		return false
	}

	return false
}

// GetDirFiles 获取目录下的文件
// 参数
// dir: 目录的路径
// 返回值:
// 目录下的文件路径(用dir拼接,dir是绝对路径就是绝对路径,dir是相对路径就是相对路径)和错误
func GetDirFiles(dir string) ([]string, error) {
	dirList, err := os.ReadDir(dir)
	if err != nil {
		return nil, err
	}

	filesRet := make([]string, 0)

	for _, file := range dirList {
		if file.IsDir() {
			files, err := GetDirFiles(filepath.Join(dir, file.Name()))
			if err != nil {
				return nil, err
			}

			filesRet = append(filesRet, files...)
		} else {
			filesRet = append(filesRet, filepath.Join(dir, file.Name()))
		}
	}

	return filesRet, nil
}

// ReadFileWithBuffer 带缓冲读文件,一般用于大文件读取
// 参数
// filePath: 文件的路径
// bufferSize: 缓冲大小,每次最多读取的字节数
// readCallback: 每次读取回调
// 返回值:
// 错误
func ReadFileWithBuffer(filePath string, bufferSize int, readCallback func(b []byte)) error {
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}

	for {
		buffer := make([]byte, bufferSize)
		readSize, err := file.Read(buffer)
		if err != nil && err != io.EOF {
			return err
		}

		if readCallback != nil {
			readCallback(buffer[:readSize])
		}

		if err != nil && err == io.EOF {
			break
		}
	}

	return nil
}

// ZipDir zip压缩目录
// 参数
// dirPath: 目录的路径
// savePath: 生成zip文件的路径
// 返回值:
// 错误
func ZipDir(dirPath string, savePath string) error {
	archive, err := os.Create(savePath)
	if err != nil {
		return err
	}

	defer func(archive *os.File) {
		err := archive.Close()
		if err != nil {
			log.Println(err)
			return
		}
	}(archive)

	zipWriter := zip.NewWriter(archive)

	defer func(zipWriter *zip.Writer) {
		err := zipWriter.Close()
		if err != nil {
			log.Println(err)
			return
		}
	}(zipWriter)

	err = filepath.Walk(dirPath,
		func(path string, f os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if f.IsDir() {
				return nil
			}

			zipPath := strings.TrimPrefix(path, dirPath)
			err = writeZipFile(path, zipPath, zipWriter)
			if err != nil {
				return err
			}

			return nil
		})
	if err != nil {
		return err
	}

	return nil
}

// UnzipFile 解压缩zip文件
// 参数
// zipFilePath: zip文件路径
// destDir: 解压缩的目的目录
// 返回值:
// 错误
func UnzipFile(zipFilePath string, destDir string) error {
	srcFile, err := os.Open(zipFilePath)
	if err != nil {
		return err
	}

	defer func() {
		err := srcFile.Close()
		if err != nil {
			log.Println(err)
			return
		}
	}()

	srcFileInfo, err := srcFile.Stat()
	if err != nil {
		return err
	}

	return unzip(srcFile, srcFileInfo.Size(), destDir)
}

// UnzipBytes 解压zip文件
// 参数
// zipFileBytes: zip文件的字节内容
// destDir: 解压缩的目的目录
// 返回值:
// 错误
func UnzipBytes(zipFileBytes []byte, destDir string) error {
	return unzip(bytes.NewReader(zipFileBytes), int64(len(zipFileBytes)), destDir)
}

func writeZipFile(filePath string, zipPath string, zipWriter *zip.Writer) error {
	file, err := os.Open(filePath)
	if err != nil {
		return err
	}

	defer func() {
		err := file.Close()
		if err != nil {
			log.Println(err)
			return
		}
	}()

	writer, err := zipWriter.Create(zipPath)
	if err != nil {
		return err
	}

	_, err = io.Copy(writer, file)
	if err != nil {
		return err
	}

	return nil
}

func unzip(r io.ReaderAt, size int64, destDir string) error {
	zipReader, err := zip.NewReader(r, size)
	if err != nil {
		return err
	}

	for _, f := range zipReader.File {
		fPath := filepath.Join(destDir, f.Name)
		if f.FileInfo().IsDir() {
			if err := os.MkdirAll(fPath, os.ModePerm); err != nil {
				return err
			}
		} else {
			if err := os.MkdirAll(filepath.Dir(fPath), os.ModePerm); err != nil {
				return err
			}

			inFile, err := f.Open()
			if err != nil {
				return err
			}

			outFile, err := os.OpenFile(fPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
			if err != nil {
				return err
			}

			if _, err = io.Copy(outFile, inFile); err != nil {
				return err
			}

			if err = inFile.Close(); err != nil {
				return err
			}

			if err = outFile.Close(); err != nil {
				return err
			}
		}
	}

	return nil
}