// Copyright (C) 2015 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package main

import (
	"fmt"

	"github.com/syncthing/syncthing/lib/events"
)

// The verbose logging service subscribes to events and prints these in
// verbose format to the console using INFO level.
type verboseService struct {
	stop    chan struct{} // signals time to stop
	started chan struct{} // signals startup complete
}

func newVerboseService() *verboseService {
	return &verboseService{
		stop:    make(chan struct{}),
		started: make(chan struct{}),
	}
}

// Serve runs the verbose logging service.
func (s *verboseService) Serve() {
	sub := events.Default.Subscribe(events.AllEvents)
	defer events.Default.Unsubscribe(sub)

	select {
	case <-s.started:
		// The started channel has already been closed; do nothing.
	default:
		// This is the first time around. Indicate that we're ready to start
		// processing events.
		close(s.started)
	}

	for {
		select {
		case ev := <-sub.C():
			formatted := s.formatEvent(ev)
			if formatted != "" {
				l.Verboseln(formatted)
			}
		case <-s.stop:
			return
		}
	}
}

// Stop stops the verbose logging service.
func (s *verboseService) Stop() {
	close(s.stop)
}

// WaitForStart returns once the verbose logging service is ready to receive
// events, or immediately if it's already running.
func (s *verboseService) WaitForStart() {
	<-s.started
}

func (s *verboseService) formatEvent(ev events.Event) string {
	switch ev.Type {
	case events.DownloadProgress, events.LocalIndexUpdated:
		// Skip
		return ""

	case events.Starting:
		return fmt.Sprintf("Starting up (%s)", ev.Data.(map[string]string)["home"])

	case events.StartupComplete:
		return "Startup complete"

	case events.DeviceDiscovered:
		data := ev.Data.(map[string]interface{})
		return fmt.Sprintf("Discovered device %v at %v", data["device"], data["addrs"])

	case events.DeviceConnected:
		data := ev.Data.(map[string]string)
		return fmt.Sprintf("Connected to device %v at %v (type %s)", data["id"], data["addr"], data["type"])

	case events.DeviceDisconnected:
		data := ev.Data.(map[string]string)
		return fmt.Sprintf("Disconnected from device %v", data["id"])

	case events.StateChanged:
		data := ev.Data.(map[string]interface{})
		return fmt.Sprintf("Folder %q is now %v", data["folder"], data["to"])

	case events.LocalChangeDetected:
		data := ev.Data.(map[string]string)
		return fmt.Sprintf("Local change detected in folder %q: %s %s %s", data["folder"], data["action"], data["type"], data["path"])

	case events.RemoteChangeDetected:
		data := ev.Data.(map[string]string)
		return fmt.Sprintf("Remote change detected in folder %q: %s %s %s", data["folder"], data["action"], data["type"], data["path"])

	case events.RemoteIndexUpdated:
		data := ev.Data.(map[string]interface{})
		return fmt.Sprintf("Device %v sent an index update for %q with %d items", data["device"], data["folder"], data["items"])

	case events.DeviceRejected:
		data := ev.Data.(map[string]string)
		return fmt.Sprintf("Rejected connection from device %v at %v", data["device"], data["address"])

	case events.FolderRejected:
		data := ev.Data.(map[string]string)
		return fmt.Sprintf("Rejected unshared folder %q from device %v", data["folder"], data["device"])

	case events.ItemStarted:
		data := ev.Data.(map[string]string)
		return fmt.Sprintf("Started syncing %q / %q (%v %v)", data["folder"], data["item"], data["action"], data["type"])

	case events.ItemFinished:
		data := ev.Data.(map[string]interface{})
		if err, ok := data["error"].(*string); ok && err != nil {
			// If the err interface{} is not nil, it is a string pointer.
			// Dereference it to get the actual error or Sprintf will print
			// the pointer value....
			return fmt.Sprintf("Finished syncing %q / %q (%v %v): %v", data["folder"], data["item"], data["action"], data["type"], *err)
		}
		return fmt.Sprintf("Finished syncing %q / %q (%v %v): Success", data["folder"], data["item"], data["action"], data["type"])

	case events.ConfigSaved:
		return "Configuration was saved"

	case events.FolderCompletion:
		data := ev.Data.(map[string]interface{})
		return fmt.Sprintf("Completion for folder %q on device %v is %v%%", data["folder"], data["device"], data["completion"])

	case events.FolderSummary:
		data := ev.Data.(map[string]interface{})
		sum := make(map[string]interface{})
		for k, v := range data["summary"].(map[string]interface{}) {
			if k == "invalid" || k == "ignorePatterns" || k == "stateChanged" {
				continue
			}
			sum[k] = v
		}
		return fmt.Sprintf("Summary for folder %q is %v", data["folder"], sum)

	case events.FolderScanProgress:
		data := ev.Data.(map[string]interface{})
		folder := data["folder"].(string)
		current := data["current"].(int64)
		total := data["total"].(int64)
		rate := data["rate"].(float64) / 1024 / 1024
		var pct int64
		if total > 0 {
			pct = 100 * current / total
		}
		return fmt.Sprintf("Scanning folder %q, %d%% done (%.01f MiB/s)", folder, pct, rate)

	case events.DevicePaused:
		data := ev.Data.(map[string]string)
		device := data["device"]
		return fmt.Sprintf("Device %v was paused", device)

	case events.DeviceResumed:
		data := ev.Data.(map[string]string)
		device := data["device"]
		return fmt.Sprintf("Device %v was resumed", device)

	case events.FolderPaused:
		data := ev.Data.(map[string]string)
		id := data["id"]
		label := data["label"]
		return fmt.Sprintf("Folder %v (%v) was paused", id, label)

	case events.FolderResumed:
		data := ev.Data.(map[string]string)
		id := data["id"]
		label := data["label"]
		return fmt.Sprintf("Folder %v (%v) was resumed", id, label)

	case events.ListenAddressesChanged:
		data := ev.Data.(map[string]interface{})
		address := data["address"]
		lan := data["lan"]
		wan := data["wan"]
		return fmt.Sprintf("Listen address %s resolution has changed: lan addresses: %s wan addresses: %s", address, lan, wan)

	case events.LoginAttempt:
		data := ev.Data.(map[string]interface{})
		username := data["username"].(string)
		var success string
		if data["success"].(bool) {
			success = "successful"
		} else {
			success = "failed"
		}
		return fmt.Sprintf("Login %s for username %s.", success, username)
	}

	return fmt.Sprintf("%s %#v", ev.Type, ev)
}
