// Copyright (C) 2015 Scaleway. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE.md file.

package api

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"

	"github.com/moul/anonuuid"
	"github.com/renstrom/fuzzysearch/fuzzy"
)

const (
	// CacheRegion permits to access at the region field
	CacheRegion = iota
	// CacheArch permits to access at the arch field
	CacheArch
	// CacheOwner permits to access at the owner field
	CacheOwner
	// CacheTitle permits to access at the title field
	CacheTitle
	// CacheMarketPlaceUUID is used to determine the UUID of local images
	CacheMarketPlaceUUID
	// CacheMaxfield is used to determine the size of array
	CacheMaxfield
)

// ScalewayCache is used not to query the API to resolve full identifiers
type ScalewayCache struct {
	// Images contains names of Scaleway images indexed by identifier
	Images map[string][CacheMaxfield]string `json:"images"`

	// Snapshots contains names of Scaleway snapshots indexed by identifier
	Snapshots map[string][CacheMaxfield]string `json:"snapshots"`

	// Volumes contains names of Scaleway volumes indexed by identifier
	Volumes map[string][CacheMaxfield]string `json:"volumes"`

	// Bootscripts contains names of Scaleway bootscripts indexed by identifier
	Bootscripts map[string][CacheMaxfield]string `json:"bootscripts"`

	// Servers contains names of Scaleway servers indexed by identifier
	Servers map[string][CacheMaxfield]string `json:"servers"`

	// Path is the path to the cache file
	Path string `json:"-"`

	// Modified tells if the cache needs to be overwritten or not
	Modified bool `json:"-"`

	// Lock allows ScalewayCache to be used concurrently
	Lock sync.Mutex `json:"-"`

	hookSave func()
}

const (
	// IdentifierUnknown is used when we don't know explicitly the type key of the object (used for nil comparison)
	IdentifierUnknown = 1 << iota
	// IdentifierServer is the type key of cached server objects
	IdentifierServer
	// IdentifierImage is the type key of cached image objects
	IdentifierImage
	// IdentifierSnapshot is the type key of cached snapshot objects
	IdentifierSnapshot
	// IdentifierBootscript is the type key of cached bootscript objects
	IdentifierBootscript
	// IdentifierVolume is the type key of cached volume objects
	IdentifierVolume
)

// ScalewayResolverResult is a structure containing human-readable information
// about resolver results. This structure is used to display the user choices.
type ScalewayResolverResult struct {
	Identifier string
	Type       int
	Name       string
	Arch       string
	Needle     string
	RankMatch  int
	Region     string
}

// ScalewayResolverResults is a list of `ScalewayResolverResult`
type ScalewayResolverResults []ScalewayResolverResult

// NewScalewayResolverResult returns a new ScalewayResolverResult
func NewScalewayResolverResult(Identifier, Name, Arch, Region string, Type int) (ScalewayResolverResult, error) {
	if err := anonuuid.IsUUID(Identifier); err != nil {
		return ScalewayResolverResult{}, err
	}
	return ScalewayResolverResult{
		Identifier: Identifier,
		Type:       Type,
		Name:       Name,
		Arch:       Arch,
		Region:     Region,
	}, nil
}

func (s ScalewayResolverResults) Len() int {
	return len(s)
}

func (s ScalewayResolverResults) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

func (s ScalewayResolverResults) Less(i, j int) bool {
	return s[i].RankMatch < s[j].RankMatch
}

// TruncIdentifier returns first 8 characters of an Identifier (UUID)
func (s *ScalewayResolverResult) TruncIdentifier() string {
	return s.Identifier[:8]
}

func identifierTypeName(kind int) string {
	switch kind {
	case IdentifierServer:
		return "Server"
	case IdentifierImage:
		return "Image"
	case IdentifierSnapshot:
		return "Snapshot"
	case IdentifierVolume:
		return "Volume"
	case IdentifierBootscript:
		return "Bootscript"
	}
	return ""
}

// CodeName returns a full resource name with typed prefix
func (s *ScalewayResolverResult) CodeName() string {
	name := strings.ToLower(s.Name)
	name = regexp.MustCompile(`[^a-z0-9-]`).ReplaceAllString(name, "-")
	name = regexp.MustCompile(`--+`).ReplaceAllString(name, "-")
	name = strings.Trim(name, "-")

	return fmt.Sprintf("%s:%s", strings.ToLower(identifierTypeName(s.Type)), name)
}

// FilterByArch deletes the elements which not match with arch
func (s *ScalewayResolverResults) FilterByArch(arch string) {
REDO:
	for i := range *s {
		if (*s)[i].Arch != arch {
			(*s)[i] = (*s)[len(*s)-1]
			*s = (*s)[:len(*s)-1]
			goto REDO
		}
	}
}

// NewScalewayCache loads a per-user cache
func NewScalewayCache(hookSave func()) (*ScalewayCache, error) {
	var cache ScalewayCache

	cache.hookSave = hookSave
	homeDir := os.Getenv("HOME") // *nix
	if homeDir == "" {           // Windows
		homeDir = os.Getenv("USERPROFILE")
	}
	if homeDir == "" {
		homeDir = "/tmp"
	}
	cachePath := filepath.Join(homeDir, ".scw-cache.db")
	cache.Path = cachePath
	_, err := os.Stat(cachePath)
	if os.IsNotExist(err) {
		cache.Clear()
		return &cache, nil
	} else if err != nil {
		return nil, err
	}
	file, err := ioutil.ReadFile(cachePath)
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal(file, &cache)
	if err != nil {
		// fix compatibility with older version
		if err = os.Remove(cachePath); err != nil {
			return nil, err
		}
		cache.Clear()
		return &cache, nil
	}
	if cache.Images == nil {
		cache.Images = make(map[string][CacheMaxfield]string)
	}
	if cache.Snapshots == nil {
		cache.Snapshots = make(map[string][CacheMaxfield]string)
	}
	if cache.Volumes == nil {
		cache.Volumes = make(map[string][CacheMaxfield]string)
	}
	if cache.Servers == nil {
		cache.Servers = make(map[string][CacheMaxfield]string)
	}
	if cache.Bootscripts == nil {
		cache.Bootscripts = make(map[string][CacheMaxfield]string)
	}
	return &cache, nil
}

// Clear removes all information from the cache
func (c *ScalewayCache) Clear() {
	c.Images = make(map[string][CacheMaxfield]string)
	c.Snapshots = make(map[string][CacheMaxfield]string)
	c.Volumes = make(map[string][CacheMaxfield]string)
	c.Bootscripts = make(map[string][CacheMaxfield]string)
	c.Servers = make(map[string][CacheMaxfield]string)
	c.Modified = true
}

// Flush flushes the cache database
func (c *ScalewayCache) Flush() error {
	return os.Remove(c.Path)
}

// Save atomically overwrites the current cache database
func (c *ScalewayCache) Save() error {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	c.hookSave()
	if c.Modified {
		file, err := ioutil.TempFile(filepath.Dir(c.Path), filepath.Base(c.Path))
		if err != nil {
			return err
		}

		if err := json.NewEncoder(file).Encode(c); err != nil {
			file.Close()
			os.Remove(file.Name())
			return err
		}

		file.Close()
		if err := os.Rename(file.Name(), c.Path); err != nil {
			os.Remove(file.Name())
			return err
		}
	}
	return nil
}

// ComputeRankMatch fills `ScalewayResolverResult.RankMatch` with its `fuzzy` score
func (s *ScalewayResolverResult) ComputeRankMatch(needle string) {
	s.Needle = needle
	s.RankMatch = fuzzy.RankMatch(needle, s.Name)
}

// LookUpImages attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpImages(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res ScalewayResolverResults
	var exactMatches ScalewayResolverResults

	if acceptUUID && anonuuid.IsUUID(needle) == nil {
		if fields, ok := c.Images[needle]; ok {
			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
	// FIXME: if 'user/' is in needle, only watch for a user image
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, fields := range c.Images {
		if fields[CacheTitle] == needle {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			exactMatches = append(exactMatches, entry)
		}
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		} else if strings.HasPrefix(fields[CacheMarketPlaceUUID], needle) || nameRegex.MatchString(fields[CacheMarketPlaceUUID]) {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierImage)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	if len(exactMatches) == 1 {
		return exactMatches, nil
	}

	return removeDuplicatesResults(res), nil
}

// LookUpSnapshots attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpSnapshots(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res ScalewayResolverResults
	var exactMatches ScalewayResolverResults

	if acceptUUID && anonuuid.IsUUID(needle) == nil {
		if fields, ok := c.Snapshots[needle]; ok {
			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	needle = regexp.MustCompile(`^user/`).ReplaceAllString(needle, "")
	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, fields := range c.Snapshots {
		if fields[CacheTitle] == needle {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			exactMatches = append(exactMatches, entry)
		}
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierSnapshot)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	if len(exactMatches) == 1 {
		return exactMatches, nil
	}

	return removeDuplicatesResults(res), nil
}

// LookUpVolumes attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpVolumes(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res ScalewayResolverResults
	var exactMatches ScalewayResolverResults

	if acceptUUID && anonuuid.IsUUID(needle) == nil {
		if fields, ok := c.Volumes[needle]; ok {
			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, fields := range c.Volumes {
		if fields[CacheTitle] == needle {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			exactMatches = append(exactMatches, entry)
		}
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierVolume)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	if len(exactMatches) == 1 {
		return exactMatches, nil
	}

	return removeDuplicatesResults(res), nil
}

// LookUpBootscripts attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpBootscripts(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res ScalewayResolverResults
	var exactMatches ScalewayResolverResults

	if acceptUUID && anonuuid.IsUUID(needle) == nil {
		if fields, ok := c.Bootscripts[needle]; ok {
			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, fields := range c.Bootscripts {
		if fields[CacheTitle] == needle {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			exactMatches = append(exactMatches, entry)
		}
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierBootscript)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	if len(exactMatches) == 1 {
		return exactMatches, nil
	}

	return removeDuplicatesResults(res), nil
}

// LookUpServers attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpServers(needle string, acceptUUID bool) (ScalewayResolverResults, error) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	var res ScalewayResolverResults
	var exactMatches ScalewayResolverResults

	if acceptUUID && anonuuid.IsUUID(needle) == nil {
		if fields, ok := c.Servers[needle]; ok {
			entry, err := NewScalewayResolverResult(needle, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	nameRegex := regexp.MustCompile(`(?i)` + regexp.MustCompile(`[_-]`).ReplaceAllString(needle, ".*"))
	for identifier, fields := range c.Servers {
		if fields[CacheTitle] == needle {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			exactMatches = append(exactMatches, entry)
		}
		if strings.HasPrefix(identifier, needle) || nameRegex.MatchString(fields[CacheTitle]) {
			entry, err := NewScalewayResolverResult(identifier, fields[CacheTitle], fields[CacheArch], fields[CacheRegion], IdentifierServer)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			res = append(res, entry)
		}
	}

	if len(exactMatches) == 1 {
		return exactMatches, nil
	}

	return removeDuplicatesResults(res), nil
}

// removeDuplicatesResults transforms an array into a unique array
func removeDuplicatesResults(elements ScalewayResolverResults) ScalewayResolverResults {
	encountered := map[string]ScalewayResolverResult{}

	// Create a map of all unique elements.
	for v := range elements {
		encountered[elements[v].Identifier] = elements[v]
	}

	// Place all keys from the map into a slice.
	results := ScalewayResolverResults{}
	for _, result := range encountered {
		results = append(results, result)
	}
	return results
}

// parseNeedle parses a user needle and try to extract a forced object type
// i.e:
//   - server:blah-blah -> kind=server, needle=blah-blah
//   - blah-blah -> kind="", needle=blah-blah
//   - not-existing-type:blah-blah
func parseNeedle(input string) (identifierType int, needle string) {
	parts := strings.Split(input, ":")
	if len(parts) == 2 {
		switch parts[0] {
		case "server":
			return IdentifierServer, parts[1]
		case "image":
			return IdentifierImage, parts[1]
		case "snapshot":
			return IdentifierSnapshot, parts[1]
		case "bootscript":
			return IdentifierBootscript, parts[1]
		case "volume":
			return IdentifierVolume, parts[1]
		}
	}
	return IdentifierUnknown, input
}

// LookUpIdentifiers attempts to return identifiers matching a pattern
func (c *ScalewayCache) LookUpIdentifiers(needle string) (ScalewayResolverResults, error) {
	results := ScalewayResolverResults{}

	identifierType, needle := parseNeedle(needle)

	if identifierType&(IdentifierUnknown|IdentifierServer) > 0 {
		servers, err := c.LookUpServers(needle, false)
		if err != nil {
			return ScalewayResolverResults{}, err
		}
		for _, result := range servers {
			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierServer)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			results = append(results, entry)
		}
	}

	if identifierType&(IdentifierUnknown|IdentifierImage) > 0 {
		images, err := c.LookUpImages(needle, false)
		if err != nil {
			return ScalewayResolverResults{}, err
		}
		for _, result := range images {
			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierImage)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			results = append(results, entry)
		}
	}

	if identifierType&(IdentifierUnknown|IdentifierSnapshot) > 0 {
		snapshots, err := c.LookUpSnapshots(needle, false)
		if err != nil {
			return ScalewayResolverResults{}, err
		}
		for _, result := range snapshots {
			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierSnapshot)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			results = append(results, entry)
		}
	}

	if identifierType&(IdentifierUnknown|IdentifierVolume) > 0 {
		volumes, err := c.LookUpVolumes(needle, false)
		if err != nil {
			return ScalewayResolverResults{}, err
		}
		for _, result := range volumes {
			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierVolume)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			results = append(results, entry)
		}
	}

	if identifierType&(IdentifierUnknown|IdentifierBootscript) > 0 {
		bootscripts, err := c.LookUpBootscripts(needle, false)
		if err != nil {
			return ScalewayResolverResults{}, err
		}
		for _, result := range bootscripts {
			entry, err := NewScalewayResolverResult(result.Identifier, result.Name, result.Arch, result.Region, IdentifierBootscript)
			if err != nil {
				return ScalewayResolverResults{}, err
			}
			entry.ComputeRankMatch(needle)
			results = append(results, entry)
		}
	}
	return results, nil
}

// InsertServer registers a server in the cache
func (c *ScalewayCache) InsertServer(identifier, region, arch, owner, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	fields, exists := c.Servers[identifier]
	if !exists || fields[CacheTitle] != name {
		c.Servers[identifier] = [CacheMaxfield]string{region, arch, owner, name}
		c.Modified = true
	}
}

// RemoveServer removes a server from the cache
func (c *ScalewayCache) RemoveServer(identifier string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	delete(c.Servers, identifier)
	c.Modified = true
}

// ClearServers removes all servers from the cache
func (c *ScalewayCache) ClearServers() {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	c.Servers = make(map[string][CacheMaxfield]string)
	c.Modified = true
}

// InsertImage registers an image in the cache
func (c *ScalewayCache) InsertImage(identifier, region, arch, owner, name, marketPlaceUUID string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	fields, exists := c.Images[identifier]
	if !exists || fields[CacheTitle] != name {
		c.Images[identifier] = [CacheMaxfield]string{region, arch, owner, name, marketPlaceUUID}
		c.Modified = true
	}
}

// RemoveImage removes a server from the cache
func (c *ScalewayCache) RemoveImage(identifier string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	delete(c.Images, identifier)
	c.Modified = true
}

// ClearImages removes all images from the cache
func (c *ScalewayCache) ClearImages() {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	c.Images = make(map[string][CacheMaxfield]string)
	c.Modified = true
}

// InsertSnapshot registers an snapshot in the cache
func (c *ScalewayCache) InsertSnapshot(identifier, region, arch, owner, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	fields, exists := c.Snapshots[identifier]
	if !exists || fields[CacheTitle] != name {
		c.Snapshots[identifier] = [CacheMaxfield]string{region, arch, owner, name}
		c.Modified = true
	}
}

// RemoveSnapshot removes a server from the cache
func (c *ScalewayCache) RemoveSnapshot(identifier string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	delete(c.Snapshots, identifier)
	c.Modified = true
}

// ClearSnapshots removes all snapshots from the cache
func (c *ScalewayCache) ClearSnapshots() {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	c.Snapshots = make(map[string][CacheMaxfield]string)
	c.Modified = true
}

// InsertVolume registers an volume in the cache
func (c *ScalewayCache) InsertVolume(identifier, region, arch, owner, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	fields, exists := c.Volumes[identifier]
	if !exists || fields[CacheTitle] != name {
		c.Volumes[identifier] = [CacheMaxfield]string{region, arch, owner, name}
		c.Modified = true
	}
}

// RemoveVolume removes a server from the cache
func (c *ScalewayCache) RemoveVolume(identifier string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	delete(c.Volumes, identifier)
	c.Modified = true
}

// ClearVolumes removes all volumes from the cache
func (c *ScalewayCache) ClearVolumes() {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	c.Volumes = make(map[string][CacheMaxfield]string)
	c.Modified = true
}

// InsertBootscript registers an bootscript in the cache
func (c *ScalewayCache) InsertBootscript(identifier, region, arch, owner, name string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	fields, exists := c.Bootscripts[identifier]
	if !exists || fields[CacheTitle] != name {
		c.Bootscripts[identifier] = [CacheMaxfield]string{region, arch, owner, name}
		c.Modified = true
	}
}

// RemoveBootscript removes a bootscript from the cache
func (c *ScalewayCache) RemoveBootscript(identifier string) {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	delete(c.Bootscripts, identifier)
	c.Modified = true
}

// ClearBootscripts removes all bootscripts from the cache
func (c *ScalewayCache) ClearBootscripts() {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	c.Bootscripts = make(map[string][CacheMaxfield]string)
	c.Modified = true
}

// GetNbServers returns the number of servers in the cache
func (c *ScalewayCache) GetNbServers() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Servers)
}

// GetNbImages returns the number of images in the cache
func (c *ScalewayCache) GetNbImages() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Images)
}

// GetNbSnapshots returns the number of snapshots in the cache
func (c *ScalewayCache) GetNbSnapshots() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Snapshots)
}

// GetNbVolumes returns the number of volumes in the cache
func (c *ScalewayCache) GetNbVolumes() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Volumes)
}

// GetNbBootscripts returns the number of bootscripts in the cache
func (c *ScalewayCache) GetNbBootscripts() int {
	c.Lock.Lock()
	defer c.Lock.Unlock()

	return len(c.Bootscripts)
}
