package service

import "os"
import "log"
import "fmt"
import "net"
import "errors"
import "strings"
import "crypto/tls"
import "encoding/base64"
import "hnakra/protocol"

// M creates a very basic mount with the specified name and description.
func M (name, description string) Mount {
	return Mount {
		Name:        name,
		Description: description,
	}
}

// Mount contains generic information common to all mounts.
type Mount struct {
	// Host specifies the host to mount on. If the host is left empty, it
	// will default to @ (meaning default/any host). The port is entirely up
	// to the router. Maximum length for host portion: 255 bytes
	Host string

	// Path specifies the path to mount on. If the path ends with a /, then
	// all requests under the path will be sent to this service. If there is
	// no trailing /, this service will only recieve requests that match the
	// path exactly (when normalized).
	// Maximum length: 2^16-1 bytes
	Path string

	// Router specifies the host:port of the router to connect to. This
	// defaults to $HNAKRA_ROUTER_HOST:$HNAKRA_ROUTER_PORT if left empty.
	// The default value of these environment variables (if not set) is
	// localhost:2048. If you are giving this service to other people, it is
	// a good idea to leave this empty.
	Router string

	// Name should be set to a human-readable name of the service.
	// Maximum length: 255 bytes
	Name string

	// Description should be set to a human-readable description of what the
	// service does.
	// Maximum length: 255 bytes
	Description string

	// User is an optional string that specifies who is connecting to the
	// router. This can be used by routers in conjunction with the key
	// bytes in order to enforce rules about which service can do what. This
	// defaults to $HNAKRA_USER if left empty. If you are giving this
	// service to other people, it is a good idea to leave this empty.
	// Maximum length: 255 bytes
	User string

	// Key is an optional byte slice that is sent to the router in order for
	// it to authorize the connection. If nil, this defaults to the contents
	// of $HNAKRA_KEY interpreted as base64. Do not embed a key in your
	// code - you should only use this if your service reads the key from a
	// safe source upon startup. In addition to this, If you choose to set
	// the key via the aforementioned enviroment variable, ensure that it
	// can only be read by the service.
	// Maximum length: 255 bytes
	Key []byte

	// TLSConfig is an optional TLS configuration.
	TLSConfig *tls.Config

	idFactory *protocol.IDFactory
}

func (mount *Mount) connect (scheme string) (conn net.Conn, err error) {
	log.Println("(i) service", mount.Name)
	mount.idFactory = protocol.NewServiceIDFactory()
	
	defaultRouterHost := os.Getenv("HNAKRA_ROUTER_HOST")
	if defaultRouterHost == "" {
		defaultRouterHost = "localhost"
	}
	defaultRouterPort := os.Getenv("HNAKRA_ROUTER_PORT")
	if defaultRouterPort == "" {
		defaultRouterPort = "2048"
	}

	// parse router host/port
	routerHost, routerPort, _ := strings.Cut(mount.Router, ":")
	if routerHost == "" {
		routerHost = defaultRouterHost
	}
	if routerPort == "" {
		routerPort = defaultRouterPort
	}

	// get mount point
	host := mount.Host
	if host == "" {
		host = "@"
	}
	if len(host) > 255 {
		return nil, errors.New("mount point host cannot be longer than 255 bytes")
	}
	path := mount.Path
	if path == "" {
		path = "/"
	}
	if len(path) > int(protocol.MaxIntOfSize(2)) {
		return nil, errors.New(fmt.Sprint (
			"mount point path cannot be longer than ",
			protocol.MaxIntOfSize(2), " bytes"))
	}

	// get user
	user := mount.User
	if user == "" {
		user = os.Getenv("HNAKRA_USER")
	}
	if len(user) > 255 {
		return nil, errors.New("user cannot be longer than 255 bytes")
	}

	// get key
	key := mount.Key
	if key == nil {
		key, err = base64.StdEncoding.DecodeString(os.Getenv("HNAKRA_KEY"))
		if err != nil { return nil, err }
	}
	if len(key) > 255 {
		return nil, errors.New("key cannot be longer than 255 bytes")
	}

	// ensure name/description aren't too big
	if len(mount.Name) > 255 {
		return nil, errors.New("service name cannot be longer than 255 bytes")
	}
	if len(mount.Description) > 255 {
		return nil, errors.New("service description cannot be longer than 255 bytes")
	}

	// connect to router
	routerAddr := fmt.Sprint(routerHost, ":", routerPort)
	log.Println("... dialing", routerAddr)
	conn, err = tls.Dial("tcp", routerAddr, mount.TLSConfig)
	if err != nil { return nil, err }
	
	// log in
	log.Println (
		"... logging in as", user,
		"on", scheme + "://" + host + path)
	err = protocol.MessageLogin {
		ID:          mount.idFactory.Next(),
		Version:     protocol.Version { Major: 0, Minor: 0 },
		User:        user,
		Key:         key,
		Name:        mount.Name,
		Description: mount.Description,
		Scheme:      scheme,
		Host:        host,
		Path:        path,
	}.Send(conn)
	if err != nil {
		conn.Close()
		return nil, err
	}

	// read status
	message, err := protocol.ReadMessage(conn)
	if err != nil {
		conn.Close()
		return nil, err
	}
	status, ok := message.(protocol.MessageStatus)
	if !ok {
		conn.Close()
		return nil, errors.New(fmt.Sprint("router sent unknown type, expecting", protocol.TypeStatus))
	}
	if status.Status != protocol.StatusOk {
		return nil, status
	}
	
	log.Println(".// logged in")
	return conn, nil
}

// TODO: other protocols like gemini, websocket, ftp, etc
