// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"sync"

	apiv1 "github.com/google/android-cuttlefish/frontend/src/liboperator/api/v1"
	"github.com/google/android-cuttlefish/frontend/src/liboperator/operator"
)

const (
	DefaultSocketPath        = "/run/cuttlefish/operator"
	DefaultControlSocketPath = "/run/cuttlefish/operator_control"
	DefaultHttpPort          = 1080
	DefaultTLSCertDir        = "/etc/cuttlefish-common/operator/cert"
	DefaultStaticFilesDir    = "static"    // relative path
	DefaultInterceptDir      = "intercept" // relative path
	DefaultWebUIUrl          = ""
	DefaultListenAddress     = "127.0.0.1"
)

func startHttpServer(address string, port int) error {
	log.Println(fmt.Sprint("Operator is listening at http://localhost:", port))

	// handler is nil, so DefaultServeMux is used.
	return http.ListenAndServe(fmt.Sprintf("%s:%d", address, port), nil)
}

func startHttpsServer(address string, port int, certPath string, keyPath string) error {
	log.Println(fmt.Sprint("Operator is listening at https://localhost:", port))
	return http.ListenAndServeTLS(fmt.Sprintf("%s:%d", address, port),
		certPath,
		keyPath,
		// handler is nil, so DefaultServeMux is used.
		//
		// Using DefaultServerMux in both servers (http and https) is not a problem
		// as http.ServeMux instances are thread safe.
		nil)
}

// Whether a device file request should be intercepted and served from the signaling server instead
func maybeIntercept(path string) *string {
	if path == "/js/server_connector.js" {
		alt := fmt.Sprintf("%s%s", DefaultInterceptDir, path)
		return &alt
	}
	return nil
}

func start(starters []func() error) {
	wg := new(sync.WaitGroup)
	wg.Add(len(starters))
	for _, starter := range starters {
		go func(f func() error) {
			defer wg.Done()
			if err := f(); err != nil {
				log.Fatal(err)
			}
		}(starter)
	}
	wg.Wait()
}

func main() {
	socketPath := flag.String("socket_path", DefaultSocketPath, "Path to the device endpoint unix socket.")
	controlSocketPath := flag.String("control_socket_path", DefaultControlSocketPath, "Path to the control endpoint unix socket.")
	httpPort := flag.Int("http_port", DefaultHttpPort, "Port to serve HTTP requests on.")
	httpsPort := flag.Int("https_port", -1, "Port to serve HTTPS requests on.")
	tlsCertDir := flag.String("tls_cert_dir", DefaultTLSCertDir, "Directory where the TLS certificates are located.")
	webUiUrlStr := flag.String("webui_url", DefaultWebUIUrl, "WebUI URL.")
	address := flag.String("listen_addr", DefaultListenAddress, "IP address to listen for requests.")
	logFile := flag.String("log_file", "", "Path to file to write logs to.")

	flag.Parse()

	if *logFile != "" {
		f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening log file %q: %v", *logFile, err)
		}
		defer f.Close()
		w := io.MultiWriter(os.Stderr, f)
		log.SetOutput(w)
	}

	certPath := *tlsCertDir + "/cert.pem"
	keyPath := *tlsCertDir + "/key.pem"

	pool := operator.NewDevicePool()
	polledSet := operator.NewPolledSet()
	config := apiv1.InfraConfig{
		Type: "config",
		IceServers: []apiv1.IceServer{
			{URLs: []string{"stun:stun.l.google.com:19302"}},
		},
	}

	r := operator.CreateHttpHandlers(pool, polledSet, config, maybeIntercept)
	if *webUiUrlStr != "" {
		webUiUrl, _ := url.Parse(*webUiUrlStr)
		proxy := httputil.NewSingleHostReverseProxy(webUiUrl)
		proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
			log.Printf("request %q failed: proxy error: %v", r.Method+" "+r.URL.Path, err)
			w.Header().Add("x-cutf-proxy", "op-webui")
			w.WriteHeader(http.StatusBadGateway)
		}
		r.PathPrefix("/").Handler(proxy)
	} else {
		fs := http.FileServer(http.Dir(DefaultStaticFilesDir))
		r.PathPrefix("/").Handler(fs)
	}
	http.Handle("/", r)

	starters := []func() error{
		func() error {
			st, err := operator.SetupControlEndpoint(pool, *controlSocketPath)
			if err != nil {
				return err
			}
			return st()
		},
		func() error {
			st, err := operator.SetupDeviceEndpoint(pool, config, *socketPath)
			if err != nil {
				return err
			}
			return st()
		},
		func() error { return startHttpServer(*address, *httpPort) },
	}
	if *httpsPort > 0 {
		starters = append(starters, func() error {
			return startHttpsServer(*address, *httpsPort, certPath, keyPath)
		})
	}
	start(starters)
}
