/*
 * SPDX-FileCopyrightText: © Hypermode Inc. <hello@hypermode.com>
 * SPDX-License-Identifier: Apache-2.0
 */

package z

import (
	
	
	
	
	
	
)

// MmapFile represents an mmapd file and includes both the buffer to the data
// and the file descriptor.
type MmapFile struct {
	Data []byte
	Fd   *os.File
}

var NewFile = errors.New("Create a new file")

func ( *os.File,  int,  bool) (*MmapFile, error) {
	 := .Name()
	,  := .Stat()
	if  != nil {
		return nil, errors.Join(, fmt.Errorf("cannot stat file: %s", ))
	}

	var  error
	 := .Size()
	if  > 0 &&  == 0 {
		// If file is empty, truncate it to sz.
		if  := .Truncate(int64());  != nil {
			return nil, errors.Join(, errors.New("error while truncation"))
		}
		 = int64()
		 = NewFile
	}

	// fmt.Printf("Mmaping file: %s with writable: %v filesize: %d\n", fd.Name(), writable, fileSize)
	,  := Mmap(, , ) // Mmap up to file size.
	if  != nil {
		return nil, errors.Join(, fmt.Errorf("while mmapping %s with size: %d", .Name(), ))
	}

	if  == 0 {
		,  := filepath.Split()
		if  := SyncDir();  != nil {
			return nil, 
		}
	}
	return &MmapFile{
		Data: ,
		Fd:   ,
	}, 
}

// OpenMmapFile opens an existing file or creates a new file. If the file is
// created, it would truncate the file to maxSz. In both cases, it would mmap
// the file to maxSz and returned it. In case the file is created, z.NewFile is
// returned.
func ( string,  int,  int) (*MmapFile, error) {
	// fmt.Printf("opening file %s with flag: %v\n", filename, flag)
	,  := os.OpenFile(, , 0666)
	if  != nil {
		return nil, errors.Join(, fmt.Errorf("unable to open: %s", ))
	}
	 := true
	if  == os.O_RDONLY {
		 = false
	}
	return OpenMmapFileUsing(, , )
}

type mmapReader struct {
	Data   []byte
	offset int
}

func ( *mmapReader) ( []byte) (int, error) {
	if .offset > len(.Data) {
		return 0, io.EOF
	}
	 := copy(, .Data[.offset:])
	.offset += 
	if  < len() {
		return , io.EOF
	}
	return , nil
}

func ( *MmapFile) ( int) io.Reader {
	return &mmapReader{
		Data:   .Data,
		offset: ,
	}
}

// Bytes returns data starting from offset off of size sz. If there's not enough data, it would
// return nil slice and io.EOF.
func ( *MmapFile) (,  int) ([]byte, error) {
	if len(.Data[:]) <  {
		return nil, io.EOF
	}
	return .Data[ : +], nil
}

// Slice returns the slice at the given offset.
func ( *MmapFile) ( int) []byte {
	 := binary.BigEndian.Uint32(.Data[:])
	 :=  + 4
	 :=  + int()
	if  > len(.Data) {
		return []byte{}
	}
	 := .Data[:]
	return 
}

// AllocateSlice allocates a slice of the given size at the given offset.
func ( *MmapFile) (,  int) ([]byte, int, error) {
	 :=  + 4

	// If the file is too small, double its size or increase it by 1GB, whichever is smaller.
	if + > len(.Data) {
		const  = 1 << 30
		 := len(.Data)
		if  >  {
			 = 
		}
		if  < +4 {
			 =  + 4
		}
		if  := .Truncate(int64(len(.Data) + ));  != nil {
			return nil, 0, 
		}
	}

	binary.BigEndian.PutUint32(.Data[:], uint32())
	return .Data[ : +],  + , nil
}

func ( *MmapFile) () error {
	if  == nil {
		return nil
	}
	return Msync(.Data)
}

func ( *MmapFile) () error {
	// Badger can set the m.Data directly, without setting any Fd. In that case, this should be a
	// NOOP.
	if .Fd == nil {
		return nil
	}

	if  := Munmap(.Data);  != nil {
		return fmt.Errorf("while munmap file: %s, error: %v\n", .Fd.Name(), )
	}
	.Data = nil
	if  := .Fd.Truncate(0);  != nil {
		return fmt.Errorf("while truncate file: %s, error: %v\n", .Fd.Name(), )
	}
	if  := .Fd.Close();  != nil {
		return fmt.Errorf("while close file: %s, error: %v\n", .Fd.Name(), )
	}
	return os.Remove(.Fd.Name())
}

// Close would close the file. It would also truncate the file if maxSz >= 0.
func ( *MmapFile) ( int64) error {
	// Badger can set the m.Data directly, without setting any Fd. In that case, this should be a
	// NOOP.
	if .Fd == nil {
		return nil
	}
	if  := .Sync();  != nil {
		return fmt.Errorf("while sync file: %s, error: %v\n", .Fd.Name(), )
	}
	if  := Munmap(.Data);  != nil {
		return fmt.Errorf("while munmap file: %s, error: %v\n", .Fd.Name(), )
	}
	if  >= 0 {
		if  := .Fd.Truncate();  != nil {
			return fmt.Errorf("while truncate file: %s, error: %v\n", .Fd.Name(), )
		}
	}
	return .Fd.Close()
}

func ( string) error {
	,  := os.Open()
	if  != nil {
		return errors.Join(, fmt.Errorf("while opening %s", ))
	}
	if  := .Sync();  != nil {
		return errors.Join(, fmt.Errorf("while syncing %s", ))
	}
	if  := .Close();  != nil {
		return errors.Join(, fmt.Errorf("while closing %s", ))
	}
	return nil
}