package file

import (
	"errors"
	"fmt"
	"os"
	"path"
	"path/filepath"
	"strings"

	"be.ems/lib/file"
	"github.com/spf13/afero"
)

// 获取文件列表
func GetFileList(op FileOption) (FileInfo, error) {
	var fileInfo FileInfo
	if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) {
		return fileInfo, nil
	}
	info, err := NewFileInfo(op)
	if err != nil {
		return fileInfo, err
	}
	fileInfo = *info
	return fileInfo, nil
}

func NewFileInfo(op FileOption) (*FileInfo, error) {
	var appFs = afero.NewOsFs()

	info, err := appFs.Stat(op.Path)
	if err != nil {
		return nil, err
	}

	fileInfo := &FileInfo{
		Fs:        appFs,
		Path:      op.Path,
		Name:      info.Name(),
		IsDir:     info.IsDir(),
		FileMode:  info.Mode(),
		ModTime:   info.ModTime(),
		Size:      info.Size(),
		IsSymlink: file.IsSymlink(info.Mode()),
		Extension: filepath.Ext(info.Name()),
		IsHidden:  file.IsHidden(op.Path),
		Mode:      fmt.Sprintf("%04o", info.Mode().Perm()),
		MimeType:  file.GetMimeType(op.Path),
	}
	if fileInfo.IsSymlink {
		fileInfo.LinkPath = file.GetSymlink(op.Path)
	}
	if op.Expand {
		if fileInfo.IsDir {
			if err := listChildren(fileInfo, op.Dir, op.ShowHidden, op.ContainSub, op.Search, op.Page, op.PageSize); err != nil {
				return nil, err
			}
			return fileInfo, nil
		} else {
			if err := getContent(fileInfo); err != nil {
				return nil, err
			}
		}
	}
	return fileInfo, nil
}

func listChildren(f *FileInfo, dir, showHidden, containSub bool, search string, page, pageSize int) error {
	afs := &afero.Afero{Fs: f.Fs}
	var (
		files []FileSearchInfo
		err   error
		total int
	)

	if search != "" && containSub {
		files, total, err = f.search(search, page*pageSize)
		if err != nil {
			return err
		}
	} else {
		dirFiles, err := afs.ReadDir(f.Path)
		if err != nil {
			return err
		}
		for _, file := range dirFiles {
			files = append(files, FileSearchInfo{
				Path:     f.Path,
				FileInfo: file,
			})
		}
	}

	var items []*FileInfo
	for _, df := range files {
		if dir && !df.IsDir() {
			continue
		}
		name := df.Name()
		fPath := path.Join(df.Path, df.Name())
		if search != "" {
			if containSub {
				fPath = df.Path
				name = strings.TrimPrefix(strings.TrimPrefix(fPath, f.Path), "/")
			} else {
				lowerName := strings.ToLower(name)
				lowerSearch := strings.ToLower(search)
				if !strings.Contains(lowerName, lowerSearch) {
					continue
				}
			}
		}
		if !showHidden && file.IsHidden(name) {
			continue
		}
		f.ItemTotal++
		isSymlink, isInvalidLink := false, false
		if file.IsSymlink(df.Mode()) {
			isSymlink = true
			info, err := f.Fs.Stat(fPath)
			if err == nil {
				df.FileInfo = info
			} else {
				isInvalidLink = true
			}
		}

		fileInfo := &FileInfo{
			Fs:        f.Fs,
			Name:      name,
			Size:      df.Size(),
			ModTime:   df.ModTime(),
			FileMode:  df.Mode(),
			IsDir:     df.IsDir(),
			IsSymlink: isSymlink,
			IsHidden:  file.IsHidden(fPath),
			Extension: filepath.Ext(name),
			Path:      fPath,
			Mode:      fmt.Sprintf("%04o", df.Mode().Perm()),
		}

		if isSymlink {
			fileInfo.LinkPath = file.GetSymlink(fPath)
		}
		if df.Size() > 0 {
			fileInfo.MimeType = file.GetMimeType(fPath)
		}
		if isInvalidLink {
			fileInfo.Type = "invalid_link"
		}
		items = append(items, fileInfo)
	}
	if containSub {
		f.ItemTotal = total
	}
	start := (page - 1) * pageSize
	end := pageSize + start
	var result []*FileInfo
	if start < 0 || start > f.ItemTotal || end < 0 || start > end {
		result = items
	} else {
		if end > f.ItemTotal {
			result = items[start:]
		} else {
			result = items[start:end]
		}
	}

	f.Items = result
	return nil
}

func getContent(f *FileInfo) error {
	if f.Size <= 10*1024*1024 {
		afs := &afero.Afero{Fs: f.Fs}
		cByte, err := afs.ReadFile(f.Path)
		if err != nil {
			return nil
		}
		if len(cByte) > 0 && detectBinary(cByte) {
			return errors.New("ErrFileCanNotRead")
		}
		f.Content = string(cByte)
		return nil
	} else {
		return errors.New("ErrFileCanNotRead")
	}
}

func detectBinary(buf []byte) bool {
	whiteByte := 0
	n := 1024
	if len(buf) < 1024 {
		n = len(buf)
	}
	for i := 0; i < n; i++ {
		if (buf[i] >= 0x20) || buf[i] == 9 || buf[i] == 10 || buf[i] == 13 {
			whiteByte++
		} else if buf[i] <= 6 || (buf[i] >= 14 && buf[i] <= 31) {
			return true
		}
	}

	return whiteByte < 1
}
