package shell

import (
	
	
	
	
	

	bolt 
	
	
	daemonmod 
	
	mathmod 
	pathmod 
	
	
	
	
	
	
)

const (
	daemonWaitLoops   = 100
	daemonWaitPerLoop = 10 * time.Millisecond
)

type daemonStatus int

const (
	daemonOK daemonStatus = iota
	sockfileMissing
	sockfileOtherError
	connectionShutdown
	connectionOtherError
	daemonInvalidDB
	daemonOutdated
)

const (
	daemonWontWorkMsg     = "Daemon-related functions will likely not work."
	connectionShutdownFmt = "Socket file %s exists but is not responding to request. This is likely due to abnormal shutdown of the daemon. Going to remove socket file and re-spawn a daemon.\n"
)

var errInvalidDB = errors.New("daemon reported that database is invalid. If you upgraded Elvish from a pre-0.10 version, you need to upgrade your database by following instructions in https://github.com/elves/upgrade-db-for-0.10/")

// InitRuntime initializes the runtime. The caller should call CleanupRuntime
// when the Evaler is no longer needed.
func ( io.Writer,  Paths,  bool) *eval.Evaler {
	 := eval.NewEvaler()
	.SetLibDir(.LibDir)
	.AddModule("math", mathmod.Ns)
	.AddModule("path", pathmod.Ns)
	.AddModule("platform", platform.Ns)
	.AddModule("re", re.Ns)
	.AddModule("str", str.Ns)
	.AddModule("file", file.Ns)
	if unix.ExposeUnixNs {
		.AddModule("unix", unix.Ns)
	}

	if  && .Sock != "" && .Db != "" {
		 := &daemon.SpawnConfig{
			RunDir:   .RunDir,
			BinPath:  .Bin,
			DbPath:   .Db,
			SockPath: .Sock,
		}
		// TODO(xiaq): Connect to daemon and install daemon module
		// asynchronously.
		,  := connectToDaemon(, )
		if  != nil {
			fmt.Fprintln(, "Cannot connect to daemon:", )
			fmt.Fprintln(, daemonWontWorkMsg)
		}
		// Even if error is not nil, we install daemon-related functionalities
		// anyway. Daemon may eventually come online and become functional.
		.SetDaemonClient()
		.AddModule("store", store.Ns())
		.AddModule("daemon", daemonmod.Ns(, ))
	}
	return 
}

// CleanupRuntime cleans up the runtime.
func ( io.Writer,  *eval.Evaler) {
	 := .DaemonClient()
	if  != nil {
		 := .Close()
		if  != nil {
			fmt.Fprintln(,
				"warning: failed to close connection to daemon:", )
		}
	}
}

func ( io.Writer,  *daemon.SpawnConfig) (daemon.Client, error) {
	 := .SockPath
	 := daemon.NewClient()
	,  := detectDaemon(, )
	 := false

	switch  {
	case daemonOK:
	case sockfileMissing:
		 = true
	case sockfileOtherError:
		return , fmt.Errorf("socket file %s inaccessible: %v", , )
	case connectionShutdown:
		fmt.Fprintf(, connectionShutdownFmt, )
		 := os.Remove()
		if  != nil {
			return , fmt.Errorf("failed to remove socket file: %v", )
		}
		 = true
	case connectionOtherError:
		return , fmt.Errorf("unexpected RPC error on socket %s: %v", , )
	case daemonInvalidDB:
		return , errInvalidDB
	case daemonOutdated:
		fmt.Fprintln(, "Daemon is outdated; going to kill old daemon and re-spawn")
		 := killDaemon()
		if  != nil {
			return , fmt.Errorf("failed to kill old daemon: %v", )
		}
		 = true
	default:
		return , fmt.Errorf("code bug: unknown daemon status %d", )
	}

	if ! {
		return , nil
	}

	 = daemon.Spawn()
	if  != nil {
		return , fmt.Errorf("failed to spawn daemon: %v", )
	}
	logger.Println("Spawned daemon")

	// Wait for daemon to come online
	for  := 0;  <= daemonWaitLoops; ++ {
		.ResetConn()
		,  := detectDaemon(, )

		switch  {
		case daemonOK:
			return , nil
		case sockfileMissing:
			// Continue waiting
		case sockfileOtherError:
			return , fmt.Errorf("socket file %s inaccessible: %v", , )
		case connectionShutdown:
			// Continue waiting
		case connectionOtherError:
			return , fmt.Errorf("unexpected RPC error on socket %s: %v", , )
		case daemonInvalidDB:
			return , errInvalidDB
		case daemonOutdated:
			return , fmt.Errorf("code bug: newly spawned daemon is outdated")
		default:
			return , fmt.Errorf("code bug: unknown daemon status %d", )
		}
		time.Sleep(daemonWaitPerLoop)
	}
	return , fmt.Errorf("daemon unreachable after waiting for %s", daemonWaitLoops*daemonWaitPerLoop)
}

func ( string,  daemon.Client) (daemonStatus, error) {
	,  := os.Stat()
	if  != nil {
		if os.IsNotExist() {
			return sockfileMissing, 
		}
		return sockfileOtherError, 
	}

	,  := .Version()
	if  != nil {
		switch {
		case  == rpc.ErrShutdown:
			return connectionShutdown, 
		case .Error() == bolt.ErrInvalid.Error():
			return daemonInvalidDB, 
		default:
			return connectionOtherError, 
		}
	}
	if  < daemon.Version {
		return daemonOutdated, nil
	}
	return daemonOK, nil
}

func ( daemon.Client) error {
	,  := .Pid()
	if  != nil {
		return fmt.Errorf("cannot get pid of daemon: %v", )
	}
	,  := os.FindProcess()
	if  != nil {
		return fmt.Errorf("cannot find daemon process (pid=%d): %v", , )
	}
	return .Signal(os.Interrupt)
}