package housekeeper

import (
	"encoding"
	"errors"
	"io"
)

const cachedBeatmapBinSize = 8 + 1 + 15 + 15 + 8

func b2i(b bool) byte {
	if b {
		return 1
	}
	return 0
}

func marshalBinaryCopy(dst []byte, t encoding.BinaryMarshaler) {
	b, _ := t.MarshalBinary()
	copy(dst, b)
}

// copied from binary.BigEndian
func putUint64(b []byte, v uint64) {
	_ = b[7] // boundary check
	b[0] = byte(v >> 56)
	b[1] = byte(v >> 48)
	b[2] = byte(v >> 40)
	b[3] = byte(v >> 32)
	b[4] = byte(v >> 24)
	b[5] = byte(v >> 16)
	b[6] = byte(v >> 8)
	b[7] = byte(v)
}

func writeBeatmaps(w io.Writer, c []*CachedBeatmap) error {
	_, err := w.Write(append([]byte("CGBIN001"), cachedBeatmapBinSize))
	if err != nil {
		return err
	}
	for _, b := range c {
		if b == nil || !b.isDownloaded {
			continue
		}
		enc := make([]byte, cachedBeatmapBinSize)
		putUint64(enc[:8], uint64(b.ID))
		enc[8] = b2i(b.NoVideo)
		marshalBinaryCopy(enc[9:24], b.LastUpdate)
		marshalBinaryCopy(enc[24:39], b.lastRequested)
		putUint64(enc[39:47], b.fileSize)
		_, err := w.Write(enc)
		if err != nil {
			return err
		}
	}
	return nil
}

// copied from binary.BigEndian
func readUint64(b []byte) uint64 {
	_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
	return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
		uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56

}

func readCachedBeatmap(b []byte) *CachedBeatmap {
	m := &CachedBeatmap{}
	m.ID = int(readUint64(b[:8]))
	m.NoVideo = b[8] == 1
	(&m.LastUpdate).UnmarshalBinary(b[9:24])
	(&m.lastRequested).UnmarshalBinary(b[24:39])
	m.fileSize = readUint64(b[39:47])
	m.isDownloaded = true
	return m
}

func readBeatmaps(r io.Reader) ([]*CachedBeatmap, error) {
	b := make([]byte, 8)
	_, err := r.Read(b)
	if err != nil {
		return nil, err
	}
	if string(b) != "CGBIN001" {
		return nil, errors.New("unknown cgbin version")
	}

	b = make([]byte, 1)
	_, err = r.Read(b)
	if err != nil {
		return nil, err
	}

	bmLength := b[0]
	if bmLength == 0 {
		return nil, nil
	}
	b = make([]byte, bmLength)
	beatmaps := make([]*CachedBeatmap, 0, 50)

	for {
		read, err := r.Read(b)
		switch {
		case err == io.EOF:
			return beatmaps, nil
		case err != nil:
			return nil, err
		case byte(read) != bmLength:
			return beatmaps, nil
		}
		beatmaps = append(beatmaps, readCachedBeatmap(b))
	}
}
