/*
 * Copyright 2016 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 "VtsCompilerUtils.h"

#include <limits.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <cstdint>
#include <fstream>
#include <iostream>
#include <sstream>

#include <google/protobuf/text_format.h>

#include "utils/InterfaceSpecUtil.h"
#include "utils/StringUtil.h"

#include "test/vts/proto/ComponentSpecificationMessage.pb.h"

using namespace std;

namespace android {
namespace vts {

string ComponentClassToString(int component_class) {
  switch (component_class) {
    case UNKNOWN_CLASS:
      return "unknown_class";
    case HAL_CONVENTIONAL:
      return "hal_conventional";
    case HAL_CONVENTIONAL_SUBMODULE:
      return "hal_conventional_submodule";
    case HAL_HIDL:
      return "hal_hidl";
    case HAL_HIDL_WRAPPED_CONVENTIONAL:
      return "hal_hidl_wrapped_conventional";
    case HAL_LEGACY:
      return "hal_legacy";
    case LIB_SHARED:
      return "lib_shared";
  }
  cerr << "error: invalid component_class " << component_class << endl;
  exit(-1);
}

string ComponentTypeToString(int component_type) {
  switch (component_type) {
    case UNKNOWN_TYPE:
      return "unknown_type";
    case AUDIO:
      return "audio";
    case CAMERA:
      return "camera";
    case GPS:
      return "gps";
    case LIGHT:
      return "light";
    case WIFI:
      return "wifi";
    case MOBILE:
      return "mobile";
    case BLUETOOTH:
      return "bluetooth";
    case TV_INPUT:
      return "tv_input";
    case NFC:
      return "nfc";
    case VEHICLE:
      return "vehicle";
    case VIBRATOR:
      return "vibrator";
    case THERMAL:
      return "thermal";
    case CONTEXTHUB:
      return "contexthub";
    case SENSORS:
      return "sensors";
    case VR:
      return "vr";
    case GRAPHICS_ALLOCATOR:
      return "graphics_allocator";
    case GRAPHICS_MAPPER:
      return "graphics_mapper";
    case GRAPHICS_COMPOSER:
      return "graphics_composer";
    case BIONIC_LIBM:
      return "bionic_libm";
    case TV_CEC:
      return "tv_cec";
    case RADIO:
      return "radio";
    case MEDIA_OMX:
      return "media_omx";
    case BIONIC_LIBC:
      return "bionic_libc";
    case VNDK_LIBCUTILS:
      return "vndk_libcutils";
  }
  cerr << "error: invalid component_type " << component_type << endl;
  exit(-1);
}

string GetCppVariableType(const std::string scalar_type_string) {
  if (scalar_type_string == "void" ||
      scalar_type_string == "int32_t" || scalar_type_string == "uint32_t" ||
      scalar_type_string == "int8_t" || scalar_type_string == "uint8_t" ||
      scalar_type_string == "int64_t" || scalar_type_string == "uint64_t" ||
      scalar_type_string == "int16_t" || scalar_type_string == "uint16_t") {
    return scalar_type_string;
  } else if (scalar_type_string == "bool_t") {
    return "bool";
  } else if (scalar_type_string == "float_t") {
    return "float";
  } else if (scalar_type_string == "double_t") {
    return "double";
  } else if (scalar_type_string == "ufloat") {
    return "unsigned float";
  } else if (scalar_type_string == "udouble") {
    return "unsigned double";
  } else if (scalar_type_string == "string") {
    return "std::string";
  } else if (scalar_type_string == "pointer") {
    return "void*";
  } else if (scalar_type_string == "char_pointer") {
    return "char*";
  } else if (scalar_type_string == "uchar_pointer") {
    return "unsigned char*";
  } else if (scalar_type_string == "void_pointer") {
    return "void*";
  } else if (scalar_type_string == "function_pointer") {
    return "void*";
  }

  cerr << __func__ << ":" << __LINE__ << " "
       << "error: unknown scalar_type " << scalar_type_string << endl;
  exit(-1);
}

string GetCppVariableType(const VariableSpecificationMessage& arg,
                          bool generate_const) {
  string result;
  switch (arg.type()) {
    case TYPE_VOID:
    {
      return "void";
    }
    case TYPE_PREDEFINED:
    {
      result = arg.predefined_type();
      break;
    }
    case TYPE_SCALAR:
    {
      result = GetCppVariableType(arg.scalar_type());
      break;
    }
    case TYPE_STRING:
    {
      result = "::android::hardware::hidl_string";
      break;
    }
    case TYPE_ENUM:
    {
      if (!arg.has_enum_value() && arg.has_predefined_type()) {
        result = arg.predefined_type();
      } else if (arg.has_enum_value() && arg.has_name()) {
        result = arg.name();  // nested enum type.
      } else {
        cerr << __func__ << ":" << __LINE__
             << " ERROR no predefined_type set for enum variable" << endl;
        exit(-1);
      }
      break;
    }
    case TYPE_VECTOR:
    {
      string element_type = GetCppVariableType(arg.vector_value(0));
      result = "::android::hardware::hidl_vec<" + element_type + ">";
      break;
    }
    case TYPE_ARRAY:
    {
      VariableSpecificationMessage cur_val = arg;
      vector<int32_t> array_sizes;
      while (cur_val.type() == TYPE_ARRAY) {
        array_sizes.push_back(cur_val.vector_size());
        VariableSpecificationMessage temp = cur_val.vector_value(0);
        cur_val = temp;
      }
      string element_type = GetCppVariableType(cur_val);
      result = "::android::hardware::hidl_array<" + element_type + ", ";
      for (size_t i = 0; i < array_sizes.size(); i++) {
        result += to_string(array_sizes[i]);
        if (i != array_sizes.size() - 1) result += ", ";
      }
      result += ">";
      break;
    }
    case TYPE_STRUCT:
    {
      if (arg.struct_value_size() == 0 && arg.has_predefined_type()) {
        result = arg.predefined_type();
      } else if (arg.has_struct_type()) {
        result = arg.struct_type();
      } else if (arg.sub_struct_size() > 0 || arg.struct_value_size() > 0) {
        result = arg.name();
      } else {
        cerr << __func__ << ":" << __LINE__ << " ERROR"
             << " no predefined_type, struct_type, nor sub_struct set"
             << " for struct variable"
             << " (arg name " << arg.name() << ")" << endl;
        exit(-1);
      }
      break;
    }
    case TYPE_UNION:
    {
      if (arg.union_value_size() == 0 && arg.has_predefined_type()) {
        result = arg.predefined_type();
      } else if (arg.has_union_type()) {
        result = arg.union_type();
      } else {
        cerr << __func__ << ":" << __LINE__
             << " ERROR no predefined_type or union_type set for union"
             << " variable" << endl;
        exit(-1);
      }
      break;
    }
    case TYPE_HIDL_CALLBACK:
    {
      if (arg.has_predefined_type()) {
        result = "sp<" + arg.predefined_type() + ">";
      } else {
        cerr << __func__ << ":" << __LINE__
             << " ERROR no predefined_type set for hidl callback variable"
             << endl;
        exit(-1);
      }
      break;
    }
    case TYPE_HANDLE:
    {
      result = "::android::hardware::hidl_handle";
      break;
    }
    case TYPE_HIDL_INTERFACE:
    {
      if (arg.has_predefined_type()) {
        result = "sp<" + arg.predefined_type() + ">";
      } else {
        cerr << __func__ << ":" << __LINE__
             << " ERROR no predefined_type set for hidl interface variable"
             << endl;
        exit(-1);
      }
      break;
    }
    case TYPE_MASK:
    {
      result = GetCppVariableType(arg.scalar_type());
      break;
    }
    case TYPE_HIDL_MEMORY:
    {
      result = "::android::hardware::hidl_memory";
      break;
    }
    case TYPE_POINTER:
    {
      result = "void*";
      if (generate_const) {
        result = "const " + result;
      }
      return result;
    }
    case TYPE_FMQ_SYNC:
    {
      string element_type = GetCppVariableType(arg.fmq_value(0));
      result = "::android::hardware::MQDescriptorSync<" + element_type + ">";
      break;
    }
    case TYPE_FMQ_UNSYNC:
    {
      string element_type = GetCppVariableType(arg.fmq_value(0));
      result = "::android::hardware::MQDescriptorUnsync<" + element_type + ">";
      break;
    }
    case TYPE_REF:
    {
      VariableSpecificationMessage cur_val = arg;
      int ref_depth = 0;
      while (cur_val.type() == TYPE_REF) {
        ref_depth++;
        VariableSpecificationMessage temp = cur_val.ref_value();
        cur_val = temp;
      }
      string element_type = GetCppVariableType(cur_val);
      result = element_type;
      for (int i = 0; i < ref_depth; i++) {
        result += " const*";
      }
      return result;
      break;
    }
    default:
    {
      cerr << __func__ << ":" << __LINE__ << " " << ": type " << arg.type()
           << " not supported" << endl;
      exit(-1);
    }
  }
  if (generate_const) {
    return "const " + result + "&";
  }
  return result;
}

string GetConversionToProtobufFunctionName(VariableSpecificationMessage arg) {
  if (arg.type() == TYPE_PREDEFINED) {
    if (arg.predefined_type() == "camera_info_t*") {
      return "ConvertCameraInfoToProtobuf";
    } else if (arg.predefined_type() == "hw_device_t**") {
      return "";
    } else {
      cerr << __FILE__ << ":" << __LINE__ << " "
           << "error: unknown instance type " << arg.predefined_type() << endl;
    }
  }
  cerr << __FUNCTION__ << ": non-supported type " << arg.type() << endl;
  exit(-1);
}

string GetCppInstanceType(
    const VariableSpecificationMessage& arg,
    const string& msg,
    const ComponentSpecificationMessage* message) {
  switch(arg.type()) {
    case TYPE_PREDEFINED: {
      if (arg.predefined_type() == "struct light_state_t*") {
        if (msg.length() == 0) {
          return "GenerateLightState()";
        } else {
          return "GenerateLightStateUsingMessage(" + msg + ")";
        }
      } else if (arg.predefined_type() == "GpsCallbacks*") {
        return "GenerateGpsCallbacks()";
      } else if (arg.predefined_type() == "GpsUtcTime") {
        return "GenerateGpsUtcTime()";
      } else if (arg.predefined_type() == "vts_gps_latitude") {
        return "GenerateLatitude()";
      } else if (arg.predefined_type() == "vts_gps_longitude") {
        return "GenerateLongitude()";
      } else if (arg.predefined_type() == "vts_gps_accuracy") {
        return "GenerateGpsAccuracy()";
      } else if (arg.predefined_type() == "vts_gps_flags_uint16") {
        return "GenerateGpsFlagsUint16()";
      } else if (arg.predefined_type() == "GpsPositionMode") {
        return "GenerateGpsPositionMode()";
      } else if (arg.predefined_type() == "GpsPositionRecurrence") {
        return "GenerateGpsPositionRecurrence()";
      } else if (arg.predefined_type() == "hw_module_t*") {
        return "(hw_module_t*) malloc(sizeof(hw_module_t))";
      } else if (arg.predefined_type() == "hw_module_t**") {
        return "(hw_module_t**) malloc(sizeof(hw_module_t*))";
      } else if (arg.predefined_type() == "hw_device_t**") {
        return "(hw_device_t**) malloc(sizeof(hw_device_t*))";
      } else if (arg.predefined_type() == "camera_info_t*") {
        if (msg.length() == 0) {
          return "GenerateCameraInfo()";
        } else {
          return "GenerateCameraInfoUsingMessage(" + msg + ")";
        }
      } else if (arg.predefined_type() == "camera_module_callbacks_t*") {
        return "GenerateCameraModuleCallbacks()";
      } else if (arg.predefined_type() == "camera_notify_callback") {
        return "GenerateCameraNotifyCallback()";
      } else if (arg.predefined_type() == "camera_data_callback") {
        return "GenerateCameraDataCallback()";
      } else if (arg.predefined_type() == "camera_data_timestamp_callback") {
        return "GenerateCameraDataTimestampCallback()";
      } else if (arg.predefined_type() == "camera_request_memory") {
        return "GenerateCameraRequestMemory()";
      } else if (arg.predefined_type() == "wifi_handle*") {
        return "(wifi_handle*) malloc(sizeof(wifi_handle))";
      } else if (arg.predefined_type() == "struct camera_device*") {
        return "(struct camera_device*) malloc(sizeof(struct camera_device))";
      } else if (arg.predefined_type() == "struct preview_stream_ops*") {
        return "(preview_stream_ops*) malloc(sizeof(preview_stream_ops))";
      } else if (endsWith(arg.predefined_type(), "*")) {
        // known use cases: bt_callbacks_t
        return "(" + arg.predefined_type() + ") malloc(sizeof("
            + arg.predefined_type().substr(0, arg.predefined_type().size() - 1)
            + "))";
      } else {
        cerr << __func__ << ":" << __LINE__ << " "
             << "error: unknown instance type " << arg.predefined_type() << endl;
      }
      break;
    }
    case TYPE_SCALAR: {
      if (arg.scalar_type() == "bool_t") {
        return "RandomBool()";
      } else if (arg.scalar_type() == "uint32_t") {
        return "RandomUint32()";
      } else if (arg.scalar_type() == "int32_t") {
        return "RandomInt32()";
      } else if (arg.scalar_type() == "uint64_t") {
        return "RandomUint64()";
      } else if (arg.scalar_type() == "int64_t") {
        return "RandomInt64()";
      } else if (arg.scalar_type() == "uint16_t") {
        return "RandomUint16()";
      } else if (arg.scalar_type() == "int16_t") {
        return "RandomInt16()";
      } else if (arg.scalar_type() == "uint8_t") {
        return "RandomUint8()";
      } else if (arg.scalar_type() == "int8_t") {
        return "RandomInt8()";
      } else if (arg.scalar_type() == "float_t") {
        return "RandomFloat()";
      } else if (arg.scalar_type() == "double_t") {
        return "RandomDouble()";
      } else if (arg.scalar_type() == "char_pointer") {
        return "RandomCharPointer()";
      } else if (arg.scalar_type() == "uchar_pointer") {
        return "(unsigned char*) RandomCharPointer()";
      } else if (arg.scalar_type() == "pointer" ||
                 arg.scalar_type() == "void_pointer") {
        return "RandomVoidPointer()";
      }
      cerr << __FILE__ << ":" << __LINE__ << " "
           << "error: unsupported scalar data type " << arg.scalar_type() << endl;
      exit(-1);
    }
    case TYPE_ENUM:
    case TYPE_MASK: {
      if (!arg.has_enum_value() && arg.has_predefined_type()) {
        if (!message || message->component_class() != HAL_HIDL) {
          return "(" + arg.predefined_type() +  ") RandomUint32()";
        } else {
          std::string predefined_type_name = arg.predefined_type();
          ReplaceSubString(predefined_type_name, "::", "__");
          return "Random" + predefined_type_name + "()";
          // TODO: generate a function which can dynamically choose the value.
          /* for (const auto& attribute : message->attribute()) {
            if (attribute.type() == TYPE_ENUM &&
                attribute.name() == arg.predefined_type()) {
              // TODO: pick at runtime
              return message->component_name() + "::"
                  + arg.predefined_type() + "::"
                  + attribute.enum_value().enumerator(0);
            }
          } */
        }
      } else {
        cerr << __func__
             << " ENUM either has enum value or doesn't have predefined type"
             << endl;
        exit(-1);
      }
      break;
    }
    case TYPE_STRING: {
      return "android::hardware::hidl_string(RandomCharPointer())";
    }
    case TYPE_STRUCT: {
      if (arg.struct_value_size() == 0 && arg.has_predefined_type()) {
        return message->component_name() + "::" + arg.predefined_type() +  "()";
      }
      break;
    }
    case TYPE_VECTOR: {  // only for HAL_HIDL
      // TODO: generate code that initializes a local hidl_vec.
      return "";
    }
    case TYPE_HIDL_CALLBACK: {
      return arg.predefined_type() + "()";
    }
    default:
      break;
  }
  cerr << __func__ << ": error: unsupported type " << arg.type() << endl;
  exit(-1);
}

int vts_fs_mkdirs(char* file_path, mode_t mode) {
  char* p;

  for (p = strchr(file_path + 1, '/'); p; p = strchr(p + 1, '/')) {
    *p = '\0';
    if (mkdir(file_path, mode) == -1) {
      if (errno != EEXIST) {
        *p = '/';
        return -1;
      }
    }
    *p = '/';
  }
  return 0;
}

string ClearStringWithNameSpaceAccess(const string& str) {
  string result = str;
  ReplaceSubString(result, "::", "__");
  return result;
}

// Returns a string which joins the given dir_path and file_name.
string PathJoin(const char* dir_path, const char* file_name) {
  string result;
  if (dir_path) {
    result = dir_path;
    if (!file_name) return result;
  } else if (!file_name) return result;

  if (file_name[0] != '.') {
    if (result.c_str()[result.length()-1] != '/') {
      result += "/";
    }
  }
  result += file_name;
  return result;
}

// Returns a string which remove given base_path from file_path if included.
string RemoveBaseDir(const string& file_path, const string& base_path) {
  if (strncmp(file_path.c_str(), base_path.c_str(), base_path.length())) {
    return file_path;
  }
  string result;
  result = &file_path.c_str()[base_path.length()];
  if (result.c_str()[0] == '/') {
    result = &result.c_str()[1];
  }
  return result;
}

string GetPackageName(const ComponentSpecificationMessage& message) {
  if (!message.package().empty()) {
    return message.package();
  }
  return "";
}

string GetPackagePath(const ComponentSpecificationMessage& message) {
  string package_path = GetPackageName(message);
  ReplaceSubString(package_path, ".", "/");
  return package_path;
}

string GetPackageNamespaceToken(const ComponentSpecificationMessage& message) {
  string package_token = GetPackageName(message);
  ReplaceSubString(package_token, ".", "::");
  return package_token;
}

string GetVersion(const ComponentSpecificationMessage& message,
                  bool for_macro) {
  return GetVersionString(message.component_type_version(), for_macro);
}

int GetMajorVersion(const ComponentSpecificationMessage& message) {
  string version = GetVersion(message);
  return stoi(version.substr(0, version.find('.')));
}

int GetMinorVersion(const ComponentSpecificationMessage& message) {
  string version = GetVersion(message);
  return stoi(version.substr(version.find('.') + 1));
}

string GetComponentBaseName(const ComponentSpecificationMessage& message) {
  if (!message.component_name().empty()) {
    return (message.component_name() == "types"
                ? "types"
                : message.component_name().substr(1));
  } else
    return GetComponentName(message);
}

string GetComponentName(const ComponentSpecificationMessage& message) {
  if (!message.component_name().empty()) {
    return message.component_name();
  }

  string component_name = message.original_data_structure_name();
  while (!component_name.empty()
      && (std::isspace(component_name.back()) || component_name.back() == '*')) {
    component_name.pop_back();
  }
  const auto pos = component_name.find_last_of(" ");
  if (pos != std::string::npos) {
    component_name = component_name.substr(pos + 1);
  }
  return component_name;
}

FQName GetFQName(const ComponentSpecificationMessage& message) {
  return FQName(message.package(),
                GetVersionString(message.component_type_version()),
                GetComponentName(message));
}

string GetVarString(const string& var_name) {
  string var_str = var_name;
  for (size_t i = 0; i < var_name.length(); i++) {
    if (!isdigit(var_str[i]) && !isalpha(var_str[i])) {
      var_str[i] = '_';
    }
  }
  return var_str;
}

}  // namespace vts
}  // namespace android