//go:build windows
// +build windows

package godirwalk

import (
	"fmt"
	"os"
)

// Scanner is an iterator to enumerate the contents of a directory.
type Scanner struct {
	osDirname string
	childName string
	dh        *os.File // dh is handle to open directory
	de        *Dirent
	err       error // err is the error associated with scanning directory
	childMode os.FileMode
}

// NewScanner returns a new directory Scanner that lazily enumerates
// the contents of a single directory. To prevent resource leaks,
// caller must invoke either the Scanner's Close or Err method after
// it has completed scanning a directory.
//
//     scanner, err := godirwalk.NewScanner(dirname)
//     if err != nil {
//         fatal("cannot scan directory: %s", err)
//     }
//
//     for scanner.Scan() {
//         dirent, err := scanner.Dirent()
//         if err != nil {
//             warning("cannot get dirent: %s", err)
//             continue
//         }
//         name := dirent.Name()
//         if name == "break" {
//             break
//         }
//         if name == "continue" {
//             continue
//         }
//         fmt.Printf("%v %v\n", dirent.ModeType(), dirent.Name())
//     }
//     if err := scanner.Err(); err != nil {
//         fatal("cannot scan directory: %s", err)
//     }
func NewScanner(osDirname string) (*Scanner, error) {
	dh, err := os.Open(osDirname)
	if err != nil {
		return nil, err
	}
	scanner := &Scanner{
		osDirname: osDirname,
		dh:        dh,
	}
	return scanner, nil
}

// NewScannerWithScratchBuffer returns a new directory Scanner that
// lazily enumerates the contents of a single directory. On platforms
// other than Windows it uses the provided scratch buffer to read from
// the file system. On Windows the scratch buffer parameter is
// ignored. To prevent resource leaks, caller must invoke either the
// Scanner's Close or Err method after it has completed scanning a
// directory.
func NewScannerWithScratchBuffer(osDirname string, scratchBuffer []byte) (*Scanner, error) {
	return NewScanner(osDirname)
}

// Close releases resources associated with scanning a directory. Call
// either this or the Err method when the directory no longer needs to
// be scanned.
func (s *Scanner) Close() error {
	return s.Err()
}

// Dirent returns the current directory entry while scanning a directory.
func (s *Scanner) Dirent() (*Dirent, error) {
	if s.de == nil {
		s.de = &Dirent{
			name:     s.childName,
			path:     s.osDirname,
			modeType: s.childMode,
		}
	}
	return s.de, nil
}

// done is called when directory scanner unable to continue, with either the
// triggering error, or nil when there are simply no more entries to read from
// the directory.
func (s *Scanner) done(err error) {
	if s.dh == nil {
		return
	}

	s.err = err

	if err = s.dh.Close(); s.err == nil {
		s.err = err
	}

	s.childName, s.osDirname = "", ""
	s.de, s.dh = nil, nil
}

// Err returns any error associated with scanning a directory. It is
// normal to call Err after Scan returns false, even though they both
// ensure Scanner resources are released. Call either this or the
// Close method when the directory no longer needs to be scanned.
func (s *Scanner) Err() error {
	s.done(nil)
	return s.err
}

// Name returns the base name of the current directory entry while scanning a
// directory.
func (s *Scanner) Name() string { return s.childName }

// Scan potentially reads and then decodes the next directory entry from the
// file system.
//
// When it returns false, this releases resources used by the Scanner then
// returns any error associated with closing the file system directory resource.
func (s *Scanner) Scan() bool {
	if s.dh == nil {
		return false
	}

	s.de = nil

	fileinfos, err := s.dh.Readdir(1)
	if err != nil {
		s.done(err)
		return false
	}

	if l := len(fileinfos); l != 1 {
		s.done(fmt.Errorf("expected a single entry rather than %d", l))
		return false
	}

	fi := fileinfos[0]
	s.childMode = fi.Mode() & os.ModeType
	s.childName = fi.Name()
	return true
}
