package tools

import (
	"os"
	"os/exec"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"golang.org/x/sync/semaphore"
)

const (
	SNOWFLAKE_MAX_MACHINE_ID int64 = (1 << 10) - 1
	SNOWFLAKE_MAX_SEQUENCE   int64 = (1 << 12) - 1
	SNOWFLAKE_EPOCH_MILLI          = 1207008000000
	SNOWFLAKE_EPOCH_SECONDS        = SNOWFLAKE_EPOCH_MILLI / 1000 // Apr 1st 2008 (Teto B-Day!)
	TIMEOUT_SHUTDOWN               = 1 * time.Minute              // Standard Timeout for Shutdowns
	TIMEOUT_CONTEXT                = 10 * time.Second             // Standard Timeout for Requests
	FS_FILE_PUBLIC                 = os.FileMode(0640)            // rw-r-----
	FS_FILE_PRIVATE                = os.FileMode(0600)            // rw-------
	FS_DIR_PUBLIC                  = os.FileMode(0750)            // rwxr-x---
	FS_DIR_PRIVATE                 = os.FileMode(0700)            // rwx------
)

var (
	TEMP_CAPACITY   atomic.Int64
	LIMIT_JSON      = EnvNumber("LIMIT_JSON", 8*1024)           // ( 8KB) Size limit per incoming JSON string
	LIMIT_FILE      = EnvNumber("LIMIT_FILE", 25*1024*1024)     // (25MB) Size limit per incoming media file
	LIMIT_TEMP      = EnvNumber("LIMIT_TEMP", 2*1024*1024*1024) // ( 2GB) Disk space allowed for temporary files
	LIMIT_ENCODES   = EnvNumber("LIMIT_ENCODES", 1)             // Concurrent Uploads
	LIMIT_PROBES    = EnvNumber("LIMIT_PROBES", 4)              // Concurrent Probes
	LIMIT_MIME_TYPE = EnvSlice("LIMIT_MIME_TYPE", ",", []string{
		/* STANDARD */ "image/jpeg", "image/png", "image/gif", "image/webp", "image/heic", "image/heif",
		/* FUTURE   */ "image/avif", "image/jxl",
		/* LEGACY   */ "image/tiff", "image/bmp",
		/* STANDARD */ "video/mp4", "video/webm", "video/quicktime", "video/x-matroska",
		/* LEGACY   */ "video/avi", "video/x-ms-wmv",
	})
	TEMPLATE_BASE_WEB   = EnvString("TEMPLATE_BASE_WEB", "http://localhost:5173")
	TEMPLATE_BASE_CDN   = EnvString("TEMPLATE_BASE_CDN", "http://localhost:3000")
	TEMPLATE_BASE_API   = EnvString("TEMPLATE_BASE_API", "http://localhost:8080")
	MACHINE_ID          = EnvString("MACHINE_ID", "0")
	MACHINE_HOSTNAME    = EnvString("MACHINE_HOSTNAME", "le fishe")
	MACHINE_PROVERB     = EnvString("MACHINE_PROVERB", "><> .o( blub blub)")
	DATABASE_URL        = EnvString("DATABASE_URL", "postgresql://postgres:password@localhost:5432")
	STORAGE_DISK_TEMP   = EnvString("STORAGE_DISK_TEMP", "_temp")
	STORAGE_DISK_PUBLIC = EnvString("STORAGE_DISK_PUBLIC", "_public")
	ONNX_RUNTIME_PATH   = EnvString("ONNX_RUNTIME_PATH", "")
	ONNX_RUNTIME_CUDA   = EnvString("ONNX_RUNTIME_CUDA", "") != ""
	HTTP_ADDRESS        = EnvString("HTTP_ADDRESS", "127.0.0.1:8080")
	HTTP_PROXY          = EnvString("HTTP_PROXY", "")
)

var (
	SEMA_UPLOADS = semaphore.NewWeighted(int64(LIMIT_TEMP))
	SEMA_ENCODES = semaphore.NewWeighted(int64(LIMIT_ENCODES))
	SEMA_PROBES  = semaphore.NewWeighted(int64(LIMIT_PROBES))
)

func init() {
	// Prepare Directories
	if err := os.MkdirAll(STORAGE_DISK_PUBLIC, FS_DIR_PUBLIC); err != nil {
		LoggerInit.Log(FATAL, "Cannot Create Public Directory")
		return
	}
	if err := os.MkdirAll(STORAGE_DISK_TEMP, FS_DIR_PRIVATE); err != nil {
		LoggerInit.Log(FATAL, "Cannot Create Temp Directory")
		return
	}

	// Check Executables
	if err := exec.Command("ffmpeg", "--help").Run(); err != nil {
		LoggerInit.Log(FATAL, "FFmpeg failed to start: %s", err)
		return
	}
	if err := exec.Command("ffprobe", "--help").Run(); err != nil {
		LoggerInit.Log(FATAL, "FFprobe failed to start: %s", err)
		return
	}
}

// Read String from Environment
func EnvString(field, initial string) string {
	if value := os.Getenv(field); value == "" {
		return initial
	} else {
		return value
	}
}

// Read String from Environment and Parse it as a number
func EnvNumber(field string, initial int) int {
	if value := os.Getenv(field); value == "" {
		return initial
	} else if number, err := strconv.Atoi(value); err != nil {
		return initial
	} else {
		return number
	}
}

// Read String from Environment and Parse it as a slice using the given delimiter
func EnvSlice(field, delimiter string, initial []string) []string {
	if value := os.Getenv(field); value == "" {
		return initial
	} else {
		return strings.Split(value, delimiter)
	}
}
