// Copyright 2019 Daniel Theophanes.
// Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.

package service

import (
	"fmt"
	"os"
	"os/signal"
	"syscall"
	"text/template"
)

const version = "freebsd"

type freebsdSystem struct{}

func (freebsdSystem) String() string {
	return version
}
func (freebsdSystem) Detect() bool {
	return true
}
func (freebsdSystem) Interactive() bool {
	return interactive
}
func (freebsdSystem) New(i Interface, c *Config) (Service, error) {
	s := &freebsdService{
		i:      i,
		Config: c,
	}

	return s, nil
}

func init() {
	ChooseSystem(freebsdSystem{})
}

var interactive = false

func init() {
	var err error
	interactive, err = isInteractive()
	if err != nil {
		panic(err)
	}
}

func isInteractive() (bool, error) {
	return os.Getenv("IS_DAEMON") != "1", nil
}

type freebsdService struct {
	i Interface
	*Config
}

func (s *freebsdService) String() string {
	if len(s.DisplayName) > 0 {
		return s.DisplayName
	}
	return s.Name
}

func (s *freebsdService) Platform() string {
	return version
}

func (s *freebsdService) template() *template.Template {
	functions := template.FuncMap{
		"bool": func(v bool) string {
			if v {
				return "true"
			}
			return "false"
		},
	}

	customConfig := s.Option.string(optionSysvScript, "")

	if customConfig != "" {
		return template.Must(template.New("").Funcs(functions).Parse(customConfig))
	} else {
		return template.Must(template.New("").Funcs(functions).Parse(rcScript))
	}
}

func (s *freebsdService) configPath() (cp string, err error) {
	cp = "/usr/local/etc/rc.d/" + s.Config.Name
	return
}

func (s *freebsdService) Install() error {
	path, err := s.execPath()
	if err != nil {
		return err
	}

	// write start script
	confPath, err := s.configPath()
	if err != nil {
		return err
	}
	_, err = os.Stat(confPath)
	if err == nil {
		return fmt.Errorf("Init already exists: %s", confPath)
	}

	f, err := os.Create(confPath)
	if err != nil {
		return err
	}
	defer f.Close()

	var to = &struct {
		*Config
		Path string
	}{
		s.Config,
		path,
	}

	err = s.template().Execute(f, to)
	if err != nil {
		return err
	}
	// Run sync to ensure that the conf file is saved to disk.
	f.Sync()

	if err = os.Chmod(confPath, 0755); err != nil {
		return err
	}

	return nil
}

func (s *freebsdService) Uninstall() error {
	cp, err := s.configPath()
	if err != nil {
		return err
	}
	return os.Remove(cp)
}

func (s *freebsdService) Status() (Status, error) {
	cp, err := s.configPath()
	if err != nil {
		return StatusUnknown, err
	}

	if _, err = os.Stat(cp); os.IsNotExist(err) {
		return StatusStopped, ErrNotInstalled
	}

	status, _, err := runCommand("service", false, s.Name, "status")
	if status == 1 {
		return StatusStopped, nil
	} else if err != nil {
		return StatusUnknown, err
	}
	return StatusRunning, nil
}

func (s *freebsdService) Start() error {
	return run("service", s.Name, "start")
}

func (s *freebsdService) Stop() error {
	return run("service", s.Name, "stop")
}

func (s *freebsdService) Restart() error {
	return run("service", s.Name, "restart")
}

func (s *freebsdService) Run() error {
	var err error

	err = s.i.Start(s)
	if err != nil {
		return err
	}

	s.Option.funcSingle(optionRunWait, func() {
		var sigChan = make(chan os.Signal, 3)
		signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
		<-sigChan
	})()

	return s.i.Stop(s)
}

func (s *freebsdService) Logger(errs chan<- error) (Logger, error) {
	if interactive {
		return ConsoleLogger, nil
	}
	return s.SystemLogger(errs)
}

func (s *freebsdService) SystemLogger(errs chan<- error) (Logger, error) {
	return newSysLogger(s.Name, errs)
}

var rcScript = `#!/bin/sh

# PROVIDE: {{.Name}}
# REQUIRE: SERVERS
# KEYWORD: shutdown

. /etc/rc.subr

name="{{.Name}}"
{{.Name}}_env="IS_DAEMON=1"
pidfile="/var/run/${name}.pid"
command="/usr/sbin/daemon"
daemon_args="-P ${pidfile} -r -t \"${name}: daemon\"{{if .WorkingDirectory}} -c {{.WorkingDirectory}}{{end}}"
command_args="${daemon_args} {{.Path}}{{range .Arguments}} {{.}}{{end}}"

run_rc_command "$1"
`
