// Copyright © 2013, 2014, The Go-LXC Authors. All rights reserved.
// Use of this source code is governed by a LGPLv2.1
// license that can be found in the LICENSE file.

//go:build linux && cgo
// +build linux,cgo

package lxc

// #include <lxc/lxccontainer.h>
import "C"

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

// Verbosity type
type Verbosity int

const (
	// Quiet makes some API calls not to write anything to stdout
	Quiet Verbosity = 1 << iota
	// Verbose makes some API calls write to stdout
	Verbose
)

// BackendStore type specifies possible backend types.
type BackendStore int

const (
	// Btrfs backendstore type
	Btrfs BackendStore = iota + 1
	// Directory backendstore type
	Directory
	// LVM backendstore type
	LVM
	// ZFS backendstore type
	ZFS
	// Aufs backendstore type
	Aufs
	// Overlayfs backendstore type
	Overlayfs
	// Loopback backendstore type
	Loopback
	// Best backendstore type
	Best
)

// BackendStore as string
func (t BackendStore) String() string {
	switch t {
	case Directory:
		return "dir"
	case ZFS:
		return "zfs"
	case Btrfs:
		return "btrfs"
	case LVM:
		return "lvm"
	case Aufs:
		return "aufs"
	case Overlayfs:
		return "overlayfs"
	case Loopback:
		return "loop"
	case Best:
		return "best"
	}
	return ""
}

var backendStoreMap = map[string]BackendStore{
	"dir":       Directory,
	"zfs":       ZFS,
	"btrfs":     Btrfs,
	"lvm":       LVM,
	"aufs":      Aufs,
	"overlayfs": Overlayfs,
	"loopback":  Loopback,
	"best":      Best,
}

// Set is the method to set the flag value, part of the flag.Value interface.
func (t *BackendStore) Set(value string) error {
	backend, ok := backendStoreMap[value]
	if ok {
		*t = backend
		return nil
	}
	return ErrUnknownBackendStore
}

// State type specifies possible container states.
type State int

const (
	// STOPPED means container is not running
	STOPPED State = iota + 1
	// STARTING means container is starting
	STARTING
	// RUNNING means container is running
	RUNNING
	// STOPPING means container is stopping
	STOPPING
	// ABORTING means container is aborting
	ABORTING
	// FREEZING means container is freezing
	FREEZING
	// FROZEN means containe is frozen
	FROZEN
	// THAWED means container is thawed
	THAWED
)

// StateMap provides the mapping betweens the state names and states
var StateMap = map[string]State{
	"STOPPED":  STOPPED,
	"STARTING": STARTING,
	"RUNNING":  RUNNING,
	"STOPPING": STOPPING,
	"ABORTING": ABORTING,
	"FREEZING": FREEZING,
	"FROZEN":   FROZEN,
	"THAWED":   THAWED,
}

// State as string
func (t State) String() string {
	switch t {
	case STOPPED:
		return "STOPPED"
	case STARTING:
		return "STARTING"
	case RUNNING:
		return "RUNNING"
	case STOPPING:
		return "STOPPING"
	case ABORTING:
		return "ABORTING"
	case FREEZING:
		return "FREEZING"
	case FROZEN:
		return "FROZEN"
	case THAWED:
		return "THAWED"
	}
	return ""
}

// Taken from http://golang.org/doc/effective_go.html#constants

// ByteSize type
type ByteSize float64

const (
	// B - byte
	B = iota

	// KB - kilobyte
	KB ByteSize = 1 << (10 * iota)

	// MB - megabyte
	MB

	// GB - gigabyte
	GB

	// TB - terabyte
	TB

	// PB - petabyte
	PB

	// EB - exabyte
	EB

	// ZB - zettabyte
	ZB

	// YB - yottabyte
	YB
)

func (b ByteSize) String() string {
	switch {
	case b >= YB:
		return fmt.Sprintf("%.2fYB", b/YB)
	case b >= ZB:
		return fmt.Sprintf("%.2fZB", b/ZB)
	case b >= EB:
		return fmt.Sprintf("%.2fEB", b/EB)
	case b >= PB:
		return fmt.Sprintf("%.2fPB", b/PB)
	case b >= TB:
		return fmt.Sprintf("%.2fTB", b/TB)
	case b >= GB:
		return fmt.Sprintf("%.2fGB", b/GB)
	case b >= MB:
		return fmt.Sprintf("%.2fMB", b/MB)
	case b >= KB:
		return fmt.Sprintf("%.2fKB", b/KB)
	}
	return fmt.Sprintf("%.2fB", b)
}

// Used to convert user input to ByteSize
var unitMap = map[string]ByteSize{
	"B":     B,
	"BYTE":  B,
	"BYTES": B,

	"KB":        KB,
	"KILOBYTE":  KB,
	"KILOBYTES": KB,

	"MB":        MB,
	"MEGABYTE":  MB,
	"MEGABYTES": MB,

	"GB":        GB,
	"GIGABYTE":  GB,
	"GIGABYTES": GB,

	"TB":        TB,
	"TERABYTE":  TB,
	"TERABYTES": TB,

	"PB":        PB,
	"PETABYTE":  PB,
	"PETABYTES": PB,

	"EB":       EB,
	"EXABYTE":  EB,
	"EXABYTES": EB,
}

// Inspired from https://github.com/inhies/go-bytesize

// ParseBytes parses a byte size string. A byte size string is a number followed by
// a unit suffix, such as "1024B" or "1 MB". Valid byte units are "B", "KB",
// "MB", "GB", "TB", "PB" and "EB". You can also use the long
// format of units, such as "kilobyte" or "kilobytes".
func ParseBytes(s string) (ByteSize, error) {
	// Remove leading and trailing space
	s = strings.TrimSpace(s)

	split := make([]string, 0)
	for i, r := range s {
		if !unicode.IsDigit(r) {
			// Split the string by digit and size designator, remove space
			split = append(split, strings.TrimSpace(string(s[:i])))
			split = append(split, strings.TrimSpace(string(s[i:])))
			break
		}
	}

	// Check to see if we split successfully
	if len(split) != 2 {
		return 0, errors.New("Unrecognized size suffix")
	}

	// Check for MB, MEGABYTE, and MEGABYTES
	unit, ok := unitMap[strings.ToUpper(split[1])]
	if !ok {
		return 0, errors.New("Unrecognized size suffix " + split[1])

	}

	value, err := strconv.ParseFloat(split[0], 64)
	if err != nil {
		return 0, err
	}

	bytesize := ByteSize(value * float64(unit))
	return bytesize, nil
}

// LogLevel type specifies possible log levels.
type LogLevel int

const (
	// TRACE priority
	TRACE LogLevel = iota
	// DEBUG priority
	DEBUG
	// INFO priority
	INFO
	// NOTICE priority
	NOTICE
	// WARN priority
	WARN
	// ERROR priority
	ERROR
	// CRIT priority
	CRIT
	// ALERT priority
	ALERT
	// FATAL priority
	FATAL
)

var logLevelMap = map[string]LogLevel{
	"TRACE":  TRACE,
	"DEBUG":  DEBUG,
	"INFO":   INFO,
	"NOTICE": NOTICE,
	"WARN":   WARN,
	"ERROR":  ERROR,
	"CRIT":   CRIT,
	"ALERT":  ALERT,
	"FATAL":  FATAL,
}

func (l LogLevel) String() string {
	switch l {
	case TRACE:
		return "TRACE"
	case DEBUG:
		return "DEBUG"
	case INFO:
		return "INFO"
	case NOTICE:
		return "NOTICE"
	case WARN:
		return "WARN"
	case ERROR:
		return "ERROR"
	case CRIT:
		return "CRIT"
	case ALERT:
		return "ALERT"
	case FATAL:
		return "FATAL"
	}
	return "NOTSET"
}

// Personality allows to set the architecture for the container.
type Personality int64

const (
	// X86 - Intel 32bit
	X86 Personality = 0x0008

	// X86_64 - Intel 64bit
	X86_64 = 0x0000
)

const (
	// MIGRATE_PRE_DUMP - pre-dump live migration phase
	MIGRATE_PRE_DUMP = 0

	// MIGRATE_DUMP - main live migration phase
	MIGRATE_DUMP = 1

	// MIGRATE_RESTORE - post migration phase
	MIGRATE_RESTORE = 2

	// MIGRATE_FEATURE_CHECK - migration feature check
	MIGRATE_FEATURE_CHECK = 3
)

// CriuFeatures represents a set of CRIU features
type CriuFeatures uint64

const (
	// FEATURE_MEM_TRACK - memory tracking support
	FEATURE_MEM_TRACK CriuFeatures = 1 << iota

	// FEATURE_LAZY_PAGES - lazy pages support
	FEATURE_LAZY_PAGES
)
