package files

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
)

// ChunkSpec is a wrapper around an uncompressed array of chunk identifiers
type ChunkSpec []uint64

// CreateChunkSpec given a full list of chunks with their downloaded status (true for downloaded, false otherwise)
// derives a list of identifiers of chunks that have not been downloaded yet
func CreateChunkSpec(progress []bool) ChunkSpec {
	chunks := ChunkSpec{}
	for i, p := range progress {
		if !p {
			chunks = append(chunks, uint64(i))
		}
	}
	return chunks
}

// Deserialize takes in a compressed chunk spec and returns an uncompressed ChunkSpec or an error
// if the serialized chunk spec has format errors
func Deserialize(serialized string) (*ChunkSpec, error) {

	var chunkSpec ChunkSpec

	if len(serialized) == 0 {
		return &chunkSpec, nil
	}

	ranges := strings.Split(serialized, ",")
	for _, r := range ranges {
		parts := strings.Split(r, ":")
		if len(parts) == 1 {
			single, err := strconv.Atoi(r)
			if err != nil {
				return nil, errors.New("invalid chunk spec")
			}
			chunkSpec = append(chunkSpec, uint64(single))
		} else if len(parts) == 2 {
			start, err1 := strconv.Atoi(parts[0])
			end, err2 := strconv.Atoi(parts[1])
			if err1 != nil || err2 != nil {
				return nil, errors.New("invalid chunk spec")
			}
			for i := start; i <= end; i++ {
				chunkSpec = append(chunkSpec, uint64(i))
			}
		} else {
			return nil, errors.New("invalid chunk spec")
		}
	}
	return &chunkSpec, nil
}

// Serialize compresses the ChunkSpec into a list of inclusive ranges e.g. 1,2,3,5,6,7 becomes "1:3,5:7"
func (cs ChunkSpec) Serialize() string {
	result := ""

	i := 0

	for {
		if i >= len(cs) {
			break
		}
		j := i + 1
		for ; j < len(cs) && cs[j] == cs[j-1]+1; j++ {
		}

		if result != "" {
			result += ","
		}

		if j == i+1 {
			result += fmt.Sprintf("%d", cs[i])
		} else {
			result += fmt.Sprintf("%d:%d", cs[i], cs[j-1])
		}
		i = j
	}

	return result
}
