// Copyright 2019 The gRPC Authors
//
// 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
//
//     http://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 http2interop

import (
	"encoding/binary"
	"fmt"
	"io"
)

type FrameHeader struct {
	Length   int
	Type     FrameType
	Flags    byte
	Reserved Reserved
	StreamID
}

type Reserved bool

func (r Reserved) String() string {
	if r {
		return "R"
	}
	return ""
}

func (fh *FrameHeader) Parse(r io.Reader) error {
	buf := make([]byte, 9)
	if _, err := io.ReadFull(r, buf); err != nil {
		return err
	}
	return fh.UnmarshalBinary(buf)
}

func (fh *FrameHeader) UnmarshalBinary(b []byte) error {
	if len(b) != 9 {
		return fmt.Errorf("Invalid frame header length %d", len(b))
	}
	*fh = FrameHeader{
		Length:   int(b[0])<<16 | int(b[1])<<8 | int(b[2]),
		Type:     FrameType(b[3]),
		Flags:    b[4],
		Reserved: Reserved(b[5]>>7 == 1),
		StreamID: StreamID(binary.BigEndian.Uint32(b[5:9]) & 0x7fffffff),
	}
	return nil
}

func (fh *FrameHeader) MarshalBinary() ([]byte, error) {
	buf := make([]byte, 9, 9+fh.Length)

	if fh.Length > 0xFFFFFF || fh.Length < 0 {
		return nil, fmt.Errorf("Invalid frame header length: %d", fh.Length)
	}
	if fh.StreamID < 0 {
		return nil, fmt.Errorf("Invalid Stream ID: %v", fh.StreamID)
	}

	buf[0], buf[1], buf[2] = byte(fh.Length>>16), byte(fh.Length>>8), byte(fh.Length)
	buf[3] = byte(fh.Type)
	buf[4] = fh.Flags
	var res uint32
	if fh.Reserved {
		res = 0x80000000
	}
	binary.BigEndian.PutUint32(buf[5:], uint32(fh.StreamID)|res)

	return buf, nil
}

type StreamID int32

type FrameType byte

func (ft FrameType) String() string {
	switch ft {
	case DataFrameType:
		return "DATA"
	case HeadersFrameType:
		return "HEADERS"
	case PriorityFrameType:
		return "PRIORITY"
	case ResetStreamFrameType:
		return "RST_STREAM"
	case SettingsFrameType:
		return "SETTINGS"
	case PushPromiseFrameType:
		return "PUSH_PROMISE"
	case PingFrameType:
		return "PING"
	case GoAwayFrameType:
		return "GOAWAY"
	case WindowUpdateFrameType:
		return "WINDOW_UPDATE"
	case ContinuationFrameType:
		return "CONTINUATION"
	case HTTP1FrameType:
		return "HTTP/1.? (Bad)"
	default:
		return fmt.Sprintf("UNKNOWN(%d)", byte(ft))
	}
}

// Types
const (
	DataFrameType         FrameType = 0
	HeadersFrameType      FrameType = 1
	PriorityFrameType     FrameType = 2
	ResetStreamFrameType  FrameType = 3
	SettingsFrameType     FrameType = 4
	PushPromiseFrameType  FrameType = 5
	PingFrameType         FrameType = 6
	GoAwayFrameType       FrameType = 7
	WindowUpdateFrameType FrameType = 8
	ContinuationFrameType FrameType = 9

	// HTTP1FrameType is not a real type, but rather a convenient way to check if the response
	// is an http response.  The type of a frame header is the 4th byte, which in an http1
	// response will be "HTTP/1.1 200 OK" or something like that.  The character for "P" is 80.
	HTTP1FrameType FrameType = 80
)
