// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package statfs_test

import (
	"fmt"
	"github.com/jacobsa/oglematchers"
	"math"
	"regexp"
	"syscall"

	"github.com/jacobsa/fuse/fuseops"
	. "github.com/jacobsa/ogletest"
)

// Sample output:
//
//	Filesystem  1024-blocks Used Available Capacity iused ifree %iused  Mounted on
//	fake@bucket          32   16        16    50%       0     0  100%   /Users/jacobsa/tmp/mp
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+\d+\s+\d+\s+\d+%.*$`)

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

func convertName(in []int8) string {
	var tmp []byte
	for _, v := range in {
		if v == 0 {
			break
		}

		tmp = append(tmp, byte(v))
	}

	return string(tmp)
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *StatFSTest) Syscall_ZeroValues() {
	var err error
	var stat syscall.Statfs_t

	// Call without configuring a canned response, meaning the OS will see the
	// zero value for each field. The assertions below act as documentation for
	// the OS's behavior in this case.
	err = syscall.Statfs(t.Dir, &stat)
	AssertEq(nil, err)

	ExpectEq(4096, stat.Bsize)
	ExpectEq(65536, stat.Iosize)
	ExpectEq(0, stat.Blocks)
	ExpectEq(0, stat.Bfree)
	ExpectEq(0, stat.Bavail)
	ExpectEq(0, stat.Files)
	ExpectEq(0, stat.Ffree)
	ExpectThat(convertName(stat.Fstypename[:]), oglematchers.AnyOf(oglematchers.Equals("osxfuse"), oglematchers.Equals("macfuse")))
	ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
	ExpectEq(fsName, convertName(stat.Mntfromname[:]))
}

func (t *StatFSTest) Syscall_NonZeroValues() {
	var err error
	var stat syscall.Statfs_t

	// Set up the canned response.
	canned := fuseops.StatFSOp{
		BlockSize: 1 << 15,
		IoSize:    1 << 16,

		Blocks:          1<<51 + 3,
		BlocksFree:      1<<43 + 5,
		BlocksAvailable: 1<<41 + 7,

		Inodes:     1<<59 + 11,
		InodesFree: 1<<58 + 13,
	}

	t.fs.SetStatFSResponse(canned)

	// Stat.
	err = syscall.Statfs(t.Dir, &stat)
	AssertEq(nil, err)

	ExpectEq(canned.BlockSize, stat.Bsize)
	ExpectEq(canned.IoSize, stat.Iosize)
	ExpectEq(canned.Blocks, stat.Blocks)
	ExpectEq(canned.BlocksFree, stat.Bfree)
	ExpectEq(canned.BlocksAvailable, stat.Bavail)
	ExpectEq(canned.Inodes, stat.Files)
	ExpectEq(canned.InodesFree, stat.Ffree)
	ExpectThat(convertName(stat.Fstypename[:]), oglematchers.AnyOf(oglematchers.Equals("osxfuse"), oglematchers.Equals("macfuse")))
	ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
	ExpectEq(fsName, convertName(stat.Mntfromname[:]))
}

func (t *StatFSTest) BlockSizes() {
	var err error

	// Test a bunch of block sizes that the OS does or doesn't support
	// faithfully, checking what it transforms them too.
	testCases := []struct {
		fsBlockSize   uint32
		expectedBsize uint32
	}{
		0:  {0, 4096},
		1:  {1, 128},
		2:  {3, 128},
		3:  {511, 512},
		4:  {512, 512},
		5:  {513, 1024},
		6:  {1023, 1024},
		7:  {1024, 1024},
		8:  {4095, 4096},
		9:  {1 << 16, 1 << 16},
		10: {1 << 17, 1 << 17},
		11: {1 << 18, 1 << 18},
		12: {1 << 19, 1 << 19},

		13: {1<<20 - 1, 1 << 20},
		14: {1 << 20, 1 << 20},
		15: {1<<20 + 1, 1 << 20},

		16: {1 << 21, 1 << 20},
		17: {1 << 22, 1 << 20},

		18: {math.MaxInt32 - 1, 1 << 20},
		19: {math.MaxInt32, 1 << 20},
		20: {math.MaxInt32 + 1, 128},
		21: {math.MaxInt32 + 1<<15, 1 << 15},
		22: {math.MaxUint32, 1 << 20},
	}

	for i, tc := range testCases {
		desc := fmt.Sprintf("Case %d: block size %d", i, tc.fsBlockSize)

		// Set up.
		canned := fuseops.StatFSOp{
			BlockSize: tc.fsBlockSize,
			Blocks:    10,
		}

		t.fs.SetStatFSResponse(canned)

		// Check.
		var stat syscall.Statfs_t
		err = syscall.Statfs(t.Dir, &stat)
		AssertEq(nil, err)

		ExpectEq(tc.expectedBsize, stat.Bsize, "%s", desc)
	}
}

func (t *StatFSTest) IoSizes() {
	var err error

	// Test a bunch of io sizes that the OS does or doesn't support faithfully,
	// checking what it transforms them too.
	testCases := []struct {
		fsIoSize       uint32
		expectedIosize uint32
	}{
		0: {0, 65536},
		1: {1, 4096},
		2: {3, 4096},
		3: {4095, 4096},
		4: {4096, 4096},
		5: {4097, 8192},
		6: {8191, 8192},
		7: {8192, 8192},
		8: {8193, 16384},

		9:  {1 << 18, 1 << 18},
		10: {1 << 20, 1 << 20},
		11: {1 << 23, 1 << 23},

		12: {1<<25 - 1, 1 << 25},
		13: {1 << 25, 1 << 25},
		14: {1<<25 + 1, 1 << 25},

		15: {math.MaxInt32 - 1, 1 << 25},
		16: {math.MaxInt32, 1 << 25},
		17: {math.MaxInt32 + 1, 4096},
		18: {math.MaxInt32 + 1<<15, 1 << 15},
		19: {math.MaxUint32, 1 << 25},
	}

	for i, tc := range testCases {
		desc := fmt.Sprintf("Case %d: IO size %d", i, tc.fsIoSize)

		// Set up.
		canned := fuseops.StatFSOp{
			IoSize: tc.fsIoSize,
			Blocks: 10,
		}

		t.fs.SetStatFSResponse(canned)

		// Check.
		var stat syscall.Statfs_t
		err = syscall.Statfs(t.Dir, &stat)
		AssertEq(nil, err)

		ExpectEq(tc.expectedIosize, stat.Iosize, "%s", desc)
	}
}
