package exifcommon

import (
	"errors"

	"encoding/binary"

	"github.com/dsoprea/go-logging"
)

var (
	parser *Parser
)

var (
	ErrNotFarValue = errors.New("not a far value")
)

// ValueContext embeds all of the parameters required to find and extract the
// actual tag value.
type ValueContext struct {
	unitCount       uint32
	valueOffset     uint32
	rawValueOffset  []byte
	addressableData []byte

	tagType   TagTypePrimitive
	byteOrder binary.ByteOrder

	// undefinedValueTagType is the effective type to use if this is an
	// "undefined" value.
	undefinedValueTagType TagTypePrimitive

	ifdPath string
	tagId   uint16
}

// TODO(dustin): We can update newValueContext() to derive `valueOffset` itself (from `rawValueOffset`).

// newValueContext returns a new ValueContext struct.
func NewValueContext(ifdPath string, tagId uint16, unitCount, valueOffset uint32, rawValueOffset, addressableData []byte, tagType TagTypePrimitive, byteOrder binary.ByteOrder) *ValueContext {
	return &ValueContext{
		unitCount:       unitCount,
		valueOffset:     valueOffset,
		rawValueOffset:  rawValueOffset,
		addressableData: addressableData,

		tagType:   tagType,
		byteOrder: byteOrder,

		ifdPath: ifdPath,
		tagId:   tagId,
	}
}

// SetUndefinedValueType sets the effective type if this is an unknown-type tag.
func (vc *ValueContext) SetUndefinedValueType(tagType TagTypePrimitive) {
	if vc.tagType != TypeUndefined {
		log.Panicf("can not set effective type for unknown-type tag because this is *not* an unknown-type tag")
	}

	vc.undefinedValueTagType = tagType
}

// UnitCount returns the embedded unit-count.
func (vc *ValueContext) UnitCount() uint32 {
	return vc.unitCount
}

// ValueOffset returns the value-offset decoded as a `uint32`.
func (vc *ValueContext) ValueOffset() uint32 {
	return vc.valueOffset
}

// RawValueOffset returns the uninterpreted value-offset. This is used for
// embedded values (values small enough to fit within the offset bytes rather
// than needing to be stored elsewhere and referred to by an actual offset).
func (vc *ValueContext) RawValueOffset() []byte {
	return vc.rawValueOffset
}

// AddressableData returns the block of data that we can dereference into.
func (vc *ValueContext) AddressableData() []byte {
	return vc.addressableData
}

// ByteOrder returns the byte-order of numbers.
func (vc *ValueContext) ByteOrder() binary.ByteOrder {
	return vc.byteOrder
}

// IfdPath returns the path of the IFD containing this tag.
func (vc *ValueContext) IfdPath() string {
	return vc.ifdPath
}

// TagId returns the ID of the tag that we represent.
func (vc *ValueContext) TagId() uint16 {
	return vc.tagId
}

// isEmbedded returns whether the value is embedded or a reference. This can't
// be precalculated since the size is not defined for all types (namely the
// "undefined" types).
func (vc *ValueContext) isEmbedded() bool {
	tagType := vc.effectiveValueType()

	return (tagType.Size() * int(vc.unitCount)) <= 4
}

// SizeInBytes returns the number of bytes that this value requires. The
// underlying call will panic if the type is UNDEFINED. It is the
// responsibility of the caller to preemptively check that.
func (vc *ValueContext) SizeInBytes() int {
	tagType := vc.effectiveValueType()

	return tagType.Size() * int(vc.unitCount)
}

// effectiveValueType returns the effective type of the unknown-type tag or, if
// not unknown, the actual type.
func (vc *ValueContext) effectiveValueType() (tagType TagTypePrimitive) {
	if vc.tagType == TypeUndefined {
		tagType = vc.undefinedValueTagType

		if tagType == 0 {
			log.Panicf("undefined-value type not set")
		}
	} else {
		tagType = vc.tagType
	}

	return tagType
}

// readRawEncoded returns the encoded bytes for the value that we represent.
func (vc *ValueContext) readRawEncoded() (rawBytes []byte, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	tagType := vc.effectiveValueType()

	unitSizeRaw := uint32(tagType.Size())

	if vc.isEmbedded() == true {
		byteLength := unitSizeRaw * vc.unitCount
		return vc.rawValueOffset[:byteLength], nil
	} else {
		return vc.addressableData[vc.valueOffset : vc.valueOffset+vc.unitCount*unitSizeRaw], nil
	}
}

// GetFarOffset returns the offset if the value is not embedded [within the
// pointer itself] or an error if an embedded value.
func (vc *ValueContext) GetFarOffset() (offset uint32, err error) {
	if vc.isEmbedded() == true {
		return 0, ErrNotFarValue
	}

	return vc.valueOffset, nil
}

// ReadRawEncoded returns the encoded bytes for the value that we represent.
func (vc *ValueContext) ReadRawEncoded() (rawBytes []byte, err error) {

	// TODO(dustin): Remove this method and rename readRawEncoded in its place.

	return vc.readRawEncoded()
}

// Format returns a string representation for the value.
//
// Where the type is not ASCII, `justFirst` indicates whether to just stringify
// the first item in the slice (or return an empty string if the slice is
// empty).
//
// Since this method lacks the information to process undefined-type tags (e.g.
// byte-order, tag-ID, IFD type), it will return an error if attempted. See
// `Undefined()`.
func (vc *ValueContext) Format() (value string, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawBytes, err := vc.readRawEncoded()
	log.PanicIf(err)

	phrase, err := FormatFromBytes(rawBytes, vc.effectiveValueType(), false, vc.byteOrder)
	log.PanicIf(err)

	return phrase, nil
}

// FormatFirst is similar to `Format` but only gets and stringifies the first
// item.
func (vc *ValueContext) FormatFirst() (value string, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawBytes, err := vc.readRawEncoded()
	log.PanicIf(err)

	phrase, err := FormatFromBytes(rawBytes, vc.tagType, true, vc.byteOrder)
	log.PanicIf(err)

	return phrase, nil
}

// ReadBytes parses the encoded byte-array from the value-context.
func (vc *ValueContext) ReadBytes() (value []byte, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawValue, err := vc.readRawEncoded()
	log.PanicIf(err)

	value, err = parser.ParseBytes(rawValue, vc.unitCount)
	log.PanicIf(err)

	return value, nil
}

// ReadAscii parses the encoded NUL-terminated ASCII string from the value-
// context.
func (vc *ValueContext) ReadAscii() (value string, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawValue, err := vc.readRawEncoded()
	log.PanicIf(err)

	value, err = parser.ParseAscii(rawValue, vc.unitCount)
	log.PanicIf(err)

	return value, nil
}

// ReadAsciiNoNul parses the non-NUL-terminated encoded ASCII string from the
// value-context.
func (vc *ValueContext) ReadAsciiNoNul() (value string, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawValue, err := vc.readRawEncoded()
	log.PanicIf(err)

	value, err = parser.ParseAsciiNoNul(rawValue, vc.unitCount)
	log.PanicIf(err)

	return value, nil
}

// ReadShorts parses the list of encoded shorts from the value-context.
func (vc *ValueContext) ReadShorts() (value []uint16, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawValue, err := vc.readRawEncoded()
	log.PanicIf(err)

	value, err = parser.ParseShorts(rawValue, vc.unitCount, vc.byteOrder)
	log.PanicIf(err)

	return value, nil
}

// ReadLongs parses the list of encoded, unsigned longs from the value-context.
func (vc *ValueContext) ReadLongs() (value []uint32, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawValue, err := vc.readRawEncoded()
	log.PanicIf(err)

	value, err = parser.ParseLongs(rawValue, vc.unitCount, vc.byteOrder)
	log.PanicIf(err)

	return value, nil
}

// ReadRationals parses the list of encoded, unsigned rationals from the value-
// context.
func (vc *ValueContext) ReadRationals() (value []Rational, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawValue, err := vc.readRawEncoded()
	log.PanicIf(err)

	value, err = parser.ParseRationals(rawValue, vc.unitCount, vc.byteOrder)
	log.PanicIf(err)

	return value, nil
}

// ReadSignedLongs parses the list of encoded, signed longs from the value-context.
func (vc *ValueContext) ReadSignedLongs() (value []int32, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawValue, err := vc.readRawEncoded()
	log.PanicIf(err)

	value, err = parser.ParseSignedLongs(rawValue, vc.unitCount, vc.byteOrder)
	log.PanicIf(err)

	return value, nil
}

// ReadSignedRationals parses the list of encoded, signed rationals from the
// value-context.
func (vc *ValueContext) ReadSignedRationals() (value []SignedRational, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	rawValue, err := vc.readRawEncoded()
	log.PanicIf(err)

	value, err = parser.ParseSignedRationals(rawValue, vc.unitCount, vc.byteOrder)
	log.PanicIf(err)

	return value, nil
}

// Values knows how to resolve the given value. This value is always a list
// (undefined-values aside), so we're named accordingly.
//
// Since this method lacks the information to process unknown-type tags (e.g.
// byte-order, tag-ID, IFD type), it will return an error if attempted. See
// `Undefined()`.
func (vc *ValueContext) Values() (values interface{}, err error) {
	defer func() {
		if state := recover(); state != nil {
			err = log.Wrap(state.(error))
		}
	}()

	if vc.tagType == TypeByte {
		values, err = vc.ReadBytes()
		log.PanicIf(err)
	} else if vc.tagType == TypeAscii {
		values, err = vc.ReadAscii()
		log.PanicIf(err)
	} else if vc.tagType == TypeAsciiNoNul {
		values, err = vc.ReadAsciiNoNul()
		log.PanicIf(err)
	} else if vc.tagType == TypeShort {
		values, err = vc.ReadShorts()
		log.PanicIf(err)
	} else if vc.tagType == TypeLong {
		values, err = vc.ReadLongs()
		log.PanicIf(err)
	} else if vc.tagType == TypeRational {
		values, err = vc.ReadRationals()
		log.PanicIf(err)
	} else if vc.tagType == TypeSignedLong {
		values, err = vc.ReadSignedLongs()
		log.PanicIf(err)
	} else if vc.tagType == TypeSignedRational {
		values, err = vc.ReadSignedRationals()
		log.PanicIf(err)
	} else if vc.tagType == TypeUndefined {
		log.Panicf("will not parse undefined-type value")

		// Never called.
		return nil, nil
	} else {
		log.Panicf("value of type [%s] is unparseable", vc.tagType)

		// Never called.
		return nil, nil
	}

	return values, nil
}

func init() {
	parser = new(Parser)
}
