/*
 * 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_METRICS_METRICS_H_
#define SRC_TRACE_PROCESSOR_METRICS_METRICS_H_

#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>

#include "perfetto/base/status.h"
#include "perfetto/ext/base/status_or.h"
#include "perfetto/ext/base/string_view.h"
#include "perfetto/protozero/message.h"
#include "perfetto/protozero/packed_repeated_fields.h"
#include "perfetto/protozero/scattered_heap_buffer.h"
#include "perfetto/trace_processor/basic_types.h"
#include "perfetto/trace_processor/trace_processor.h"
#include "src/trace_processor/perfetto_sql/engine/perfetto_sql_connection.h"
#include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h"
#include "src/trace_processor/sqlite/bindings/sqlite_function.h"
#include "src/trace_processor/sqlite/bindings/sqlite_result.h"
#include "src/trace_processor/sqlite/bindings/sqlite_type.h"
#include "src/trace_processor/sqlite/bindings/sqlite_value.h"
#include "src/trace_processor/util/descriptors.h"

#include "protos/perfetto/trace_processor/metrics_impl.pbzero.h"

namespace perfetto::trace_processor::metrics {

// A description of a SQL metric in C++.
struct SqlMetricFile {
  // The path of this file with the root at the metrics root.
  std::string path;

  // The field in the output proto which will be filled by the result of
  // querying the table specified by |output_table_name|.
  // Optional because not all protos need to have a field associated with them
  // in the root proto; most files will be just be run using RUN_METRIC by
  // other files.
  std::optional<std::string> proto_field_name;

  // The table name which will be created by the SQL below to read the proto
  // bytes from.
  // Should only be set when |proto_field_name| is set.
  std::optional<std::string> output_table_name;

  // The SQL run by this metric.
  std::string sql;
};

// Helper class to build a nested (metric) proto checking the schema against
// a descriptor.
// Visible for testing.
class ProtoBuilder {
 public:
  ProtoBuilder(const DescriptorPool*, const ProtoDescriptor*);

  base::Status AppendSqlValue(const std::string& field_name,
                              const SqlValue& value);

  // Returns the serialized |protos::ProtoBuilderResult| with the built proto
  // as the nested |protobuf| message.
  // Note: no other functions should be called on this class after this method
  // is called.
  std::vector<uint8_t> SerializeToProtoBuilderResult();

  // Returns the serialized version of the raw message being built.
  // This function should only be used at the top level where type checking is
  // no longer important because the proto will be returned as is. In all other
  // instances, prefer |SerializeToProtoBuilderResult()| instead.
  // Note: no other functions should be called on this class after this method
  // is called.
  std::vector<uint8_t> SerializeRaw();

 private:
  base::Status AppendSingleLong(const FieldDescriptor& field, int64_t value);
  base::Status AppendSingleDouble(const FieldDescriptor& field, double value);
  base::Status AppendSingleString(const FieldDescriptor& field,
                                  base::StringView data);
  base::Status AppendSingleBytes(const FieldDescriptor& field,
                                 const uint8_t* ptr,
                                 size_t size);
  base::Status AppendRepeated(const FieldDescriptor& field,
                              const uint8_t* ptr,
                              size_t size);

  base::StatusOr<const FieldDescriptor*> FindFieldByName(
      const std::string& field_name);

  const DescriptorPool* pool_ = nullptr;
  const ProtoDescriptor* descriptor_ = nullptr;
  protozero::HeapBuffered<protozero::Message> message_;
};

// Helper class to combine a set of repeated fields into a single proto blob
// to return to SQLite.
// Visible for testing.
class RepeatedFieldBuilder {
 public:
  RepeatedFieldBuilder();

  base::Status AddSqlValue(SqlValue value);

  // Returns the serialized |protos::ProtoBuilderResult| with the set of
  // repeated fields as |repeated_values| in the proto.
  // Note: no other functions should be called on this class after this method
  // is called.
  std::vector<uint8_t> SerializeToProtoBuilderResult();

 private:
  base::Status AddLong(int64_t value);
  base::Status AddDouble(double value);
  base::Status AddString(base::StringView value);
  base::Status AddBytes(const uint8_t* data, size_t size);

  base::Status EnsureType(SqlValue::Type);

  protozero::HeapBuffered<protos::pbzero::ProtoBuilderResult> message_;
  std::optional<SqlValue::Type> repeated_field_type_;
  protos::pbzero::RepeatedBuilderResult* repeated_ = nullptr;
  protozero::PackedFixedSizeInt<int64_t> int64_packed_repeated_;
  protozero::PackedFixedSizeInt<double> double_packed_repeated_;
};

// Replaces templated variables inside |raw_text| using the substitution given
// by |substitutions| writing the result to |out|.
// The syntax followed is a cut-down variant of Jinja. This means variables that
// are to be replaced use {{variable-name}} in the raw text with subsitutions
// containing a mapping from (variable-name -> replacement).
int TemplateReplace(
    const std::string& raw_text,
    const std::unordered_map<std::string, std::string>& substitutions,
    std::string* out);

// Implements the NULL_IF_EMPTY SQL function.
struct NullIfEmpty : public sqlite::Function<NullIfEmpty> {
  static constexpr char kName[] = "null_if_empty";
  static constexpr int kArgCount = 1;

  static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv);
};

// Implements all the proto creation functions.
struct BuildProto : public sqlite::Function<BuildProto> {
  struct UserData {
    TraceProcessor* tp;
    const DescriptorPool* pool;
    uint32_t descriptor_idx;
  };

  static constexpr char kName[] = "build_proto";
  static constexpr int kArgCount = -1;  // Variable arguments

  static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv);
};

// Implements the RUN_METRIC SQL function.
struct RunMetric : public sqlite::Function<RunMetric> {
  struct UserData {
    PerfettoSqlConnection* connection;
    std::vector<SqlMetricFile>* metrics;
  };

  static constexpr char kName[] = "run_metric";
  static constexpr int kArgCount = -1;  // Variable arguments

  static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv);
};

// Implements the UNWRAP_METRIC_PROTO SQL function.
struct UnwrapMetricProto : public sqlite::Function<UnwrapMetricProto> {
  static constexpr char kName[] = "unwrap_metric_proto";
  static constexpr int kArgCount = 2;

  static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv);
};

// These functions implement the RepeatedField SQL aggregate functions.
struct RepeatedField : public sqlite::AggregateFunction<RepeatedField> {
  static constexpr char kName[] = "RepeatedField";
  static constexpr int kArgCount = 1;

  static void Step(sqlite3_context* ctx, int argc, sqlite3_value** argv);
  static void Final(sqlite3_context* ctx);
};

base::Status ComputeMetrics(PerfettoSqlConnection*,
                            const std::vector<std::string>& metrics_to_compute,
                            const std::vector<SqlMetricFile>& metrics,
                            const DescriptorPool& pool,
                            const ProtoDescriptor& root_descriptor,
                            std::vector<uint8_t>* metrics_proto);

}  // namespace perfetto::trace_processor::metrics

#endif  // SRC_TRACE_PROCESSOR_METRICS_METRICS_H_
