// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// The golsp command is an LSP server for Go.
// The Language Server Protocol allows any text editor
// to be extended with IDE-like features;
// see https://langserver.org/ for details.
package main // import "golang.org/x/tools/cmd/golsp"

import (
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"runtime"
	"runtime/pprof"
	"runtime/trace"
	"time"

	"golang.org/x/tools/internal/jsonrpc2"
	"golang.org/x/tools/internal/lsp"
)

var (
	cpuprofile = flag.String("cpuprofile", "", "write CPU profile to this file")
	memprofile = flag.String("memprofile", "", "write memory profile to this file")
	traceFlag  = flag.String("trace", "", "write trace log to this file")

	// Flags for compatitibility with VSCode.
	logfile = flag.String("logfile", "", "filename to log to")
	mode    = flag.String("mode", "", "no effect")
)

func main() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: golsp [flags]\n")
		flag.PrintDefaults()
	}
	flag.Parse()
	if flag.NArg() > 0 {
		flag.Usage()
		os.Exit(2)
	}

	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		if err := pprof.StartCPUProfile(f); err != nil {
			log.Fatal(err)
		}
		// NB: profile won't be written in case of error.
		defer pprof.StopCPUProfile()
	}

	if *traceFlag != "" {
		f, err := os.Create(*traceFlag)
		if err != nil {
			log.Fatal(err)
		}
		if err := trace.Start(f); err != nil {
			log.Fatal(err)
		}
		// NB: trace log won't be written in case of error.
		defer func() {
			trace.Stop()
			log.Printf("To view the trace, run:\n$ go tool trace view %s", *traceFlag)
		}()
	}

	if *memprofile != "" {
		f, err := os.Create(*memprofile)
		if err != nil {
			log.Fatal(err)
		}
		// NB: memprofile won't be written in case of error.
		defer func() {
			runtime.GC() // get up-to-date statistics
			if err := pprof.WriteHeapProfile(f); err != nil {
				log.Fatalf("Writing memory profile: %v", err)
			}
			f.Close()
		}()
	}

	out := os.Stderr
	if *logfile != "" {
		f, err := os.Create(*logfile)
		if err != nil {
			log.Fatalf("Unable to create log file: %v", err)
		}
		defer f.Close()
		log.SetOutput(io.MultiWriter(os.Stderr, f))
		out = f
	}
	if err := lsp.RunServer(
		context.Background(),
		jsonrpc2.NewHeaderStream(os.Stdin, os.Stdout),
		func(direction jsonrpc2.Direction, id *jsonrpc2.ID, elapsed time.Duration, method string, payload *json.RawMessage, err *jsonrpc2.Error) {

			if err != nil {
				fmt.Fprintf(out, "[Error - %v] %s %s%s %v", time.Now().Format("3:04:05 PM"), direction, method, id, err)
				return
			}
			fmt.Fprintf(out, "[Trace - %v] ", time.Now().Format("3:04:05 PM"))
			switch direction {
			case jsonrpc2.Send:
				fmt.Fprint(out, "Received ")
			case jsonrpc2.Receive:
				fmt.Fprint(out, "Sending ")
			}
			switch {
			case id == nil:
				fmt.Fprint(out, "notification ")
			case elapsed >= 0:
				fmt.Fprint(out, "response ")
			default:
				fmt.Fprint(out, "request ")
			}
			fmt.Fprintf(out, "'%s", method)
			switch {
			case id == nil:
				// do nothing
			case id.Name != "":
				fmt.Fprintf(out, " - (%s)", id.Name)
			default:
				fmt.Fprintf(out, " - (%d)", id.Number)
			}
			fmt.Fprint(out, "'")
			if elapsed >= 0 {
				fmt.Fprintf(out, " in %vms", elapsed.Nanoseconds()/1000)
			}
			params := string(*payload)
			if params == "null" {
				params = "{}"
			}
			fmt.Fprintf(out, ".\r\nParams: %s\r\n\r\n\r\n", params)
		},
	); err != nil {
		log.Fatal(err)
	}
}
