// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package tar

import (
	"bytes";
	"fmt";
	"io";
	"testing";
	"testing/iotest";
)

type writerTestEntry struct {
	header		*Header;
	contents	string;
}

type writerTest struct {
	file	string;	// filename of expected output
	entries	[]*writerTestEntry;
}

var writerTests = []*writerTest{
	&writerTest{
		file: "testdata/writer.tar",
		entries: []*writerTestEntry{
			&writerTestEntry{
				header: &Header{
					Name: "small.txt",
					Mode: 0640,
					Uid: 73025,
					Gid: 5000,
					Size: 5,
					Mtime: 1246508266,
					Typeflag: '0',
					Uname: "dsymonds",
					Gname: "eng",
				},
				contents: "Kilts",
			},
			&writerTestEntry{
				header: &Header{
					Name: "small2.txt",
					Mode: 0640,
					Uid: 73025,
					Gid: 5000,
					Size: 11,
					Mtime: 1245217492,
					Typeflag: '0',
					Uname: "dsymonds",
					Gname: "eng",
				},
				contents: "Google.com\n",
			},
		},
	},
	// The truncated test file was produced using these commands:
	//   dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt
	//   tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar
	&writerTest{
		file: "testdata/writer-big.tar",
		entries: []*writerTestEntry{
			&writerTestEntry{
				header: &Header{
					Name: "tmp/16gig.txt",
					Mode: 0640,
					Uid: 73025,
					Gid: 5000,
					Size: 16<<30,
					Mtime: 1254699560,
					Typeflag: '0',
					Uname: "dsymonds",
					Gname: "eng",
				},
				// no contents
			},
		},
	},
}

// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection.
func bytestr(offset int, b []byte) string {
	const rowLen = 32;
	s := fmt.Sprintf("%04x ", offset);
	for _, ch := range b {
		switch {
		case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z':
			s += fmt.Sprintf("  %c", ch);
		default:
			s += fmt.Sprintf(" %02x", ch);
		}
	}
	return s;
}

// Render a pseudo-diff between two blocks of bytes.
func bytediff(a []byte, b []byte) string {
	const rowLen = 32;
	s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b));
	for offset := 0; len(a)+len(b) > 0; offset += rowLen {
		na, nb := rowLen, rowLen;
		if na > len(a) {
			na = len(a);
		}
		if nb > len(b) {
			nb = len(b);
		}
		sa := bytestr(offset, a[0:na]);
		sb := bytestr(offset, b[0:nb]);
		if sa != sb {
			s += fmt.Sprintf("-%v\n+%v\n", sa, sb);
		}
		a = a[na:len(a)];
		b = b[nb:len(b)];
	}
	return s;
}

func TestWriter(t *testing.T) {
testLoop:
	for i, test := range writerTests {
		expected, err := io.ReadFile(test.file);
		if err != nil {
			t.Errorf("test %d: Unexpected error: %v", i, err);
			continue;
		}

		buf := new(bytes.Buffer);
		tw := NewWriter(iotest.TruncateWriter(buf, 4<<10));	// only catch the first 4 KB
		for j, entry := range test.entries {
			if err := tw.WriteHeader(entry.header); err != nil {
				t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err);
				continue testLoop;
			}
			if _, err := io.WriteString(tw, entry.contents); err != nil {
				t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err);
				continue testLoop;
			}
		}
		if err := tw.Close(); err != nil {
			t.Errorf("test %d: Failed closing archive: %v", err);
			continue testLoop;
		}

		actual := buf.Bytes();
		if !bytes.Equal(expected, actual) {
			t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v",
				i, bytediff(expected, actual));
		}
	}
}
