/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * 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.
 */

#ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_IMPORTER_MODULE_H_
#define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_IMPORTER_MODULE_H_

#include <optional>

#include "perfetto/base/status.h"
#include "src/trace_processor/importers/common/trace_parser.h"

namespace perfetto {

namespace protos {
namespace pbzero {
class TraceConfig_Decoder;
class TracePacket_Decoder;
}  // namespace pbzero
}  // namespace protos

namespace trace_processor {

class PacketSequenceState;
class TraceBlobView;
class TraceProcessorContext;

// This file contains a base class for ProtoTraceReader/Parser modules.
// A module implements support for a subset of features of the TracePacket
// proto format.
// To add and integrate a new module:
// (1) Add MyModule as a subclass of ProtoImporterModule,
//     overriding the TokenizePacket(), ParsePacket() and/or ParseTraceConfig()
//     methods.
// (2) In the constructor call the RegisterForField method for every field
//     that the module knows how to handle.
// (3) Create a module instance and add it to TraceProcessorContext's |modules|
//     vector in either default_modules.cc or additional_modules.cc.
// See GraphicsEventModule for an example.

class ModuleResult {
 public:
  // Allow auto conversion from util::Status to Handled / Error result.
  ModuleResult(base::Status status)
      : ignored_(false),
        error_(status.ok() ? std::nullopt
                           : std::make_optional(status.message())) {}

  // Constructs a result that indicates the module ignored the packet and is
  // deferring the handling of the packet to other modules.
  static ModuleResult Ignored() { return ModuleResult(true); }

  // Constructs a result that indicates the module handled the packet. Other
  // modules will not be notified about the packet.
  static ModuleResult Handled() { return ModuleResult(false); }

  // Constructs a result that indicates an error condition while handling the
  // packet. Other modules will not be notified about the packet.
  static ModuleResult Error(const std::string& message) {
    return ModuleResult(message);
  }

  bool ignored() const { return ignored_; }
  bool ok() const { return !error_.has_value(); }
  const std::string& message() const { return *error_; }

  base::Status ToStatus() const {
    PERFETTO_DCHECK(!ignored_);
    if (error_)
      return base::Status(*error_);
    return base::OkStatus();
  }

 private:
  explicit ModuleResult(bool ignored) : ignored_(ignored) {}
  explicit ModuleResult(const std::string& error)
      : ignored_(false), error_(error) {}

  bool ignored_;
  std::optional<std::string> error_;
};

// Base class for modules.
class ProtoImporterModule {
 public:
  ProtoImporterModule();

  virtual ~ProtoImporterModule();

  // Called by ProtoTraceReader during the tokenization stage, i.e. before
  // sorting. It's called for each TracePacket that contains fields for which
  // the module was registered. If this returns a result other than
  // ModuleResult::Ignored(), tokenization of the packet will be aborted after
  // the module.
  virtual ModuleResult TokenizePacket(
      const protos::pbzero::TracePacket_Decoder&,
      TraceBlobView* packet,
      int64_t packet_timestamp,
      PacketSequenceState*,
      uint32_t field_id);

  // Called by ProtoTraceReader during the tokenization stage i.e. before
  // sorting. Indicates that sequence with id |packet_sequence_id| has cleared
  // its incremental state. This should be used to clear any cached state the
  // tokenizer has built up while reading packets until this point for this
  // packet sequence.
  virtual void OnIncrementalStateCleared(uint32_t /* packet_sequence_id */) {}

  // Called by ProtoTraceReader during the tokenization stage i.e. before
  // sorting. Indicates that sequence with id |packet_sequence_id| has a packet
  // with first_packet_on_sequence = true. This implies that there was no data
  // loss, including ring buffer overwrittes, on this sequence.
  virtual void OnFirstPacketOnSequence(uint32_t /* packet_sequence_id */) {}

  // ParsePacket functions are called by ProtoTraceParser after the sorting
  // stage for each non-ftrace TracePacket that contains fields for which the
  // module was registered.
  virtual void ParseTracePacketData(const protos::pbzero::TracePacket_Decoder&,
                                    int64_t ts,
                                    const TracePacketData&,
                                    uint32_t /*field_id*/);

  // Called by ProtoTraceParser for trace config packets after the sorting
  // stage, on all existing modules.
  virtual void ParseTraceConfig(const protos::pbzero::TraceConfig_Decoder&);

  virtual void NotifyEndOfFile() {}

 protected:
  void RegisterForField(uint32_t field_id, TraceProcessorContext*);
  // Primarily intended for special modules that need to get all TracePacket's,
  // for example for trace proto content analysis. Most modules need to register
  // for specific fields using the method above.
  void RegisterForAllFields(TraceProcessorContext*);
};

}  // namespace trace_processor
}  // namespace perfetto

#endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_PROTO_IMPORTER_MODULE_H_
