//go:build windows
// +build windows

package lcow // import "github.com/docker/docker/daemon/graphdriver/lcow"

import (
	"bytes"
	"fmt"
	"io"
	"runtime"
	"strings"
	"sync"

	"github.com/Microsoft/hcsshim"
	"github.com/Microsoft/opengcs/service/gcsutils/remotefs"
	"github.com/docker/docker/pkg/archive"
	"github.com/docker/docker/pkg/containerfs"
	"github.com/sirupsen/logrus"
)

type lcowfs struct {
	root        string
	d           *Driver
	mappedDisks []hcsshim.MappedVirtualDisk
	vmID        string
	currentSVM  *serviceVM
	sync.Mutex
}

var _ containerfs.ContainerFS = &lcowfs{}

// ErrNotSupported is an error for unsupported operations in the remotefs
var ErrNotSupported = fmt.Errorf("not supported")

// Functions to implement the ContainerFS interface
func (l *lcowfs) Path() string {
	return l.root
}

func (l *lcowfs) ResolveScopedPath(path string, rawPath bool) (string, error) {
	logrus.Debugf("remotefs.resolvescopedpath inputs: %s %s ", path, l.root)

	arg1 := l.Join(l.root, path)
	if !rawPath {
		// The l.Join("/", path) will make path an absolute path and then clean it
		// so if path = ../../X, it will become /X.
		arg1 = l.Join(l.root, l.Join("/", path))
	}
	arg2 := l.root

	output := &bytes.Buffer{}
	if err := l.runRemoteFSProcess(nil, output, remotefs.ResolvePathCmd, arg1, arg2); err != nil {
		return "", err
	}

	logrus.Debugf("remotefs.resolvescopedpath success. Output: %s\n", output.String())
	return output.String(), nil
}

func (l *lcowfs) OS() string {
	return "linux"
}

func (l *lcowfs) Architecture() string {
	return runtime.GOARCH
}

// Other functions that are used by docker like the daemon Archiver/Extractor
func (l *lcowfs) ExtractArchive(src io.Reader, dst string, opts *archive.TarOptions) error {
	logrus.Debugf("remotefs.ExtractArchve inputs: %s %+v", dst, opts)

	tarBuf := &bytes.Buffer{}
	if err := remotefs.WriteTarOptions(tarBuf, opts); err != nil {
		return fmt.Errorf("failed to marshall tar opts: %s", err)
	}

	input := io.MultiReader(tarBuf, src)
	if err := l.runRemoteFSProcess(input, nil, remotefs.ExtractArchiveCmd, dst); err != nil {
		return fmt.Errorf("failed to extract archive to %s: %s", dst, err)
	}
	return nil
}

func (l *lcowfs) ArchivePath(src string, opts *archive.TarOptions) (io.ReadCloser, error) {
	logrus.Debugf("remotefs.ArchivePath: %s %+v", src, opts)

	tarBuf := &bytes.Buffer{}
	if err := remotefs.WriteTarOptions(tarBuf, opts); err != nil {
		return nil, fmt.Errorf("failed to marshall tar opts: %s", err)
	}

	r, w := io.Pipe()
	go func() {
		defer w.Close()
		if err := l.runRemoteFSProcess(tarBuf, w, remotefs.ArchivePathCmd, src); err != nil {
			logrus.Debugf("REMOTEFS: Failed to extract archive: %s %+v %s", src, opts, err)
		}
	}()
	return r, nil
}

// Helper functions
func (l *lcowfs) startVM() error {
	l.Lock()
	defer l.Unlock()
	if l.currentSVM != nil {
		return nil
	}

	svm, err := l.d.startServiceVMIfNotRunning(l.vmID, l.mappedDisks, fmt.Sprintf("lcowfs.startVM"))
	if err != nil {
		return err
	}

	if err = svm.createUnionMount(l.root, l.mappedDisks...); err != nil {
		return err
	}
	l.currentSVM = svm
	return nil
}

func (l *lcowfs) runRemoteFSProcess(stdin io.Reader, stdout io.Writer, args ...string) error {
	if err := l.startVM(); err != nil {
		return err
	}

	// Append remotefs prefix and setup as a command line string
	cmd := fmt.Sprintf("%s %s", remotefs.RemotefsCmd, strings.Join(args, " "))
	stderr := &bytes.Buffer{}
	if err := l.currentSVM.runProcess(cmd, stdin, stdout, stderr); err != nil {
		return err
	}

	eerr, err := remotefs.ReadError(stderr)
	if eerr != nil {
		// Process returned an error so return that.
		return remotefs.ExportedToError(eerr)
	}
	return err
}
