package socks

import (
	"fmt"
	"io"
)

const (
	// NoAuth means no authentication is used when connecting
	NoAuth = uint8(0)

	// UserPassAuth means a user/password is used when connecting
	UserPassAuth = uint8(2)

	noAcceptable    = uint8(255)
	userAuthVersion = uint8(1)
	authSuccess     = uint8(0)
	authFailure     = uint8(1)
)

// AuthHandler handles socks authenication requests
type AuthHandler interface {
	Handle(io.Reader, io.Writer) error
	Register(uint8, Authenticator)
}

// StandardAuthHandler loads the default authenticators
type StandardAuthHandler struct {
	authenticators map[uint8]Authenticator
}

// NewAuthHandler creates a default auth handler
func NewAuthHandler() AuthHandler {
	defaults := make(map[uint8]Authenticator)
	defaults[NoAuth] = NewNoAuthAuthenticator()
	return &StandardAuthHandler{
		authenticators: defaults,
	}
}

// Register adds/replaces an Authenticator to use when handling Authentication requests
func (h *StandardAuthHandler) Register(method uint8, a Authenticator) {
	h.authenticators[method] = a
}

// Handle gets the methods from the SOCKS5 client and authenicates with the first supported method
func (h *StandardAuthHandler) Handle(bufConn io.Reader, conn io.Writer) error {
	methods, err := readMethods(bufConn)
	if err != nil {
		return fmt.Errorf("Failed to read auth methods: %v", err)
	}

	// first supported method is used
	for _, method := range methods {
		authenticator := h.authenticators[method]
		if authenticator != nil {
			return authenticator.Handle(bufConn, conn)
		}
	}

	// failed to authenticate. No supported authentication type found
	conn.Write([]byte{socks5Version, noAcceptable})
	return fmt.Errorf("unknown authentication type")
}

// readMethods is used to read the number and type of methods
func readMethods(r io.Reader) ([]byte, error) {
	header := []byte{0}
	if _, err := r.Read(header); err != nil {
		return nil, err
	}

	numMethods := int(header[0])
	methods := make([]byte, numMethods)
	_, err := io.ReadAtLeast(r, methods, numMethods)
	return methods, err
}
