package lightning

import (
	"context"
	"os"
	"path/filepath"
	"strings"

	"github.com/pingcap/errors"
)

type localObjectLister struct {
	base string
}

// ListObjects list objects for a local data source, and returns an iterator.
// It implements the ObjectLister interface.
func (l *localObjectLister) ListObjects(ctx context.Context, req *ListRequest) (ListResultIterator, error) {
	objInfoCh := make(chan *localObjInfo, 1000)
	go func() {
		defer close(objInfoCh)
		if err := filepath.Walk(l.base, func(path string, f os.FileInfo, err error) error {
			if os.IsNotExist(err) {
				// if path not exists, we should return nil to continue.
				return nil
			}
			if err != nil {
				return errors.Trace(err)
			}

			if f == nil || f.IsDir() {
				return nil
			}
			// in mac osx, the path parameter is absolute path; in linux, the path is relative path to execution base dir,
			// so use Rel to convert to relative path to l.base
			path, _ = filepath.Rel(l.base, path)

			if strings.Compare(path, req.StartOffsetKey) <= 0 {
				return nil
			}

			size := f.Size()
			// if not a regular file, we need to use os.stat to get the real file size
			if !f.Mode().IsRegular() {
				stat, err := os.Stat(filepath.Join(l.base, path))
				if err != nil {
					return errors.Trace(err)
				}
				size = stat.Size()
			}
			objInfoCh <- &localObjInfo{
				Info: &ObjectInfo{
					Name:   path,
					Size:   size,
					Prefix: "",
				},
			}
			return nil
		}); err != nil {
			// send the error to the objInfoCh
			objInfoCh <- &localObjInfo{Err: err}
		}
	}()
	return &localListResultIterator{
		objInfoCh: objInfoCh,
	}, nil
}

type localObjInfo struct {
	Info *ObjectInfo
	Err  error
}

type localListResultIterator struct {
	objInfoCh <-chan *localObjInfo
}

// Next retrieves one object and ready to fetch the next.
// It implements the ListResultIterator interface.
func (iter *localListResultIterator) Next() (*ObjectInfo, error) {
	objInfo, ok := <-iter.objInfoCh
	if !ok {
		return nil, errListDone
	}
	if objInfo.Err != nil {
		return nil, objInfo.Err
	}
	return objInfo.Info, nil
}
