/*
NAME
  descriptor_test.go

DESCRIPTION
  See Readme.md

AUTHOR
  Saxon Nelson-Milton <saxon@ausocean.org>

LICENSE
  descriptor_test.go is Copyright (C) 2017-2019 the Australian Ocean Lab (AusOcean)

  It is free software: you can redistribute it and/or modify them
  under the terms of the GNU General Public License as published by the
  Free Software Foundation, either version 3 of the License, or (at your
  option) any later version.

  It is distributed in the hope that it will be useful, but WITHOUT
  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 for more details.

  You should have received a copy of the GNU General Public License
  along with revid in gpl.txt.  If not, see http://www.gnu.org/licenses.
*/

package psi

import (
	"bytes"
	"testing"
)

const (
	errNotExpectedOut = "Did not get expected output: \ngot : %v, \nwant: %v"
	errUnexpectedErr  = "Unexpected error: %v\n"
)

var (
	tstPsi1 = PSI{
		Pf:  0x00,
		Tid: 0x02,
		Ssi: true,
		Sl:  0x1c,
		Tss: &TSS{
			Tide: 0x01,
			V:    0,
			Cni:  true,
			Sn:   0,
			Lsn:  0,
			Sd: &PMT{
				Pcrpid: 0x0100, // wrong
				Pil:    10,
				Pd: []Desc{
					{
						Dt: TimeDescTag,
						Dl: TimeDataSize,
						Dd: make([]byte, TimeDataSize),
					},
				},
				Essd: &ESSD{
					St:   0x1b,
					Epid: 0x0100,
					Esil: 0x00,
				},
			},
		},
	}

	tstPsi2 = PSI{
		Pf:  0x00,
		Tid: 0x02,
		Ssi: true,
		Sl:  0x12,
		Tss: &TSS{
			Tide: 0x01,
			V:    0,
			Cni:  true,
			Sn:   0,
			Lsn:  0,
			Sd: &PMT{
				Pcrpid: 0x0100,
				Pil:    0,
				Essd: &ESSD{
					St:   0x1b,
					Epid: 0x0100,
					Esil: 0x00,
				},
			},
		},
	}

	tstPsi3 = PSI{
		Pf:  0x00,
		Tid: 0x02,
		Ssi: true,
		Sl:  0x3e,
		Tss: &TSS{
			Tide: 0x01,
			V:    0,
			Cni:  true,
			Sn:   0,
			Lsn:  0,
			Sd: &PMT{
				Pcrpid: 0x0100,
				Pil:    PmtTimeLocationPil,
				Pd: []Desc{
					{
						Dt: TimeDescTag,
						Dl: TimeDataSize,
						Dd: make([]byte, TimeDataSize),
					},
					{
						Dt: LocationDescTag,
						Dl: LocationDataSize,
						Dd: make([]byte, LocationDataSize),
					},
				},
				Essd: &ESSD{
					St:   0x1b,
					Epid: 0x0100,
					Esil: 0x00,
				},
			},
		},
	}
)

var (
	pmtTimeBytesResizedBigger = []byte{
		0x00, 0x02, 0xb0, 0x1e, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x0c,
		TimeDescTag,                                                // Descriptor tag
		0x0a,                                                       // Length of bytes to follow
		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, // timestamp
		0x1b, 0xe1, 0x00, 0xf0, 0x00,
	}

	pmtTimeBytesResizedSmaller = []byte{
		0x00, 0x02, 0xb0, 0x1a, 0x00, 0x01, 0xc1, 0x00, 0x00, 0xe1, 0x00, 0xf0, 0x08,
		TimeDescTag,                        // Descriptor tag
		0x06,                               // Length of bytes to follow
		0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // timestamp
		0x1b, 0xe1, 0x00, 0xf0, 0x00,
	}
)

// TestHasDescriptorExists checks that PSIBytes.HasDescriptor performs as expected
// when the PSI we're interested in has the descriptor of interest. HasDescriptor
// should return the descriptor bytes.
// TODO: HasDescriptor also returns index of descriptor - we should check this.
func TestHasDescriptorExists(t *testing.T) {
	p := PSIBytes(tstPsi3.Bytes())
	_, got := p.HasDescriptor(LocationDescTag)
	want := []byte{
		LocationDescTag,
		LocationDataSize,
	}
	want = append(want, make([]byte, LocationDataSize)...)
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestHasDescriptorAbsent checks that PSIBytes.HasDescriptor performs as expected
// when the PSI does not have the descriptor of interest. HasDescriptor should
// return a nil slice and a negative index.
// TODO: check index here as well.
func TestHasDescriptorAbsent(t *testing.T) {
	p := PSIBytes(tstPsi3.Bytes())
	const fakeTag = 236
	_, got := p.HasDescriptor(fakeTag)
	var want []byte
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestHasDescriptorNone checks that PSIBytes.HasDescriptor behaves as expected
// when the PSI does not have any descriptors. HasDescriptor should return a nil
// slice.
// TODO: again check index here.
func TestHasDescriptorNone(t *testing.T) {
	p := PSIBytes(tstPsi2.Bytes())
	_, got := p.HasDescriptor(LocationDescTag)
	var want []byte
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestProgramInfoLen checks that PSIBytes.ProgramInfoLen correctly extracts
// the program info length from a PSI.
func TestProgramInfoLen(t *testing.T) {
	p := PSIBytes(tstPsi1.Bytes())
	got := p.ProgramInfoLen()
	want := 10
	if got != want {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestDescriptors checks that PSIBytes.descriptors correctly returns the descriptors
// from a PSI when descriptors exist.
func TestDescriptors(t *testing.T) {
	p := PSIBytes(tstPsi1.Bytes())
	got := p.descriptors()
	want := []byte{
		TimeDescTag,
		TimeDataSize,
	}
	want = append(want, make([]byte, TimeDataSize)...)
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestDescriptors checks that PSIBYtes.desriptors correctly returns nil when
// we try to get descriptors from a psi without any descriptors.
func TestDescriptorsNone(t *testing.T) {
	p := PSIBytes(tstPsi2.Bytes())
	got := p.descriptors()
	var want []byte
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestCreateDescriptorEmpty checks that PSIBytes.createDescriptor correctly adds
// a descriptor to the descriptors list in a PSI when it has no descriptors already.
func TestCreateDescriptorEmpty(t *testing.T) {
	got := PSIBytes(tstPsi2.Bytes())
	got.createDescriptor(TimeDescTag, make([]byte, TimeDataSize))
	UpdateCrc(got[1:])
	want := PSIBytes(tstPsi1.Bytes())
	if !bytes.Equal(want, got) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestCreateDescriptorNotEmpty checks that PSIBytes.createDescriptor correctly adds
// a descriptor to the descriptors list in a PSI when it already has one with
// a different tag.
func TestCreateDescriptorNotEmpty(t *testing.T) {
	got := PSIBytes(tstPsi1.Bytes())
	got.createDescriptor(LocationDescTag, make([]byte, LocationDataSize))
	UpdateCrc(got[1:])
	want := PSIBytes(tstPsi3.Bytes())
	if !bytes.Equal(want, got) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestAddDescriptorEmpty checks that PSIBytes.AddDescriptor correctly adds a descriptor
// when there are no other descriptors present in the PSI.
func TestAddDescriptorEmpty(t *testing.T) {
	got := PSIBytes(tstPsi2.Bytes())
	if err := got.AddDescriptor(TimeDescTag, make([]byte, TimeDataSize)); err != nil {
		t.Errorf(errUnexpectedErr, err.Error())
	}
	want := PSIBytes(tstPsi1.Bytes())
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestAddDescriptorNonEmpty checks that PSIBytes.AddDescriptor correctly adds a
// descriptor when there is already a descriptor of a differing type in a PSI.
func TestAddDescriptorNonEmpty(t *testing.T) {
	got := PSIBytes(tstPsi1.Bytes())
	if err := got.AddDescriptor(LocationDescTag, make([]byte, LocationDataSize)); err != nil {
		t.Errorf(errUnexpectedErr, err.Error())
	}
	want := PSIBytes(tstPsi3.Bytes())
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestAddDescriptorUpdateSame checks that PSIBytes.AddDescriptor correctly updates data in a descriptor
// with the same given tag, with data being the same size. AddDescriptor should just copy new data into
// the descriptors data field.
func TestAddDescriptorUpdateSame(t *testing.T) {
	newData := [8]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
	want := PSIBytes(tstPsi2.Bytes())
	want.createDescriptor(TimeDescTag, newData[:])
	got := PSIBytes(tstPsi1.Bytes())
	if err := got.AddDescriptor(TimeDescTag, newData[:]); err != nil {
		t.Errorf(errUnexpectedErr, err.Error())
	}
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestAddDescriptorUpdateBigger checks that PSIBytes.AddDescriptor correctly resizes descriptor with same given tag
// to a bigger size and copies in new data. AddDescriptor should find descriptor with same tag, increase size of psi,
// shift data to make room for update descriptor, and then copy in the new data.
func TestAddDescriptorUpdateBigger(t *testing.T) {
	got := PSIBytes(tstPsi1.Bytes())
	if err := got.AddDescriptor(TimeDescTag, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}); err != nil {
		t.Errorf(errUnexpectedErr, err.Error())
	}
	want := AddCrc(pmtTimeBytesResizedBigger)
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}

// TestAddDescriptorUpdateSmaller checks that PSIBytes.AddDescriptor correctly resizes descriptor with same given tag
// in a psi to a smaller size and copies in new data. AddDescriptor should find tag with same descrtiptor, shift data
// after descriptor upwards, trim the psi to new size, and then copy in new data.
func TestAddDescriptorUpdateSmaller(t *testing.T) {
	got := PSIBytes(tstPsi1.Bytes())
	if err := got.AddDescriptor(TimeDescTag, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}); err != nil {
		t.Errorf(errUnexpectedErr, err.Error())
	}
	want := AddCrc(pmtTimeBytesResizedSmaller)
	if !bytes.Equal(got, want) {
		t.Errorf(errNotExpectedOut, got, want)
	}
}