package rardecode

import (
	"errors"
	"io"
)

const (
	minWindowSize    = 0x40000
	maxQueuedFilters = 8192
)

var (
	errTooManyFilters = errors.New("rardecode: too many filters")
	errInvalidFilter  = errors.New("rardecode: invalid filter")
)

// filter functions take a byte slice, the current output offset and
// returns transformed data.
type filter func(b []byte, offset int64) ([]byte, error)

// filterBlock is a block of data to be processed by a filter.
type filterBlock struct {
	length int    // length of block
	offset int    // bytes to be read before start of block
	reset  bool   // drop all existing queued filters
	filter filter // filter function
}

// decoder is the interface for decoding compressed data
type decoder interface {
	init(r io.ByteReader, reset bool) error // initialize decoder for current file
	fill(w *window) ([]*filterBlock, error) // fill window with decoded data, returning any filters
}

// window is a sliding window buffer.
type window struct {
	buf  []byte
	mask int // buf length mask
	r    int // index in buf for reads (beginning)
	w    int // index in buf for writes (end)
	l    int // length of bytes to be processed by copyBytes
	o    int // offset of bytes to be processed by copyBytes
}

// buffered returns the number of bytes yet to be read from window
func (w *window) buffered() int { return (w.w - w.r) & w.mask }

// available returns the number of bytes that can be written before the window is full
func (w *window) available() int { return (w.r - w.w - 1) & w.mask }

func (w *window) reset(log2size uint, clear bool) {
	size := 1 << log2size
	if size < minWindowSize {
		size = minWindowSize
	}
	if size > len(w.buf) {
		b := make([]byte, size)
		if clear {
			w.w = 0
		} else if len(w.buf) > 0 {
			n := copy(b, w.buf[w.w:])
			n += copy(b[n:], w.buf[:w.w])
			w.w = n
		}
		w.buf = b
		w.mask = size - 1
	} else if clear {
		for i := range w.buf {
			w.buf[i] = 0
		}
		w.w = 0
	}
	w.r = w.w
}

// writeByte writes c to the end of the window
func (w *window) writeByte(c byte) {
	w.buf[w.w] = c
	w.w = (w.w + 1) & w.mask
}

// copyBytes copies len bytes at off distance from the end
// to the end of the window.
func (w *window) copyBytes(len, off int) {
	len &= w.mask

	n := w.available()
	if len > n {
		// if there is not enough space availaible we copy
		// as much as we can and save the offset and length
		// of the remaining data to be copied later.
		w.l = len - n
		w.o = off
		len = n
	}

	i := (w.w - off) & w.mask
	for ; len > 0; len-- {
		w.buf[w.w] = w.buf[i]
		w.w = (w.w + 1) & w.mask
		i = (i + 1) & w.mask
	}
}

// read reads bytes from the beginning of the window into p
func (w *window) read(p []byte) (n int) {
	if w.r > w.w {
		n = copy(p, w.buf[w.r:])
		w.r = (w.r + n) & w.mask
		p = p[n:]
	}
	if w.r < w.w {
		l := copy(p, w.buf[w.r:w.w])
		w.r += l
		n += l
	}
	if w.l > 0 && n > 0 {
		// if we have successfully read data, copy any
		// leftover data from a previous copyBytes.
		l := w.l
		w.l = 0
		w.copyBytes(l, w.o)
	}
	return n
}

// decodeReader implements io.Reader for decoding compressed data in RAR archives.
type decodeReader struct {
	win     window  // sliding window buffer used as decode dictionary
	dec     decoder // decoder being used to unpack file
	tot     int64   // total bytes read
	buf     []byte  // filter input/output buffer
	outbuf  []byte  // filter output not yet read
	err     error
	filters []*filterBlock // list of filterBlock's, each with offset relative to previous in list
}

func (d *decodeReader) init(r io.ByteReader, dec decoder, winsize uint, reset bool) error {
	if reset {
		d.filters = nil
	}
	d.err = nil
	d.outbuf = nil
	d.tot = 0
	d.win.reset(winsize, reset)
	d.dec = dec
	return d.dec.init(r, reset)
}

func (d *decodeReader) readErr() error {
	err := d.err
	d.err = nil
	return err
}

// queueFilter adds a filterBlock to the end decodeReader's filters.
func (d *decodeReader) queueFilter(f *filterBlock) error {
	if f.reset {
		d.filters = nil
	}
	if len(d.filters) >= maxQueuedFilters {
		return errTooManyFilters
	}
	// make offset relative to previous filter in list
	for _, fb := range d.filters {
		if f.offset < fb.offset {
			// filter block must not start before previous filter
			return errInvalidFilter
		}
		f.offset -= fb.offset
	}
	// offset & length must be < window size
	f.offset &= d.win.mask
	f.length &= d.win.mask
	d.filters = append(d.filters, f)
	return nil
}

// processFilters processes any filters valid at the current read index
// and stores the output in outbuf.
func (d *decodeReader) processFilters() (err error) {
	f := d.filters[0]
	if f.offset > 0 {
		return nil
	}
	d.filters = d.filters[1:]
	if d.win.buffered() < f.length {
		// fill() didn't return enough bytes
		err = d.readErr()
		if err == nil || err == io.EOF {
			return errInvalidFilter
		}
		return err
	}

	if cap(d.buf) < f.length {
		d.buf = make([]byte, f.length)
	}
	d.outbuf = d.buf[:f.length]
	n := d.win.read(d.outbuf)
	for {
		// run filter passing buffer and total bytes read so far
		d.outbuf, err = f.filter(d.outbuf, d.tot)
		if err != nil {
			return err
		}
		if cap(d.outbuf) > cap(d.buf) {
			// Filter returned a bigger buffer, save it for future filters.
			d.buf = d.outbuf
		}
		if len(d.filters) == 0 {
			return nil
		}
		f = d.filters[0]

		if f.offset != 0 {
			// next filter not at current offset
			f.offset -= n
			return nil
		}
		if f.length != len(d.outbuf) {
			return errInvalidFilter
		}
		d.filters = d.filters[1:]

		if cap(d.outbuf) < cap(d.buf) {
			// Filter returned a smaller buffer. Copy it back to the saved buffer
			// so the next filter can make use of the larger buffer if needed.
			d.outbuf = append(d.buf[:0], d.outbuf...)
		}
	}
}

// fill fills the decodeReader's window
func (d *decodeReader) fill() {
	if d.err != nil {
		return
	}
	var fl []*filterBlock
	fl, d.err = d.dec.fill(&d.win) // fill window using decoder
	for _, f := range fl {
		err := d.queueFilter(f)
		if err != nil {
			d.err = err
			return
		}
	}
}

// Read decodes data and stores it in p.
func (d *decodeReader) Read(p []byte) (n int, err error) {
	if len(d.outbuf) == 0 {
		// no filter output, see if we need to create more
		if d.win.buffered() == 0 {
			// fill empty window
			d.fill()
			if d.win.buffered() == 0 {
				return 0, d.readErr()
			}
		} else if len(d.filters) > 0 {
			f := d.filters[0]
			if f.offset == 0 && f.length > d.win.buffered() {
				d.fill() // filter at current offset needs more data
			}
		}
		if len(d.filters) > 0 {
			if err := d.processFilters(); err != nil {
				return 0, err
			}
		}
	}
	if len(d.outbuf) > 0 {
		// copy filter output into p
		n = copy(p, d.outbuf)
		d.outbuf = d.outbuf[n:]
	} else if len(d.filters) > 0 {
		f := d.filters[0]
		if f.offset < len(p) {
			// only read data up to beginning of next filter
			p = p[:f.offset]
		}
		n = d.win.read(p) // read directly from window
		f.offset -= n     // adjust first filter offset by bytes just read
	} else {
		n = d.win.read(p) // read directly from window
	}
	d.tot += int64(n)
	return n, nil
}
