package mpq

import (
	"fmt"
	"path/filepath"

	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset"
	"github.com/OpenDiablo2/OpenDiablo2/d2common/d2loader/asset/types"
)

const (
	bufLength = 32
)

// static check that Asset implements Asset
var _ asset.Asset = &Asset{}

// Asset represents a file record within an MPQ archive
type Asset struct {
	stream d2interface.DataStream
	data   []byte
	path   string
	source *Source
}

// Type returns the asset type
func (a *Asset) Type() types.AssetType {
	return types.Ext2AssetType(filepath.Ext(a.Path()))
}

// Source returns the source of this asset
func (a *Asset) Source() asset.Source {
	return a.source
}

// Path returns the sub-path (within the source) of this asset
func (a *Asset) Path() string {
	return a.path
}

// Read will read asset data into the given buffer
func (a *Asset) Read(buf []byte) (n int, err error) {
	return a.stream.Read(buf)
}

// Seek will seek the read position for the next read operation
func (a *Asset) Seek(offset int64, whence int) (n int64, err error) {
	return a.stream.Seek(offset, whence)
}

// Close will seek the read position for the next read operation
func (a *Asset) Close() (err error) {
	// Calling a.stream.Close() will set the stream to nil, we dont want to do that.
	// Because this asset gets cached, it may get retrieved again and used, in which
	// case we will want the stream ready. So, instead of closing, we just seek back to the start.
	// The garbage collector should get around to it if it ever gets ejected from the cache.
	_, err = a.Seek(0, 0)
	return err
}

// Data returns the raw file data as a slice of bytes
func (a *Asset) Data() ([]byte, error) {
	if a.stream == nil {
		return nil, fmt.Errorf("asset has no file: %s", a.Path())
	}

	if a.data != nil {
		return a.data, nil
	}

	_, seekErr := a.Seek(0, 0)
	if seekErr != nil {
		return nil, seekErr
	}

	buf := make([]byte, bufLength)
	data := make([]byte, 0)

	for {
		numBytesRead, readErr := a.Read(buf)

		data = append(data, buf[:numBytesRead]...)

		if readErr != nil || numBytesRead == 0 {
			break
		}
	}

	a.data = data

	return data, nil
}

// String returns the path
func (a *Asset) String() string {
	return a.Path()
}
