// Copyright 2011 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.

// +build golangorg

package main

// This file replaces main.go when running godoc under app-engine.
// See README.godoc-app for details.

import (
	"archive/zip"
	"context"
	"io"
	"log"
	"net/http"
	"os"
	"path"
	"regexp"
	"runtime"
	"strings"

	"golang.org/x/tools/godoc"
	"golang.org/x/tools/godoc/dl"
	"golang.org/x/tools/godoc/proxy"
	"golang.org/x/tools/godoc/redirect"
	"golang.org/x/tools/godoc/short"
	"golang.org/x/tools/godoc/static"
	"golang.org/x/tools/godoc/vfs"
	"golang.org/x/tools/godoc/vfs/gatefs"
	"golang.org/x/tools/godoc/vfs/mapfs"
	"golang.org/x/tools/godoc/vfs/zipfs"

	"cloud.google.com/go/datastore"
	"golang.org/x/tools/internal/memcache"
)

func main() {
	log.SetFlags(log.Lshortfile | log.LstdFlags)

	var (
		// .zip filename
		zipFilename = os.Getenv("GODOC_ZIP")

		// goroot directory in .zip file
		zipGoroot = os.Getenv("GODOC_ZIP_PREFIX")

		// glob pattern describing search index files
		// (if empty, the index is built at run-time)
		indexFilenames = os.Getenv("GODOC_INDEX_GLOB")
	)

	playEnabled = true

	log.Println("initializing godoc ...")
	log.Printf(".zip file   = %s", zipFilename)
	log.Printf(".zip GOROOT = %s", zipGoroot)
	log.Printf("index files = %s", indexFilenames)

	if zipFilename != "" {
		goroot := path.Join("/", zipGoroot) // fsHttp paths are relative to '/'
		// read .zip file and set up file systems
		rc, err := zip.OpenReader(zipFilename)
		if err != nil {
			log.Fatalf("%s: %s\n", zipFilename, err)
		}
		// rc is never closed (app running forever)
		fs.Bind("/", zipfs.New(rc, zipFilename), goroot, vfs.BindReplace)
	} else {
		rootfs := gatefs.New(vfs.OS(runtime.GOROOT()), make(chan bool, 20))
		fs.Bind("/", rootfs, "/", vfs.BindReplace)
	}

	fs.Bind("/lib/godoc", mapfs.New(static.Files), "/", vfs.BindReplace)

	corpus := godoc.NewCorpus(fs)
	corpus.Verbose = false
	corpus.MaxResults = 10000 // matches flag default in main.go
	corpus.IndexEnabled = true
	corpus.IndexFiles = indexFilenames
	if err := corpus.Init(); err != nil {
		log.Fatal(err)
	}
	corpus.IndexDirectory = indexDirectoryDefault
	corpus.InitVersionInfo()
	go corpus.RunIndexer()

	pres = godoc.NewPresentation(corpus)
	pres.TabWidth = 8
	pres.ShowPlayground = true
	pres.DeclLinks = true
	pres.NotesRx = regexp.MustCompile("BUG")
	pres.GoogleAnalytics = os.Getenv("GODOC_ANALYTICS")

	readTemplates(pres)

	datastoreClient, memcacheClient := getClients()

	// NOTE(cbro): registerHandlers registers itself against DefaultServeMux.
	// The mux returned has host enforcement, so it's important to register
	// against this mux and not DefaultServeMux.
	mux := registerHandlers(pres)
	dl.RegisterHandlers(mux, datastoreClient, memcacheClient)
	short.RegisterHandlers(mux, datastoreClient, memcacheClient)

	// Register /compile and /share handlers against the default serve mux
	// so that other app modules can make plain HTTP requests to those
	// hosts. (For reasons, HTTPS communication between modules is broken.)
	proxy.RegisterHandlers(http.DefaultServeMux)

	http.HandleFunc("/_ah/health", func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, "ok")
	})

	http.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
		io.WriteString(w, "User-agent: *\nDisallow: /search\n")
	})

	if err := redirect.LoadChangeMap("hg-git-mapping.bin"); err != nil {
		log.Fatalf("LoadChangeMap: %v", err)
	}

	log.Println("godoc initialization complete")

	// TODO(cbro): add instrumentation via opencensus.
	port := "8080"
	if p := os.Getenv("PORT"); p != "" { // PORT is set by GAE flex.
		port = p
	}
	log.Fatal(http.ListenAndServe(":"+port, nil))
}

func getClients() (*datastore.Client, *memcache.Client) {
	ctx := context.Background()

	datastoreClient, err := datastore.NewClient(ctx, "")
	if err != nil {
		if strings.Contains(err.Error(), "missing project") {
			log.Fatalf("Missing datastore project. Set the DATASTORE_PROJECT_ID env variable. Use `gcloud beta emulators datastore` to start a local datastore.")
		}
		log.Fatalf("datastore.NewClient: %v.", err)
	}

	redisAddr := os.Getenv("GODOC_REDIS_ADDR")
	if redisAddr == "" {
		log.Fatalf("Missing redis server for godoc in production mode. set GODOC_REDIS_ADDR environment variable.")
	}
	memcacheClient := memcache.New(redisAddr)
	return datastoreClient, memcacheClient
}
