package realize

import (
	"gopkg.in/yaml.v2"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"time"
)

// settings const
const (
	Permission = 0775
	File       = ".realize.yaml"
	FileOut    = ".r.outputs.log"
	FileErr    = ".r.errors.log"
	FileLog    = ".r.logs.log"
)

// random string preference
const (
	letterIdxBits = 6                    // 6 bits to represent a letter index
	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
	letterBytes   = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
)

// Settings defines a group of general settings and options
type Settings struct {
	Files     `yaml:"files,omitempty" json:"files,omitempty"`
	FileLimit int32    `yaml:"flimit,omitempty" json:"flimit,omitempty"`
	Legacy    Legacy   `yaml:"legacy" json:"legacy"`
	Recovery  Recovery `yaml:"recovery,omitempty" json:"recovery,omitempty"`
}

type Recovery struct {
	Index  bool
	Events bool
	Tools  bool
}

// Legacy is used to force polling and set a custom interval
type Legacy struct {
	Force    bool          `yaml:"force" json:"force"`
	Interval time.Duration `yaml:"interval" json:"interval"`
}

// Files defines the files generated by realize
type Files struct {
	Clean   bool     `yaml:"clean,omitempty" json:"clean,omitempty"`
	Outputs Resource `yaml:"outputs,omitempty" json:"outputs,omitempty"`
	Logs    Resource `yaml:"logs,omitempty" json:"log,omitempty"`
	Errors  Resource `yaml:"errors,omitempty" json:"error,omitempty"`
}

// Resource status and file name
type Resource struct {
	Status bool
	Path   string
	Name   string
}

// Set legacy watcher with an interval
func (l *Legacy) Set(status bool, interval int) {
	l.Force = true
	l.Interval = time.Duration(interval) * time.Second
}

// Remove realize folder
func (s *Settings) Remove(d string) error {
	_, err := os.Stat(d)
	if !os.IsNotExist(err) {
		return os.RemoveAll(d)
	}
	return err
}

// Read config file
func (s *Settings) Read(out interface{}) error {
	// backward compatibility
	if _, err := os.Stat(RFile); err != nil {
		return err
	}
	content, err := s.Stream(RFile)
	if err == nil {
		err = yaml.Unmarshal(content, out)
		return err
	}
	return err
}

// Write config file
func (s *Settings) Write(out interface{}) error {
	y, err := yaml.Marshal(out)
	if err != nil {
		return err
	}
	s.Fatal(ioutil.WriteFile(RFile, y, Permission))
	return nil
}

// Stream return a byte stream of a given file
func (s Settings) Stream(file string) ([]byte, error) {
	_, err := os.Stat(file)
	if err != nil {
		return nil, err
	}
	content, err := ioutil.ReadFile(file)
	s.Fatal(err)
	return content, err
}

// Fatal prints a fatal error with its additional messages
func (s Settings) Fatal(err error, msg ...interface{}) {
	if err != nil {
		if len(msg) > 0 {
			log.Fatalln(Red.Regular(msg...), err.Error())
		} else {
			log.Fatalln(err.Error())
		}
	}
}

// Create a new file and return its pointer
func (s Settings) Create(path string, name string) *os.File {
	file := filepath.Join(path, name)
	out, err := os.OpenFile(file, os.O_APPEND|os.O_WRONLY|os.O_CREATE|os.O_SYNC, Permission)
	s.Fatal(err)
	return out
}
