/*
 * Copyright (C) 2022 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.
 */

#include <inttypes.h>
#include <stdio.h>
#include <unistd.h>

#include <string>

#include <android-base/stringprintf.h>

#include <memory_trace/MemoryTrace.h>

namespace memory_trace {

// This is larger than the maximum length of a possible line.
constexpr size_t kBufferLen = 256;

bool FillInEntryFromString(const std::string& line, Entry& entry, std::string& error) {
  // All lines have this format:
  //   TID: ALLOCATION_TYPE POINTER [START_TIME_NS END_TIME_NS]
  // where
  //   TID is the thread id of the thread doing the operation.
  //   ALLOCATION_TYPE is one of malloc, calloc, memalign, realloc, free, thread_done
  //   POINTER is the hex value of the actual pointer
  //   START_TIME_NS is the start time of the operation in nanoseconds.
  //   END_TIME_NS is the end time of the operation in nanoseconds.
  // The START_TIME_NS and END_TIME_NS are optional parameters, either both
  // are present are neither are present.
  int op_prefix_pos = 0;
  char name[128];
  if (sscanf(line.c_str(), "%d: %127s %" SCNx64 " %n", &entry.tid, name, &entry.ptr,
             &op_prefix_pos) != 3) {
    error = "Failed to process line: " + line;
    return false;
  }

  entry.u.old_ptr = 0;
  entry.present_bytes = -1;
  entry.start_ns = 0;
  entry.end_ns = 0;

  // Handle each individual type of entry type.
  std::string type(name);
  if (type == "thread_done") {
    //   TID: thread_done 0x0 [END_TIME_NS]
    // Where END_TIME_NS is optional.
    entry.type = THREAD_DONE;
    entry.start_ns = 0;
    // Thread done has an optional time which is when the thread ended.
    // This is the only entry type that has a single timestamp.
    int n_match = sscanf(&line[op_prefix_pos], " %" SCNd64, &entry.end_ns);
    entry.start_ns = 0;
    if (n_match == EOF) {
      entry.end_ns = 0;
    } else if (n_match != 1) {
      error = "Failed to read thread_done end time: " + line;
      return false;
    }
    return true;
  }

  bool read_present_bytes = false;
  int args_offset = 0;
  const char* args = &line[op_prefix_pos];
  if (type == "malloc") {
    // Format:
    //   TID: malloc POINTER SIZE_OF_ALLOCATION [START_TIME_NS END_TIME_NS]
    if (sscanf(args, "%zu%n", &entry.size, &args_offset) != 1) {
      error = "Failed to read malloc data: " + line;
      return false;
    }
    entry.type = MALLOC;
  } else if (type == "free") {
    // Format:
    //   TID: free POINTER [START_TIME_NS END_TIME_NS] [PRESENT_BYTES]
    entry.type = FREE;
    read_present_bytes = true;
  } else if (type == "calloc") {
    // Format:
    //   TID: calloc POINTER ITEM_COUNT ITEM_SIZE [START_TIME_NS END_TIME_NS]
    if (sscanf(args, "%" SCNd64 " %zu%n", &entry.u.n_elements, &entry.size, &args_offset) != 2) {
      error = "Failed to read calloc data: " + line;
      return false;
    }
    entry.type = CALLOC;
  } else if (type == "realloc") {
    // Format:
    //   TID: realloc POINTER OLD_POINTER NEW_SIZE [START_TIME_NS END_TIME_NS] [PRESENT_BYTES]
    if (sscanf(args, "%" SCNx64 " %zu%n", &entry.u.old_ptr, &entry.size, &args_offset) != 2) {
      error = "Failed to read realloc data: " + line;
      return false;
    }
    read_present_bytes = true;
    entry.type = REALLOC;
  } else if (type == "memalign") {
    // Format:
    //   TID: memalign POINTER ALIGNMENT SIZE [START_TIME_NS END_TIME_NS]
    if (sscanf(args, "%" SCNd64 " %zu%n", &entry.u.align, &entry.size, &args_offset) != 2) {
      error = "Failed to read memalign data: " + line;
      return false;
    }
    entry.type = MEMALIGN;
  } else {
    error = "Unknown type " + type + ": " + line;
    return false;
  }

  // Get the optional timestamps if they exist.
  args = &args[args_offset];
  int n_match =
      sscanf(args, "%" SCNd64 " %" SCNd64 "%n", &entry.start_ns, &entry.end_ns, &args_offset);
  if (n_match == EOF) {
    return true;
  }

  if (n_match != 2) {
    error = "Failed to read timestamps: " + line;
    return false;
  }

  // Get the optional present bytes if it exists.
  if (read_present_bytes) {
    n_match = sscanf(&args[args_offset], "%" SCNd64, &entry.present_bytes);
    if (n_match != EOF && n_match != 1) {
      error = "Failed to read present bytes: " + line;
      return false;
    }
  }
  return true;
}

const char* TypeToName(const TypeEnum type) {
  switch (type) {
    case CALLOC:
      return "calloc";
    case FREE:
      return "free";
    case MALLOC:
      return "malloc";
    case MEMALIGN:
      return "memalign";
    case REALLOC:
      return "realloc";
    case THREAD_DONE:
      return "thread_done";
    default:
      return "unknown";
  }
}

static size_t FormatEntry(const Entry& entry, char* buffer, size_t buffer_len) {
  int len = snprintf(buffer, buffer_len, "%d: %s 0x%" PRIx64, entry.tid, TypeToName(entry.type),
                     entry.ptr);
  if (len < 0) {
    return 0;
  }
  size_t cur_len = len;
  bool output_present_bytes = false;
  switch (entry.type) {
    case FREE:
      len = 0;
      if (entry.present_bytes != -1) {
        output_present_bytes = true;
      }
      break;
    case CALLOC:
      len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %zu", entry.u.n_elements,
                     entry.size);
      break;
    case MALLOC:
      len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %zu", entry.size);
      break;
    case MEMALIGN:
      len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %zu", entry.u.align,
                     entry.size);
      break;
    case REALLOC:
      len = snprintf(&buffer[cur_len], buffer_len - cur_len, " 0x%" PRIx64 " %zu", entry.u.old_ptr,
                     entry.size);
      if (entry.present_bytes != -1) {
        output_present_bytes = true;
      }
      break;
    case THREAD_DONE:
      // Thread done only has a single optional timestamp, end_ns.
      if (entry.end_ns != 0) {
        len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRId64, entry.end_ns);
        if (len < 0) {
          return 0;
        }
        return cur_len + len;
      }
      return cur_len;
    default:
      return 0;
  }
  if (len < 0) {
    return 0;
  }

  cur_len += len;
  if (entry.start_ns == 0 && !output_present_bytes) {
    return cur_len;
  }

  len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRIu64 " %" PRIu64, entry.start_ns,
                 entry.end_ns);
  if (len < 0) {
    return 0;
  }

  cur_len += len;
  if (!output_present_bytes) {
    return cur_len;
  }

  len = snprintf(&buffer[cur_len], buffer_len - cur_len, " %" PRId64, entry.present_bytes);
  if (len < 0) {
    return 0;
  }
  return cur_len + len;
}

std::string CreateStringFromEntry(const Entry& entry) {
  std::string line(kBufferLen, '\0');

  size_t size = FormatEntry(entry, line.data(), line.size());
  if (size == 0) {
    return "";
  }
  line.resize(size);
  return line;
}

bool WriteEntryToFd(int fd, const Entry& entry) {
  char buffer[kBufferLen];
  size_t size = FormatEntry(entry, buffer, sizeof(buffer));
  if (size == 0 || size == sizeof(buffer)) {
    return false;
  }
  buffer[size++] = '\n';
  buffer[size] = '\0';
  ssize_t bytes = TEMP_FAILURE_RETRY(write(fd, buffer, size));
  if (bytes < 0 || static_cast<size_t>(bytes) != size) {
    return false;
  }
  return true;
}

}  // namespace memory_trace
