package parser

import (
	"strconv"
)

type ReferencesOffset struct {
	Id  Id
	Len int32
}

type References struct {
	Items           *cache
	Offsets         *cache
	CurrentOffsetId Id
}

type SerializedReference struct {
	Path string `json:"path"`
}

func NewReferences(config Config) (*References, error) {
	tempPath := config.TempPath

	items, err := newCache(tempPath, "references", Item{})
	if err != nil {
		return nil, err
	}

	offsets, err := newCache(tempPath, "references-offsets", ReferencesOffset{})
	if err != nil {
		return nil, err
	}

	return &References{
		Items:           items,
		Offsets:         offsets,
		CurrentOffsetId: 0,
	}, nil
}

// Store is responsible for keeping track of references that will be used when
// serializing in `For`.
//
// The references are stored in a file to cache them. It is like
// `map[Id][]Item` (where `Id` is `refId`) but relies on caching the array and
// its offset in files for storage to reduce RAM usage. The items can be
// fetched by calling `getItems`.
func (r *References) Store(refId Id, references []Item) error {
	size := len(references)

	if size == 0 {
		return nil
	}

	items := append(r.getItems(refId), references...)
	err := r.Items.SetEntry(r.CurrentOffsetId, items)
	if err != nil {
		return err
	}

	size = len(items)
	r.Offsets.SetEntry(refId, ReferencesOffset{Id: r.CurrentOffsetId, Len: int32(size)})
	r.CurrentOffsetId += Id(size)

	return nil
}

func (r *References) For(docs map[Id]string, refId Id) []SerializedReference {
	references := r.getItems(refId)
	if references == nil {
		return nil
	}

	var serializedReferences []SerializedReference

	for _, reference := range references {
		serializedReference := SerializedReference{
			Path: docs[reference.DocId] + "#L" + strconv.Itoa(int(reference.Line)),
		}

		serializedReferences = append(serializedReferences, serializedReference)
	}

	return serializedReferences
}

func (r *References) Close() error {
	return combineErrors(
		r.Items.Close(),
		r.Offsets.Close(),
	)
}

func (r *References) getItems(refId Id) []Item {
	var offset ReferencesOffset
	if err := r.Offsets.Entry(refId, &offset); err != nil || offset.Len == 0 {
		return nil
	}

	items := make([]Item, offset.Len)
	if err := r.Items.Entry(offset.Id, &items); err != nil {
		return nil
	}

	return items
}
