package syslogd

import (
	"fmt"
	"io"
	"log"
	"os"
	"path"
	"regexp"

	"github.com/deis/deis/logger/syslog"

	"github.com/deis/deis/logger/drain"
)

// LogRoot is the log path to store logs.
var LogRoot string

type handler struct {
	// To simplify implementation of our handler we embed helper
	// syslog.BaseHandler struct.
	*syslog.BaseHandler
	drainURI string
}

// Simple fiter for named/bind messages which can be used with BaseHandler
func filter(m syslog.SyslogMessage) bool {
	return true
}

func newHandler() *handler {
	h := handler{
		BaseHandler: syslog.NewBaseHandler(5, filter, false),
	}

	go h.mainLoop() // BaseHandler needs some goroutine that reads from its queue
	return &h
}

// check if a file path exists
func fileExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return false, err
}

func getLogFile(message string) (io.Writer, error) {
	r := regexp.MustCompile(`^.* ([-_a-z0-9]+)\[[a-z0-9-_\.]+\].*`)
	match := r.FindStringSubmatch(message)
	if match == nil {
		return nil, fmt.Errorf("Could not find app name in message: %s", message)
	}
	appName := match[1]
	filePath := path.Join(LogRoot, appName+".log")
	// check if file exists
	exists, err := fileExists(filePath)
	if err != nil {
		return nil, err
	}
	// return a new file or the existing file for appending
	var file io.Writer
	if exists {
		file, err = os.OpenFile(filePath, os.O_RDWR|os.O_APPEND, 0644)
	} else {
		file, err = os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0644)
	}
	return file, err
}

func writeToDisk(m syslog.SyslogMessage) error {
	file, err := getLogFile(m.String())
	if err != nil {
		return err
	}
	bytes := []byte(m.String() + "\n")
	file.Write(bytes)
	return nil
}

// mainLoop reads from BaseHandler queue using h.Get and logs messages to stdout
func (h *handler) mainLoop() {
	for {
		m := h.Get()
		if m == nil {
			break
		}
		if h.drainURI != "" {
			drain.SendToDrain(m.String(), h.drainURI)
		}
		err := writeToDisk(m)
		if err != nil {
			log.Println(err)
		}
	}
	h.End()
}

// Listen starts a new syslog server which runs until it receives a signal.
func Listen(exitChan, cleanupDone chan bool, drainChan chan string, bindAddr string) {
	fmt.Println("Starting syslog...")
	// If LogRoot doesn't exist, create it
	// equivalent to Python's `if not os.path.exists(filename)`
	if _, err := os.Stat(LogRoot); os.IsNotExist(err) {
		if err = os.MkdirAll(LogRoot, 0777); err != nil {
			log.Fatalf("unable to create LogRoot at %s: %v", LogRoot, err)
		}
	}
	// Create a server with one handler and run one listen goroutine
	s := syslog.NewServer()
	h := newHandler()
	s.AddHandler(h)
	s.Listen(bindAddr)
	fmt.Println("Syslog server started...")
	fmt.Println("deis-logger running")

	// Wait for terminating signal
	for {
		select {
		case <-exitChan:
			// Shutdown the server
			fmt.Println("Shutting down...")
			s.Shutdown()
			cleanupDone <- true
		case d := <-drainChan:
			h.drainURI = d
		}
	}
}
