// Copyright 2021 The gVisor 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 eventchannel

import (
	"encoding/binary"
	"fmt"
	"io"
	"os"
	"time"

	"google.golang.org/protobuf/proto"
	"google.golang.org/protobuf/types/known/anypb"
	pb "gvisor.dev/gvisor/pkg/eventchannel/eventchannel_go_proto"
)

// eventProcessor carries display state across multiple events.
type eventProcessor struct {
	filtering bool
	// filtered is the number of events omitted since printing the last matching
	// event. Only meaningful when filtering == true.
	filtered uint64
	// allowlist is the set of event names to display. If empty, all events are
	// displayed.
	allowlist map[string]bool
}

// newEventProcessor creates a new EventProcessor with filters.
func newEventProcessor(filters []string) *eventProcessor {
	e := &eventProcessor{
		filtering: len(filters) > 0,
		allowlist: make(map[string]bool),
	}
	for _, f := range filters {
		e.allowlist[f] = true
	}
	return e
}

// processOne reads, parses and displays a single event from the event channel.
//
// The event channel is a stream of (msglen, payload) packets; this function
// processes a single such packet. The msglen is a uvarint-encoded length for
// the associated payload. The payload is a binary-encoded 'Any' protobuf, which
// in turn encodes an arbitrary event protobuf.
func (e *eventProcessor) processOne(src io.Reader, out *os.File) error {
	// Read and parse the msglen.
	lenbuf := make([]byte, binary.MaxVarintLen64)
	if _, err := io.ReadFull(src, lenbuf); err != nil {
		return err
	}
	msglen, consumed := binary.Uvarint(lenbuf)
	if consumed <= 0 {
		return fmt.Errorf("couldn't parse the message length")
	}

	// Read the payload.
	buf := make([]byte, msglen)
	// Copy any unused bytes from the len buffer into the payload buffer. These
	// bytes are actually part of the payload.
	extraBytes := copy(buf, lenbuf[consumed:])
	if _, err := io.ReadFull(src, buf[extraBytes:]); err != nil {
		return err
	}

	// Unmarshal the payload into an "Any" protobuf, which encodes the actual
	// event.
	encodedEv := anypb.Any{}
	if err := proto.Unmarshal(buf, &encodedEv); err != nil {
		return fmt.Errorf("failed to unmarshal 'any' protobuf message: %v", err)
	}

	var ev pb.DebugEvent
	if err := encodedEv.UnmarshalTo(&ev); err != nil {
		return fmt.Errorf("failed to decode 'any' protobuf message: %v", err)
	}

	if e.filtering && e.allowlist[ev.Name] {
		e.filtered++
		return nil
	}

	if e.filtering && e.filtered > 0 {
		if e.filtered == 1 {
			fmt.Fprintf(out, "... filtered %d event ...\n\n", e.filtered)
		} else {
			fmt.Fprintf(out, "... filtered %d events ...\n\n", e.filtered)
		}
		e.filtered = 0
	}

	// Extract the inner event and display it. Example:
	//
	//   2017-10-04 14:35:05.316180374 -0700 PDT m=+1.132485846
	//   cloud_gvisor.MemoryUsage {
	//   total: 23822336
	//   }
	fmt.Fprintf(out, "%v\n%v {\n", time.Now(), ev.Name)
	fmt.Fprintf(out, "%v", ev.Text)
	fmt.Fprintf(out, "}\n\n")

	return nil
}

// ProcessAll reads, parses and displays all events from src. The events are
// displayed to out.
func ProcessAll(src io.Reader, filters []string, out *os.File) error {
	ep := newEventProcessor(filters)
	for {
		switch err := ep.processOne(src, out); err {
		case nil:
			continue
		case io.EOF:
			return nil
		default:
			return err
		}
	}
}
