package asm

import (
	"bytes"
	"fmt"
	"hash/crc64"
	"io"

	"github.com/vbatts/tar-split/tar/storage"
)

// NewOutputTarStream returns an io.ReadCloser that is an assemble tar archive
// stream.
//
// It takes a storage.FileGetter, for mapping the file payloads that are to be read in,
// and a storage.Unpacker, which has access to the rawbytes and file order
// metadata. With the combination of these two items, a precise assembled Tar
// archive is possible.
func NewOutputTarStream(fg storage.FileGetter, up storage.Unpacker) io.ReadCloser {
	// ... Since these are interfaces, this is possible, so let's not have a nil pointer
	if fg == nil || up == nil {
		return nil
	}
	pr, pw := io.Pipe()
	go func() {
		for {
			entry, err := up.Next()
			if err != nil {
				pw.CloseWithError(err)
				return
			}
			switch entry.Type {
			case storage.SegmentType:
				if _, err := pw.Write(entry.Payload); err != nil {
					pw.CloseWithError(err)
					return
				}
			case storage.FileType:
				if entry.Size == 0 {
					continue
				}
				fh, err := fg.Get(entry.Name)
				if err != nil {
					pw.CloseWithError(err)
					return
				}
				c := crc64.New(storage.CRCTable)
				tRdr := io.TeeReader(fh, c)
				if _, err := io.Copy(pw, tRdr); err != nil {
					fh.Close()
					pw.CloseWithError(err)
					return
				}
				if !bytes.Equal(c.Sum(nil), entry.Payload) {
					// I would rather this be a comparable ErrInvalidChecksum or such,
					// but since it's coming through the PipeReader, the context of
					// _which_ file would be lost...
					fh.Close()
					pw.CloseWithError(fmt.Errorf("file integrity checksum failed for %q", entry.Name))
					return
				}
				fh.Close()
			}
		}
		pw.Close()
	}()
	return pr
}
