// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// func-os.go
package expr

import (
	"bufio"
	"fmt"
	"io"
	"os"
)

type osHandle interface {
	getFile() *os.File
}

type osWriter struct {
	fh     *os.File
	writer *bufio.Writer
}

func (h *osWriter) getFile() *os.File {
	return h.fh
}

type osReader struct {
	fh     *os.File
	reader *bufio.Reader
}

func (h *osReader) getFile() *os.File {
	return h.fh
}

func createFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
	var filePath string
	if len(args) > 0 {
		filePath, _ = args[0].(string)
	}

	if len(filePath) > 0 {
		var fh *os.File
		if fh, err = os.Create(filePath); err == nil {
			result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
		}
	} else {
		err = fmt.Errorf("%s(): missing the file path", name)
	}
	return
}

func openFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
	var filePath string
	if len(args) > 0 {
		filePath, _ = args[0].(string)
	}

	if len(filePath) > 0 {
		var fh *os.File
		if fh, err = os.Open(filePath); err == nil {
			result = &osReader{fh: fh, reader: bufio.NewReader(fh)}
		}
	} else {
		err = fmt.Errorf("%s(): missing the file path", name)
	}
	return
}

func appendFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
	var filePath string
	if len(args) > 0 {
		filePath, _ = args[0].(string)
	}

	if len(filePath) > 0 {
		var fh *os.File
		if fh, err = os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0660); err == nil {
			result = &osWriter{fh: fh, writer: bufio.NewWriter(fh)}
		}
	} else {
		err = fmt.Errorf("%s(): missing the file path", name)
	}
	return
}

func closeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
	var handle osHandle

	if len(args) > 0 {
		handle, _ = args[0].(osHandle)
	}

	if handle != nil {
		if fh := handle.getFile(); fh != nil {
			if w, ok := handle.(*osWriter); ok {
				err = w.writer.Flush()
			}
			if err == nil {
				err = fh.Close()
			}
		}
	} else {
		err = fmt.Errorf("%s(): invalid file handle", name)
	}
	result = err == nil
	return
}

func writeFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
	var handle osHandle

	if len(args) > 0 {
		handle, _ = args[0].(osHandle)
	}

	if handle != nil {
		if fh := handle.getFile(); fh != nil {
			if w, ok := handle.(*osWriter); ok {
				result, err = fmt.Fprint(w.writer, args[1:]...)
			}
		}
	}
	return
}

func readFileFunc(ctx ExprContext, name string, args []any) (result any, err error) {
	var handle osHandle
	result = nil
	if len(args) > 0 {
		handle, _ = args[0].(osHandle)
	}

	if handle != nil {
		if fh := handle.getFile(); fh != nil {
			if r, ok := handle.(*osReader); ok {
				var limit byte = '\n'
				var v string
				if len(args) > 1 {
					if s, ok := args[1].(string); ok && len(s) > 0 {
						limit = s[0]
					}
				}
				if v, err = r.reader.ReadString(limit); err == nil {
					if len(v) > 0 && v[len(v)-1] == limit {
						result = v[0 : len(v)-1]
					} else {
						result = v
					}
				}
				if err == io.EOF {
					err = nil
				}
			}
		}
	}
	return
}

func ImportOsFuncs(ctx ExprContext) {
	ctx.RegisterFunc("openFile", &simpleFunctor{f: openFileFunc}, 1, 1)
	ctx.RegisterFunc("appendFile", &simpleFunctor{f: appendFileFunc}, 1, 1)
	ctx.RegisterFunc("createFile", &simpleFunctor{f: createFileFunc}, 1, 1)
	ctx.RegisterFunc("writeFile", &simpleFunctor{f: writeFileFunc}, 1, -1)
	ctx.RegisterFunc("readFile", &simpleFunctor{f: readFileFunc}, 1, 2)
	ctx.RegisterFunc("closeFile", &simpleFunctor{f: closeFileFunc}, 1, 1)
}

func init() {
	registerImport("os.file", ImportOsFuncs, "Operating system file functions")
}