package ws

import "unicode/utf8"

// State represents state of websocket endpoint.
// It used by some functions to be more strict when checking compatibility with RFC6455.
type State uint8

const (
	// StateServerSide means that endpoint (caller) is a server.
	StateServerSide State = 0x1 << iota
	// StateClientSide means that endpoint (caller) is a client.
	StateClientSide
	// StateExtended means that extension was negotiated during handshake.
	StateExtended
	// StateFragmented means that endpoint (caller) has received fragmented
	// frame and waits for continuation parts.
	StateFragmented
)

// Is checks whether the s has v enabled.
func (s State) Is(v State) bool {
	return uint8(s)&uint8(v) != 0
}

// Set enables v state on s.
func (s State) Set(v State) State {
	return s | v
}

// Clear disables v state on s.
func (s State) Clear(v State) State {
	return s & (^v)
}

// ServerSide reports whether states represents server side.
func (s State) ServerSide() bool { return s.Is(StateServerSide) }

// ClientSide reports whether state represents client side.
func (s State) ClientSide() bool { return s.Is(StateClientSide) }

// Extended reports whether state is extended.
func (s State) Extended() bool { return s.Is(StateExtended) }

// Fragmented reports whether state is fragmented.
func (s State) Fragmented() bool { return s.Is(StateFragmented) }

// ProtocolError describes error during checking/parsing websocket frames or
// headers.
type ProtocolError string

// Error implements error interface.
func (p ProtocolError) Error() string { return string(p) }

// Errors used by the protocol checkers.
var (
	ErrProtocolOpCodeReserved             = ProtocolError("use of reserved op code")
	ErrProtocolControlPayloadOverflow     = ProtocolError("control frame payload limit exceeded")
	ErrProtocolControlNotFinal            = ProtocolError("control frame is not final")
	ErrProtocolNonZeroRsv                 = ProtocolError("non-zero rsv bits with no extension negotiated")
	ErrProtocolMaskRequired               = ProtocolError("frames from client to server must be masked")
	ErrProtocolMaskUnexpected             = ProtocolError("frames from server to client must be not masked")
	ErrProtocolContinuationExpected       = ProtocolError("unexpected non-continuation data frame")
	ErrProtocolContinuationUnexpected     = ProtocolError("unexpected continuation data frame")
	ErrProtocolStatusCodeNotInUse         = ProtocolError("status code is not in use")
	ErrProtocolStatusCodeApplicationLevel = ProtocolError("status code is only application level")
	ErrProtocolStatusCodeNoMeaning        = ProtocolError("status code has no meaning yet")
	ErrProtocolStatusCodeUnknown          = ProtocolError("status code is not defined in spec")
	ErrProtocolInvalidUTF8                = ProtocolError("invalid utf8 sequence in close reason")
)

// CheckHeader checks h to contain valid header data for given state s.
//
// Note that zero state (0) means that state is clean,
// neither server or client side, nor fragmented, nor extended.
func CheckHeader(h Header, s State) error {
	if h.OpCode.IsReserved() {
		return ErrProtocolOpCodeReserved
	}
	if h.OpCode.IsControl() {
		if h.Length > MaxControlFramePayloadSize {
			return ErrProtocolControlPayloadOverflow
		}
		if !h.Fin {
			return ErrProtocolControlNotFinal
		}
	}

	switch {
	// [RFC6455]: MUST be 0 unless an extension is negotiated that defines meanings for
	// non-zero values. If a nonzero value is received and none of the
	// negotiated extensions defines the meaning of such a nonzero value, the
	// receiving endpoint MUST _Fail the WebSocket Connection_.
	case h.Rsv != 0 && !s.Extended():
		return ErrProtocolNonZeroRsv

	// [RFC6455]: The server MUST close the connection upon receiving a frame that is not masked.
	// In this case, a server MAY send a Close frame with a status code of 1002 (protocol error)
	// as defined in Section 7.4.1. A server MUST NOT mask any frames that it sends to the client.
	// A client MUST close a connection if it detects a masked frame. In this case, it MAY use the
	// status code 1002 (protocol error) as defined in Section 7.4.1.
	case s.ServerSide() && !h.Masked:
		return ErrProtocolMaskRequired
	case s.ClientSide() && h.Masked:
		return ErrProtocolMaskUnexpected

	// [RFC6455]: See detailed explanation in 5.4 section.
	case s.Fragmented() && !h.OpCode.IsControl() && h.OpCode != OpContinuation:
		return ErrProtocolContinuationExpected
	case !s.Fragmented() && h.OpCode == OpContinuation:
		return ErrProtocolContinuationUnexpected
	}

	return nil
}

// CheckCloseFrameData checks received close information
// to be valid RFC6455 compatible close info.
//
// Note that code.Empty() or code.IsAppLevel() will raise error.
//
// If endpoint sends close frame without status code (with frame.Length = 0),
// application should not check its payload.
func CheckCloseFrameData(code StatusCode, reason string) error {
	switch {
	case code.IsNotUsed():
		return ErrProtocolStatusCodeNotInUse

	case code.IsProtocolReserved():
		return ErrProtocolStatusCodeApplicationLevel

	case code == StatusNoMeaningYet:
		return ErrProtocolStatusCodeNoMeaning

	case code.IsProtocolSpec() && !code.IsProtocolDefined():
		return ErrProtocolStatusCodeUnknown

	case !utf8.ValidString(reason):
		return ErrProtocolInvalidUTF8

	default:
		return nil
	}
}
