// Package bitseq provides a structure and utilities for representing a long
// bitmask which is persisted in a datastore. It is backed by [bitmap.Bitmap]
// which operates directly on the encoded representation, without uncompressing.
package bitseq

import (
	"encoding/json"
	"fmt"
	"sync"

	"github.com/docker/docker/libnetwork/bitmap"
	"github.com/docker/docker/libnetwork/datastore"
	"github.com/docker/docker/libnetwork/types"
)

var (
	// ErrNoBitAvailable is returned when no more bits are available to set
	ErrNoBitAvailable = bitmap.ErrNoBitAvailable
	// ErrBitAllocated is returned when the specific bit requested is already set
	ErrBitAllocated = bitmap.ErrBitAllocated
)

// Handle contains the sequence representing the bitmask and its identifier
type Handle struct {
	app      string
	id       string
	dbIndex  uint64
	dbExists bool
	store    datastore.DataStore
	bm       *bitmap.Bitmap
	mu       sync.Mutex
}

// NewHandle returns a thread-safe instance of the bitmask handler
func NewHandle(app string, ds datastore.DataStore, id string, numElements uint64) (*Handle, error) {
	h := &Handle{
		bm:    bitmap.New(numElements),
		app:   app,
		id:    id,
		store: ds,
	}

	if h.store == nil {
		return h, nil
	}

	// Get the initial status from the ds if present.
	if err := h.store.GetObject(datastore.Key(h.Key()...), h); err != nil && err != datastore.ErrKeyNotFound {
		return nil, err
	}

	// If the handle is not in store, write it.
	if !h.Exists() {
		if err := h.writeToStore(); err != nil {
			return nil, fmt.Errorf("failed to write bitsequence to store: %v", err)
		}
	}

	return h, nil
}

func (h *Handle) getCopy() *Handle {
	return &Handle{
		bm:       bitmap.Copy(h.bm),
		app:      h.app,
		id:       h.id,
		dbIndex:  h.dbIndex,
		dbExists: h.dbExists,
		store:    h.store,
	}
}

// SetAnyInRange atomically sets the first unset bit in the specified range in the sequence and returns the corresponding ordinal
func (h *Handle) SetAnyInRange(start, end uint64, serial bool) (uint64, error) {
	return h.apply(func(b *bitmap.Bitmap) (uint64, error) { return b.SetAnyInRange(start, end, serial) })
}

// SetAny atomically sets the first unset bit in the sequence and returns the corresponding ordinal
func (h *Handle) SetAny(serial bool) (uint64, error) {
	return h.apply(func(b *bitmap.Bitmap) (uint64, error) { return b.SetAny(serial) })
}

// Set atomically sets the corresponding bit in the sequence
func (h *Handle) Set(ordinal uint64) error {
	_, err := h.apply(func(b *bitmap.Bitmap) (uint64, error) { return 0, b.Set(ordinal) })
	return err
}

// Unset atomically unsets the corresponding bit in the sequence
func (h *Handle) Unset(ordinal uint64) error {
	_, err := h.apply(func(b *bitmap.Bitmap) (uint64, error) { return 0, b.Unset(ordinal) })
	return err
}

// IsSet atomically checks if the ordinal bit is set. In case ordinal
// is outside of the bit sequence limits, false is returned.
func (h *Handle) IsSet(ordinal uint64) bool {
	h.mu.Lock()
	defer h.mu.Unlock()
	return h.bm.IsSet(ordinal)
}

// set/reset the bit
func (h *Handle) apply(op func(*bitmap.Bitmap) (uint64, error)) (uint64, error) {
	for {
		var store datastore.DataStore
		h.mu.Lock()
		store = h.store
		if store != nil {
			h.mu.Unlock() // The lock is acquired in the GetObject
			if err := store.GetObject(datastore.Key(h.Key()...), h); err != nil && err != datastore.ErrKeyNotFound {
				return 0, err
			}
			h.mu.Lock() // Acquire the lock back
		}

		// Create a private copy of h and work on it
		nh := h.getCopy()

		ret, err := op(nh.bm)
		if err != nil {
			h.mu.Unlock()
			return ret, err
		}

		if h.store != nil {
			h.mu.Unlock()
			// Attempt to write private copy to store
			if err := nh.writeToStore(); err != nil {
				if _, ok := err.(types.RetryError); !ok {
					return ret, fmt.Errorf("internal failure while setting the bit: %v", err)
				}
				// Retry
				continue
			}
			h.mu.Lock()
		}

		// Previous atomic push was successful. Save private copy to local copy
		h.bm = nh.bm
		h.dbExists = nh.dbExists
		h.dbIndex = nh.dbIndex
		h.mu.Unlock()
		return ret, nil
	}
}

// Destroy removes from the datastore the data belonging to this handle
func (h *Handle) Destroy() error {
	for {
		if err := h.deleteFromStore(); err != nil {
			if _, ok := err.(types.RetryError); !ok {
				return fmt.Errorf("internal failure while destroying the sequence: %v", err)
			}
			// Fetch latest
			if err := h.store.GetObject(datastore.Key(h.Key()...), h); err != nil {
				if err == datastore.ErrKeyNotFound { // already removed
					return nil
				}
				return fmt.Errorf("failed to fetch from store when destroying the sequence: %v", err)
			}
			continue
		}
		return nil
	}
}

// Bits returns the length of the bit sequence
func (h *Handle) Bits() uint64 {
	h.mu.Lock()
	defer h.mu.Unlock()
	return h.bm.Bits()
}

// Unselected returns the number of bits which are not selected
func (h *Handle) Unselected() uint64 {
	h.mu.Lock()
	defer h.mu.Unlock()
	return h.bm.Unselected()
}

func (h *Handle) String() string {
	h.mu.Lock()
	defer h.mu.Unlock()
	return fmt.Sprintf("App: %s, ID: %s, DBIndex: 0x%x, %s",
		h.app, h.id, h.dbIndex, h.bm)
}

type jsonMessage struct {
	ID       string         `json:"id"`
	Sequence *bitmap.Bitmap `json:"sequence"`
}

// MarshalJSON encodes h into a JSON message.
func (h *Handle) MarshalJSON() ([]byte, error) {
	h.mu.Lock()
	defer h.mu.Unlock()
	m := jsonMessage{ID: h.id, Sequence: h.bm}
	return json.Marshal(m)
}

// UnmarshalJSON decodes a JSON message into h.
func (h *Handle) UnmarshalJSON(data []byte) error {
	var m jsonMessage
	if err := json.Unmarshal(data, &m); err != nil {
		return err
	}

	h.mu.Lock()
	defer h.mu.Unlock()
	h.id, h.bm = m.ID, m.Sequence
	return nil
}
