/*
 * EliasDB
 *
 * Copyright 2016 Matthias Ladkau. All rights reserved.
 *
 * 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 http://mozilla.org/MPL/2.0/.
 */

package ecal

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"

	"devt.de/krotik/common/fileutil"
	"devt.de/krotik/ecal/cli/tool"
	"devt.de/krotik/ecal/stdlib"
	"devt.de/krotik/ecal/util"
	"devt.de/krotik/eliasdb/config"
	"devt.de/krotik/eliasdb/ecal/dbfunc"
	"devt.de/krotik/eliasdb/graph"
)

/*
ScriptingInterpreter models a ECAL script interpreter instance.
*/
type ScriptingInterpreter struct {
	GM          *graph.Manager       // GraphManager for the interpreter
	Interpreter *tool.CLIInterpreter // ECAL Interpreter object

	Dir       string // Root dir for interpreter
	EntryFile string // Entry file for the program
	LogLevel  string // Log level string (Debug, Info, Error)
	LogFile   string // Logfile (blank for stdout)

	RunDebugServer  bool   // Run a debug server
	DebugServerHost string // Debug server host
	DebugServerPort string // Debug server port
}

/*
NewScriptingInterpreter returns a new ECAL scripting interpreter.
*/
func NewScriptingInterpreter(scriptFolder string, gm *graph.Manager) *ScriptingInterpreter {
	return &ScriptingInterpreter{
		GM:              gm,
		Dir:             scriptFolder,
		EntryFile:       filepath.Join(scriptFolder, config.Str(config.ECALEntryScript)),
		LogLevel:        config.Str(config.ECALLogLevel),
		LogFile:         config.Str(config.ECALLogFile),
		RunDebugServer:  config.Bool(config.EnableECALDebugServer),
		DebugServerHost: config.Str(config.ECALDebugServerHost),
		DebugServerPort: config.Str(config.ECALDebugServerPort),
	}
}

/*
dummyEntryFile is a small valid ECAL which does not do anything. It is used
as the default entry file if no entry file exists.
*/
const dummyEntryFile = `0 # Write your ECAL code here
`

/*
Run runs the ECAL scripting interpreter.

After this function completes:
- EntryScript in config and all related scripts in the interpreter root dir have been executed
- ECAL Interpreter object is fully initialized
- A debug server might be running which can reload the entry script
- ECAL's event processor has been started
- GraphManager events are being forwarded to ECAL
*/
func (si *ScriptingInterpreter) Run() error {
	var err error

	// Ensure we have a dummy entry point

	if ok, _ := fileutil.PathExists(si.EntryFile); !ok {
		err = ioutil.WriteFile(si.EntryFile, []byte(dummyEntryFile), 0600)
	}

	if err == nil {
		i := tool.NewCLIInterpreter()
		si.Interpreter = i

		i.Dir = &si.Dir
		i.LogFile = &si.LogFile
		i.LogLevel = &si.LogLevel

		i.EntryFile = si.EntryFile
		i.LoadPlugins = true

		i.CreateRuntimeProvider("eliasdb-runtime")

		// Adding functions

		AddEliasDBStdlibFunctions(si.GM)

		if err == nil {

			if si.RunDebugServer {
				di := tool.NewCLIDebugInterpreter(i)

				addr := fmt.Sprintf("%v:%v", si.DebugServerHost, si.DebugServerPort)
				di.DebugServerAddr = &addr
				di.RunDebugServer = &si.RunDebugServer
				falseFlag := false
				di.EchoDebugServer = &falseFlag
				di.Interactive = &falseFlag
				di.BreakOnStart = &falseFlag
				trueFlag := true
				di.BreakOnError = &trueFlag

				err = di.Interpret()

			} else {

				err = i.Interpret(false)
			}

			// EliasDB graph events are now forwarded to ECAL via the eventbridge.

			si.GM.SetGraphRule(&EventBridge{
				Processor: i.RuntimeProvider.Processor,
				Logger:    i.RuntimeProvider.Logger,
			})
		}
	}

	// Include a traceback if possible

	if ss, ok := err.(util.TraceableRuntimeError); ok {
		err = fmt.Errorf("%v\n  %v", err.Error(), strings.Join(ss.GetTraceString(), "\n  "))
	}

	return err
}

func AddEliasDBStdlibFunctions(gm *graph.Manager) {
	stdlib.AddStdlibPkg("db", "EliasDB related functions")

	stdlib.AddStdlibFunc("db", "storeNode", &dbfunc.StoreNodeFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "updateNode", &dbfunc.UpdateNodeFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "removeNode", &dbfunc.RemoveNodeFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "fetchNode", &dbfunc.FetchNodeFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "storeEdge", &dbfunc.StoreEdgeFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "removeEdge", &dbfunc.RemoveEdgeFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "fetchEdge", &dbfunc.FetchEdgeFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "traverse", &dbfunc.TraverseFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "newTrans", &dbfunc.NewTransFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "newRollingTrans", &dbfunc.NewRollingTransFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "commit", &dbfunc.CommitTransFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "query", &dbfunc.QueryFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "graphQL", &dbfunc.GraphQLFunc{GM: gm})
	stdlib.AddStdlibFunc("db", "raiseGraphEventHandled", &dbfunc.RaiseGraphEventHandledFunc{})
	stdlib.AddStdlibFunc("db", "raiseWebEventHandled", &dbfunc.RaiseWebEventHandledFunc{})

}
