// goredo -- djb's redo implementation on pure Go
// Copyright (C) 2020-2026 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

// Inode metainformation

package main

import (
	"bytes"
	"encoding/binary"
	"os"
	"strconv"

	"go.stargrave.org/recfile/v4"
	"golang.org/x/sys/unix"
)

const InodeLen = 6 * 8

type InodeTrustType int

//go:generate stringer -type=InodeTrustType
const (
	EnvInodeTrust = "REDO_INODE_TRUST"

	InodeTrustNone InodeTrustType = iota
	InodeTrustCtime
	InodeTrustMtime
)

var InodeTrust InodeTrustType

// It is big-endian 64-bit unsigned integers: size, inodeNum,
// ctime sec, ctime nsec, mtime sec, mtime nsec.
type Inode [InodeLen]byte

func (our *Inode) Equals(their *Inode) bool {
	if !bytes.Equal(our[:2*8], their[:2*8]) {
		return false
	}
	switch InodeTrust {
	case InodeTrustCtime:
		if !bytes.Equal(our[2*8:4*8], their[2*8:4*8]) {
			return false
		}
	case InodeTrustMtime:
		if !bytes.Equal(our[4*8:6*8], their[4*8:6*8]) {
			return false
		}
	}
	return true
}

func (inode *Inode) RecfileFields() []recfile.Field {
	return []recfile.Field{
		{F: "Size", V: strconv.FormatUint(binary.BigEndian.Uint64(
			[]byte(inode[0*8:1*8])), 10)},
		{F: "InodeNum", V: strconv.FormatUint(binary.BigEndian.Uint64(
			[]byte(inode[1*8:2*8])), 10)},
		{F: "CtimeSec", V: strconv.FormatUint(binary.BigEndian.Uint64(
			[]byte(inode[2*8:3*8])), 10)},
		{F: "CtimeNsec", V: strconv.FormatUint(binary.BigEndian.Uint64(
			[]byte(inode[3*8:4*8])), 10)},
		{F: "MtimeSec", V: strconv.FormatUint(binary.BigEndian.Uint64(
			[]byte(inode[4*8:5*8])), 10)},
		{F: "MtimeNsec", V: strconv.FormatUint(binary.BigEndian.Uint64(
			[]byte(inode[5*8:6*8])), 10)},
	}
}

func inodeFromFileStat(fi os.FileInfo, stat unix.Stat_t) *Inode {
	ctimeSec, ctimeNsec := stat.Ctim.Unix()
	mtimeNsec := fi.ModTime().UnixNano()
	mtimeSec := mtimeNsec / 1000000000
	mtimeNsec -= mtimeSec * 1000000000
	inode := new(Inode)
	binary.BigEndian.PutUint64(inode[0*8:1*8], uint64(fi.Size()))
	binary.BigEndian.PutUint64(inode[1*8:2*8], uint64(stat.Ino))
	binary.BigEndian.PutUint64(inode[2*8:3*8], uint64(ctimeSec))
	binary.BigEndian.PutUint64(inode[3*8:4*8], uint64(ctimeNsec))
	binary.BigEndian.PutUint64(inode[4*8:5*8], uint64(mtimeSec))
	binary.BigEndian.PutUint64(inode[5*8:6*8], uint64(mtimeNsec))
	return inode
}

func inodeFromFileByFd(fd *os.File) (inode *Inode, isDir bool, err error) {
	fi, err := fd.Stat()
	if err != nil {
		return
	}
	if fi.IsDir() {
		isDir = true
		return
	}
	var stat unix.Stat_t
	err = unix.Fstat(int(fd.Fd()), &stat)
	if err != nil {
		return
	}
	inode = inodeFromFileStat(fi, stat)
	return
}

func inodeFromFileByPath(p string) (*Inode, error) {
	fi, err := os.Stat(p)
	if err != nil {
		return nil, err
	}
	var stat unix.Stat_t
	err = unix.Stat(p, &stat)
	if err != nil {
		return nil, err
	}
	return inodeFromFileStat(fi, stat), nil
}
