// Copyright 2024 Martin Riedl
//
// 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 gomp4

import (
	"encoding/binary"
	"fmt"
)

// TrackFragmentRunBox Track Fragment Run Box struct
//
// 8.8.8 Track Fragment Run Box
// Box Type: ‘trun’
// Container: Track Fragment Box ('traf')
// Mandatory: No
// Quantity: Zero or more
//
// Within the Track Fragment Box, there are zero or more Track Run Boxes. If the duration‐is‐empty flag is
// set in the tf_flags, there are no track runs. A track run documents a contiguous set of samples for a track.
//
// The number of optional fields is determined from the number of bits set in the lower byte of the flags,
// and the size of a record from the bits set in the second byte of the flags. This procedure shall be
// followed, to allow for new fields to be defined.
//
// If the data‐offset is not present, then the data for this run starts immediately after the data of the
// previous run, or at the base‐data‐offset defined by the track fragment header if this is the first run in a
// track fragment, If the data‐offset is present, it is relative to the base‐data‐offset established in the track
// fragment header.
//
// The composition offset values in the composition time‐to‐sample box and in the track run box may be
// signed or unsigned. The recommendations given in the composition time‐to‐sample box concerning the
// use of signed composition offsets also apply here.
type TrackFragmentRunBox struct {
	*FullBox
	// the number of samples being added in this run; also the number of rows in the
	// following table (the rows can be empty)
	SampleCount uint32
	// is added to the implicit or explicit data_offset established in the track fragment header.
	DataOffset int32
	// provides a set of flags for the first sample only of this run.
	FirstSampleFlags uint32
	Samples          []TrackFragmentRunBoxSample
}

// BoxTypeTrackFragmentRun Track Fragment Run Box
const BoxTypeTrackFragmentRun = "trun"

func init() {
	BoxDefinitions = append(BoxDefinitions, BoxDefinition{
		Type:        BoxTypeTrackFragmentRun,
		ParentTypes: []string{BoxTypeTrackFragment},
		Parser:      ParseTrackFragmentRunBox,
	})
}

const (
	// TrackFragmentRunBoxFlagDataOffsetPresent Data Offset Flag
	TrackFragmentRunBoxFlagDataOffsetPresent uint32 = 0x00000001

	// TrackFragmentRunBoxFlagFirstSampleFlagsPresent First Sample Flags
	// this over‐rides the default flags for the first sample only. This
	// makes it possible to record a group of frames where the first is a key and the rest are difference
	// frames, without supplying explicit flags for every sample. If this flag and field are used, sample‐
	// flags shall not be present.
	TrackFragmentRunBoxFlagFirstSampleFlagsPresent uint32 = 0x00000004

	// TrackFragmentRunBoxFlagSampleDurationPresent Sample Duration Flag
	// indicates that each sample has its own duration, otherwise the default is used.
	TrackFragmentRunBoxFlagSampleDurationPresent uint32 = 0x00000100

	// TrackFragmentRunBoxFlagSampleSizePresent Sample Size Flag
	// each sample has its own size, otherwise the default is used.
	TrackFragmentRunBoxFlagSampleSizePresent uint32 = 0x00000200

	// TrackFragmentRunBoxFlagSampleFlagsPresent Sample Flags Present Flag
	// each sample has its own flags, otherwise the default is used.
	TrackFragmentRunBoxFlagSampleFlagsPresent uint32 = 0x00000400

	// TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent Sample Composition Time Offset
	// each sample has a composition time offset
	// (e.g. as used for I/P/B video in MPEG).
	TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent uint32 = 0x00000800
)

// ParseTrackFragmentRunBox creates a new Track Fragment Run Box struct
func ParseTrackFragmentRunBox(parser *Parser, filePosition uint64, headerSize uint32, content []byte) (any, error) {
	// build full box with additional 4 bytes header
	fullBox := newFullBox(&Box{filePosition, headerSize}, content[0:4])
	position := 4

	// create track fragment run box
	box := &TrackFragmentRunBox{FullBox: fullBox}

	// parse sample counter
	box.SampleCount = binary.BigEndian.Uint32(content[position : position+4])
	position += 4

	// parse data offset
	if fullBox.flagUInt32&TrackFragmentRunBoxFlagDataOffsetPresent == TrackFragmentRunBoxFlagDataOffsetPresent {
		box.DataOffset = int32(binary.BigEndian.Uint32(content[position : position+4]))
		position += 4
	}

	// parse first sample flags
	if fullBox.flagUInt32&TrackFragmentRunBoxFlagFirstSampleFlagsPresent == TrackFragmentRunBoxFlagFirstSampleFlagsPresent {
		box.FirstSampleFlags = binary.BigEndian.Uint32(content[position : position+4])
		position += 4
	}

	// parse samples
	for i := uint32(0); i < box.SampleCount; i++ {
		sample := TrackFragmentRunBoxSample{}

		// parse duration
		if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleDurationPresent == TrackFragmentRunBoxFlagSampleDurationPresent {
			sample.Duration = binary.BigEndian.Uint32(content[position : position+4])
			position += 4
		}

		// parse size
		if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleSizePresent == TrackFragmentRunBoxFlagSampleSizePresent {
			sample.Size = binary.BigEndian.Uint32(content[position : position+4])
			position += 4
		}

		// parse flags
		if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleFlagsPresent == TrackFragmentRunBoxFlagSampleFlagsPresent {
			sample.Flags = binary.BigEndian.Uint32(content[position : position+4])
			position += 4
		}

		// parse composition time offset
		if fullBox.flagUInt32&TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent == TrackFragmentRunBoxFlagSampleCompositionTimeOffsetPresent {
			if fullBox.Version == 0 {
				sample.CompositionTimeOffsetV0 = binary.BigEndian.Uint32(content[position : position+4])
				position += 4
			} else {
				sample.CompositionTimeOffset = int32(binary.BigEndian.Uint32(content[position : position+4]))
				position += 4
			}
		}

		// store new sample
		box.Samples = append(box.Samples, sample)
	}

	// check, if everything has been parsed
	if position != len(content) {
		return nil, fmt.Errorf("invalid byte position %d; expected position %d in file box position %d", position, len(content), filePosition)
	}

	return box, nil
}

// TrackFragmentRunBoxSample contains sample information
type TrackFragmentRunBoxSample struct {
	Duration                uint32
	Size                    uint32
	Flags                   uint32
	CompositionTimeOffsetV0 uint32
	CompositionTimeOffset   int32
}
