package xdsconfig

import (
	"fmt"
	"strings"

	"os"

	"time"

	"github.com/Sirupsen/logrus"
	"github.com/codegangsta/cli"
	"github.com/iotbzh/xds-server/lib/syncthing"
)

// Config parameters (json format) of /config command
type Config struct {
	Version       string        `json:"version"`
	APIVersion    string        `json:"apiVersion"`
	VersionGitTag string        `json:"gitTag"`
	Builder       BuilderConfig `json:"builder"`
	Folders       FoldersConfig `json:"folders"`

	// Private / un-exported fields
	progName     string
	fileConf     FileConfig
	WebAppDir    string         `json:"-"`
	HTTPPort     string         `json:"-"`
	ShareRootDir string         `json:"-"`
	Log          *logrus.Logger `json:"-"`
	SThg         *st.SyncThing  `json:"-"`
}

// Config default values
const (
	DefaultAPIVersion = "1"
	DefaultPort       = "8000"
	DefaultShareDir   = "/mnt/share"
	DefaultLogLevel   = "error"
)

// Init loads the configuration on start-up
func Init(ctx *cli.Context) (Config, error) {
	var err error

	// Set logger level and formatter
	log := ctx.App.Metadata["logger"].(*logrus.Logger)

	logLevel := ctx.GlobalString("log")
	if logLevel == "" {
		logLevel = DefaultLogLevel
	}
	if log.Level, err = logrus.ParseLevel(logLevel); err != nil {
		fmt.Printf("Invalid log level : \"%v\"\n", logLevel)
		os.Exit(1)
	}
	log.Formatter = &logrus.TextFormatter{}

	// Define default configuration
	c := Config{
		Version:       ctx.App.Metadata["version"].(string),
		APIVersion:    DefaultAPIVersion,
		VersionGitTag: ctx.App.Metadata["git-tag"].(string),
		Builder:       BuilderConfig{},
		Folders:       FoldersConfig{},

		progName:     ctx.App.Name,
		WebAppDir:    "webapp/dist",
		HTTPPort:     DefaultPort,
		ShareRootDir: DefaultShareDir,
		Log:          log,
		SThg:         nil,
	}

	// config file settings overwrite default config
	err = updateConfigFromFile(&c, ctx.GlobalString("config"))
	if err != nil {
		return Config{}, err
	}

	// Update location of shared dir if needed
	if !dirExists(c.ShareRootDir) {
		if err := os.MkdirAll(c.ShareRootDir, 0770); err != nil {
			c.Log.Fatalf("No valid shared directory found (err=%v)", err)
		}
	}
	c.Log.Infoln("Share root directory: ", c.ShareRootDir)

	// FIXME - add a builder interface and support other builder type (eg. native)
	builderType := "syncthing"

	switch builderType {
	case "syncthing":
		// Syncthing settings only configurable from config.json file
		stGuiAddr := c.fileConf.SThgConf.GuiAddress
		stGuiApikey := c.fileConf.SThgConf.GuiAPIKey
		if stGuiAddr == "" {
			stGuiAddr = "http://localhost:8384"
		}
		if stGuiAddr[0:7] != "http://" {
			stGuiAddr = "http://" + stGuiAddr
		}

		// Retry if connection fail
		retry := 5
		for retry > 0 {
			c.SThg = st.NewSyncThing(stGuiAddr, stGuiApikey, c.Log)
			if c.SThg != nil {
				break
			}
			c.Log.Warningf("Establishing connection to Syncthing (retry %d/5)", retry)
			time.Sleep(time.Second)
			retry--
		}
		if c.SThg == nil {
			c.Log.Fatalf("ERROR: cannot connect to Syncthing (url: %s)", stGuiAddr)
		}

		// Retrieve Syncthing config
		id, err := c.SThg.IDGet()
		if err != nil {
			return Config{}, err
		}

		if c.Builder, err = NewBuilderConfig(id); err != nil {
			c.Log.Fatalln(err)
		}

		// Retrieve initial Syncthing config
		stCfg, err := c.SThg.ConfigGet()
		if err != nil {
			return Config{}, err
		}
		for _, stFld := range stCfg.Folders {
			relativePath := strings.TrimPrefix(stFld.RawPath, c.ShareRootDir)
			if relativePath == "" {
				relativePath = stFld.RawPath
			}
			newFld := NewFolderConfig(stFld.ID, stFld.Label, c.ShareRootDir, strings.Trim(relativePath, "/"))
			c.Folders = c.Folders.Update(FoldersConfig{newFld})
		}

	default:
		log.Fatalln("Unsupported builder type")
	}

	return c, nil
}

// GetFolderFromID retrieves the Folder config from id
func (c *Config) GetFolderFromID(id string) *FolderConfig {
	if idx := c.Folders.GetIdx(id); idx != -1 {
		return &c.Folders[idx]
	}
	return nil
}

// UpdateAll updates all the current configuration
func (c *Config) UpdateAll(newCfg Config) error {
	return fmt.Errorf("Not Supported")
	/*
		if err := VerifyConfig(newCfg); err != nil {
			return err
		}

		// TODO: c.Builder = c.Builder.Update(newCfg.Builder)
		c.Folders = c.Folders.Update(newCfg.Folders)

		// SEB A SUP model.NotifyListeners(c, NotifyFoldersChange, FolderConfig{})
		// FIXME To be tested & improved error handling
		for _, f := range c.Folders {
			if err := c.SThg.FolderChange(st.FolderChangeArg{
				ID:           f.ID,
				Label:        f.Label,
				RelativePath: f.RelativePath,
				SyncThingID:  f.SyncThingID,
				ShareRootDir: c.ShareRootDir,
			}); err != nil {
				return err
			}
		}

		return nil
	*/
}

// UpdateFolder updates a specific folder into the current configuration
func (c *Config) UpdateFolder(newFolder FolderConfig) (FolderConfig, error) {
	// rootPath should not be empty
	if newFolder.rootPath == "" {
		newFolder.rootPath = c.ShareRootDir
	}

	// Sanity check of folder settings
	if err := FolderVerify(newFolder); err != nil {
		return FolderConfig{}, err
	}

	c.Folders = c.Folders.Update(FoldersConfig{newFolder})

	// SEB A SUP model.NotifyListeners(c, NotifyFolderAdd, newFolder)
	err := c.SThg.FolderChange(st.FolderChangeArg{
		ID:           newFolder.ID,
		Label:        newFolder.Label,
		RelativePath: newFolder.RelativePath,
		SyncThingID:  newFolder.SyncThingID,
		ShareRootDir: c.ShareRootDir,
	})

	newFolder.BuilderSThgID = c.Builder.SyncThingID // FIXME - should be removed after local ST config rework
	newFolder.Status = FolderStatusEnable

	return newFolder, err
}

// DeleteFolder deletes a specific folder
func (c *Config) DeleteFolder(id string) (FolderConfig, error) {
	var fld FolderConfig
	var err error

	//SEB A SUP model.NotifyListeners(c, NotifyFolderDelete, fld)
	if err = c.SThg.FolderDelete(id); err != nil {
		return fld, err
	}

	c.Folders, fld, err = c.Folders.Delete(id)

	return fld, err
}

func dirExists(path string) bool {
	_, err := os.Stat(path)
	if os.IsNotExist(err) {
		return false
	}
	return true
}