// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.

package ranger

import (
	"context"
	"errors"
	"io"
	"os"

	"github.com/zeebo/errs"
)

type fileRanger struct {
	path string
	size int64
}

// FileRanger returns a Ranger from a path.
func FileRanger(path string) (Ranger, error) {
	info, err := os.Stat(path)
	if err != nil {
		return nil, Error.Wrap(err)
	}
	return &fileRanger{path: path, size: info.Size()}, nil
}

func (rr *fileRanger) Size() int64 {
	return rr.size
}

func (rr *fileRanger) Range(ctx context.Context, offset, length int64) (_ io.ReadCloser, err error) {
	defer mon.Task()(&ctx)(&err)
	if offset < 0 {
		return nil, Error.New("negative offset")
	}
	if length < 0 {
		return nil, Error.New("negative length")
	}
	if offset+length > rr.size {
		return nil, Error.New("range beyond end")
	}

	fh, err := os.Open(rr.path)
	if err != nil {
		return nil, Error.Wrap(err)
	}
	_, err = fh.Seek(offset, io.SeekStart)
	if err != nil {
		return nil, Error.Wrap(errs.Combine(err, fh.Close()))
	}

	return &FileReader{fh, length}, nil
}

// FileReader implements limit reader with io.EOF only on last read.
type FileReader struct {
	file      *os.File
	remaining int64
}

// Read reads from the underlying file.
func (reader *FileReader) Read(data []byte) (n int, err error) {
	if reader.remaining <= 0 {
		return 0, io.EOF
	}
	if int64(len(data)) > reader.remaining {
		data = data[0:reader.remaining]
	}
	n, err = reader.file.Read(data)
	reader.remaining -= int64(n)
	if errors.Is(err, io.EOF) && reader.remaining == 0 {
		err = nil
	}
	return
}

// Close closes the underlying file.
func (reader *FileReader) Close() error {
	return reader.file.Close()
}
