// Package service provides a toolkit for creating Hnakra services.
package service

import "os"
import "log"
import "time"
import "hnakra/rotate"
import "hnakra/daemon"
import "hnakra/routines"

// Service is capable of managing multiple mounts. It also sets up logging
// automatically.
type Service struct {
	ServiceInfo
	Mounts []Mount

	manager routines.Manager
}

// NewService provides a shorthand for creating a new service, leaving most
// values to their default.
func NewService (name, description string, mounts ...Mount) *Service {
	return &Service {
		ServiceInfo: ServiceInfo {
			Name: name,
			Description: description,
		},
		Mounts: mounts,
	}
}

// Run runs the mounts within the service, and only exits when all of them have
// exited. It will automatically start logging to the directory specified by
// $HNAKRA_LOG_DIR. If that variable is unset, it will just log to stdout.
// Additionally, if $HNAKRA_PIDFILE is set, it will write the process PID to the
// file specified by it.
func (service *Service) Run () error {
	// set up logging
	logDir := os.Getenv("HNAKRA_LOG_DIR")
	if logDir != "" {
		logger, err := rotate.New(logDir)
		if err != nil { log.Fatal("cannot access log dir:", err) }
		log.SetOutput(logger)
	}
	
	log.Println("... starting service", service.Name)

	// set up routine manager
	service.manager = routines.Manager { RestartDeadline: time.Second * 8 }
	service.manager.Routines = make([]routines.Routine, len(service.Mounts))
	for index, mount := range service.Mounts {
		service.manager.Routines[index] = routines.From (func () error {
			return mount.Run(service.ServiceInfo)
		}, mount.Shutdown)
	}

	// be a daemon
	daemon.ShutdownOnSigint(service)
	pidfile := daemon.PidFile(os.Getenv("HNAKRA_PIDFILE"))
	if !pidfile.Empty() {
		err := pidfile.Start()
		if err != nil { log.Println("!!! could not write pid:", err) }
		defer func () {
			err := pidfile.Close()
			if err != nil { log.Println("!!! could not delete pidfile:", err) }
		} ()
	}

	// send it
	err := service.manager.Run()
	if err != nil { log.Println("XXX", err) }
	return err
}

// Close abruptly closes all mounts in the service. This will cause Run() to
// exit.
func (service *Service) Close () (err error) {
	for _, mount := range service.Mounts {
		singleErr := mount.Close()
		if singleErr != nil {
			err = singleErr
		}
	}
	return
}

// Shutdown gracefully shuts down each mount in the service. This will cause
// Run() to exit.
func (service *Service) Shutdown () (err error) {
	return service.manager.Shutdown()
}
