// Copyright 2014 by Sascha L. Teichmann
// Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"os/signal"
	"runtime"
	"time"
)

const (
	Version        = "0.3"
	GCDuration     = "24h"
	ChangeDuration = "30s"
)

func usage() {
	fmt.Fprintf(os.Stderr,
		"Usage: %s [<options>] <database>\n", os.Args[0])
	fmt.Fprintln(os.Stderr, "Options:")
	flag.PrintDefaults()
}

func main() {

	var (
		port           int
		host           string
		driver         string
		cacheSize      int
		version        bool
		interleaved    bool
		changeUrl      string
		gcDuration     string
		changeDuration string
	)

	flag.Usage = usage

	flag.IntVar(&port, "port", 6379, "port to bind")
	flag.StringVar(&driver, "driver", "leveldb", "type of database (leveldb, sqlite)")
	flag.StringVar(&host, "host", "", "host to bind")
	flag.IntVar(&cacheSize, "cache", 32, "cache size in MB")
	flag.BoolVar(&version, "version", false, "Print version and exit.")
	flag.BoolVar(&interleaved,
		"interleaved", false, "Backend stores key in interleaved form.")
	flag.StringVar(&gcDuration,
		"gc-duration", GCDuration, "Duration between forced GCs.")
	flag.StringVar(&changeDuration,
		"change-duration", ChangeDuration, "Duration to aggregate changes.")
	flag.StringVar(&changeUrl, "change-url", "", "URL to send changes to.")
	flag.Parse()

	if version {
		fmt.Printf("Version: %s\n", Version)
		os.Exit(0)
	}

	if flag.NArg() < 1 {
		log.Fatal("Missing path to world")
	}

	var (
		err           error
		backend       Backend
		gcDur         time.Duration
		chDur         time.Duration
		changeTracker *ChangeTracker
	)

	if gcDur, err = time.ParseDuration(gcDuration); err != nil {
		log.Fatal(err)
	}

	// Setup the change listening stuff.

	var changeChan <-chan time.Time

	useChangeNotification := changeUrl != ""

	if useChangeNotification {
		if chDur, err = time.ParseDuration(changeDuration); err != nil {
			log.Fatal(err)
		}
		changeChan = time.Tick(chDur)
		changeTracker = NewChangeTracker()
	} else {
		// We will never receive ticks on this.
		changeChan = make(<-chan time.Time)
	}

	path := flag.Arg(0)

	if driver == "sqlite" {
		if backend, err = NewSqliteBackend(path, changeTracker, interleaved); err != nil {
			log.Fatal(err)
		}
	} else {
		if backend, err = NewLeveDBBackend(
			path, changeTracker, interleaved, cacheSize); err != nil {
			log.Fatal(err)
		}
	}

	defer backend.Shutdown()

	var listener net.Listener

	listener, err = net.Listen("tcp", fmt.Sprintf("%s:%d", host, port))
	if err != nil {
		log.Fatal(err)
	}
	defer listener.Close()
	log.Printf("Server started at %s", listener.Addr())

	connChan := make(chan net.Conn)
	defer close(connChan)
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, os.Kill)

	go func() {
		for {
			conn, err := listener.Accept()
			if err != nil {
				log.Fatal(err)
			}
			log.Printf("Client accepted from: %s", conn.RemoteAddr())
			connChan <- conn
		}
	}()

	log.Printf("Doing garbage collection every: %s", gcDur)
	gcChan := time.Tick(gcDur)

	for {
		select {
		case conn := <-connChan:
			var session Session
			if session, err = backend.NewSession(); err != nil {
				log.Printf("Cannot create session: %s", err)
				conn.Close()
			} else {
				go NewConnection(conn, session).Run()
			}
		case <-sigChan:
			log.Println("Shutting down")
			return
		case <-gcChan:
			log.Println("Starting garbage collection.")
			runtime.GC()
			log.Println("Garbage collection done.")
		case <-changeChan:
			if changeTracker != nil {
				changeTracker.FlushChanges(changeUrl)
			}
		}
	}
}