package zipstream

import (
	"archive/zip"
	"bufio"
	"bytes"
	"encoding/binary"
	"io"
)

type descriptorReader struct {
	br         *bufio.Reader
	size       uint64
	eof        bool
	fileHeader *zip.FileHeader
}

var (
	sigBytes = []byte{0x50, 0x4b}
)

func (r *descriptorReader) Read(p []byte) (n int, err error) {
	if r.eof {
		return 0, io.EOF
	}

	if n = len(p); n > maxRead {
		n = maxRead
	}

	z, err := r.br.Peek(n + readAhead)
	if err != nil {
		if err == io.EOF && len(z) < 46+22 { // Min length of Central directory + End of central directory
			return 0, err
		}
		n = len(z)
	}

	// Look for header of next file or central directory
	discard := n
	s := 16
	for !r.eof && s < n {
		i := bytes.Index(z[s:len(z)-4], sigBytes) + s
		if i == -1 {
			break
		}

		// If directoryHeaderSignature or fileHeaderSignature file could be finished
		if sig := binary.LittleEndian.Uint32(z[i : i+4]); sig == fileHeaderSignature ||
			sig == directoryHeaderSignature {

			// Now check for compressed file sizes to ensure not false positive and if zip64.

			if i < len(z)-8 { // Zip32
				// Zip32 optional dataDescriptorSignature
				offset := 0
				if binary.LittleEndian.Uint32(z[i-16:i-12]) == dataDescriptorSignature {
					offset = 4
				}

				// Zip32 compressed file size
				if binary.LittleEndian.Uint32(z[i-8:i-4]) == uint32(r.size)+uint32(i-12-offset) {
					n, discard = i-12-offset, i
					r.eof = true
					r.fileHeader.CRC32 = binary.LittleEndian.Uint32(z[i-12 : i-8])
					break
				}
			}

			if i > 24 {
				// Zip64 optional dataDescriptorSignature
				offset := 0
				if binary.LittleEndian.Uint32(z[i-24:i-20]) == dataDescriptorSignature {
					offset = 4
				}

				// Zip64 compressed file size
				if i >= 8 && binary.LittleEndian.Uint64(z[i-16:i-8]) == r.size+uint64(i-20-offset) {
					n, discard = i-20-offset, i
					r.eof = true
					break
				}
			}
		}

		s = i + 2
	}
	copy(p, z[:n])
	r.br.Discard(discard)
	r.size += uint64(n)
	return
}