// Copyright (c) 2014-2021 LG Electronics, Inc.
//
// 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.
//
// SPDX-License-Identifier: Apache-2.0

#include "web_process_manager.h"

#include <signal.h>

#include <climits>
#include <cstdio>
#include <fstream>
#include <string>

#include <glib.h>
#include <json/json.h>

#include "application_description.h"
#include "log_manager.h"
#include "utils.h"
#include "web_app_base.h"
#include "web_app_manager.h"
#include "web_app_manager_config.h"
#include "web_app_manager_utils.h"
#include "web_page_base.h"

WebProcessManager::WebProcessManager() : maximum_number_of_processes_(1) {
  ReadWebProcessPolicy();
}

std::list<const WebAppBase*> WebProcessManager::RunningApps() {
  return WebAppManager::Instance()->RunningApps();
}

std::list<const WebAppBase*> WebProcessManager::RunningApps(uint32_t pid) {
  return WebAppManager::Instance()->RunningApps(pid);
}

WebAppBase* WebProcessManager::FindAppById(const std::string& app_id) {
  return WebAppManager::Instance()->FindAppById(app_id);
}

WebAppBase* WebProcessManager::FindAppByInstanceId(
    const std::string& instance_id) {
  return WebAppManager::Instance()->FindAppByInstanceId(instance_id);
}

bool WebProcessManager::WebProcessInfoMapReady() {
  uint32_t count = 0;
  for (const auto& it : web_process_info_map_) {
    if (it.second.proxy_id_ != 0)
      count++;
  }

  return count == maximum_number_of_processes_;
}

uint32_t WebProcessManager::GetWebProcessProxyID(
    const ApplicationDescription* desc) const {
  if (!desc)
    return 0;

  std::string key = GetProcessKey(desc);

  auto it = web_process_info_map_.find(key);
  if (it == web_process_info_map_.end() || !it->second.proxy_id_) {
    return GetInitialWebViewProxyID();
  }

  return it->second.proxy_id_;
}

uint32_t WebProcessManager::GetWebProcessProxyID(uint32_t pid) const {
  auto res = find_if(web_process_info_map_.begin(), web_process_info_map_.end(),
                     [pid](const auto& item) {
                       return (item.second.web_process_pid_ == pid);
                     });

  if (res != web_process_info_map_.end()) {
    return res->second.proxy_id_;
  }
  return 0;
}

std::string WebProcessManager::GetWebProcessMemSize(uint32_t pid) const {
  std::string path = "/proc/" + std::to_string(pid) + "/status";
  std::ifstream in(path);

  if (!in.is_open())
    return {};

  std::string line;
  while (std::getline(in, line)) {
    if (line.find("VmRSS:", 0, 6) != std::string::npos) {
      return util::TrimString(std::string(line, 6)).c_str();
    }
  }
  return {};
}

void WebProcessManager::ReadWebProcessPolicy() {
  std::string config_path =
      WebAppManager::Instance()->Config()->GetWebProcessConfigPath();
  Json::Value web_process_environment =
      util::StringToJson(util::ReadFile(config_path));

  if (web_process_environment.isNull()) {
    LOG_ERROR(MSGID_WEBPROCESSENV_READ_FAIL, 1,
              PMLOGKS("PATH", config_path.c_str()), "JSON parsing failed");
    return;
  }

  auto create_process_for_each_app =
      web_process_environment["createProcessForEachApp"];
  if (create_process_for_each_app.isBool() &&
      create_process_for_each_app.asBool())
    maximum_number_of_processes_ = UINT_MAX;
  else {
    auto web_process_array = web_process_environment["webProcessList"];
    if (web_process_array.isArray()) {
      for (const auto& value : web_process_array) {
        if (!value.isObject())
          continue;
        auto id = value["id"];
        if (id.isString()) {
          web_process_group_app_id_list_.push_back(id.asString());
          SetWebProcessCacheProperty(value, id.asString());
        }
        auto trustLevel = value["trustLevel"];
        if (trustLevel.isString()) {
          web_process_group_trust_level_list_.push_back(trustLevel.asString());
          SetWebProcessCacheProperty(value, trustLevel.asString());
        }
      }
    }
    maximum_number_of_processes_ = (web_process_group_trust_level_list_.size() +
                                    web_process_group_app_id_list_.size());
  }

  LOG_INFO(
      MSGID_SET_WEBPROCESS_ENVIRONMENT, 3,
      PMLOGKFV("MAXIMUM_WEBPROCESS_NUMBER", "%u", maximum_number_of_processes_),
      PMLOGKFV("GROUP_TRUSTLEVELS_COUNT", "%zu",
               web_process_group_trust_level_list_.size()),
      PMLOGKFV("GROUP_APP_IDS_COUNT", "%zu",
               web_process_group_app_id_list_.size()),
      "");
}

void WebProcessManager::SetWebProcessCacheProperty(const Json::Value& object,
                                                   const std::string& key) {
  WebProcessInfo info = WebProcessInfo(0, 0);
  auto memory_cache = object["memoryCache"];
  if (memory_cache.isString()) {
    int mem_cache_size = util::StrToIntWithDefault(memory_cache.asString(), 0);
    info.memory_cache_size_ = mem_cache_size;
  }

  auto code_cache = object["codeCache"];
  if (code_cache.isString()) {
    int code_cache_int = util::StrToIntWithDefault(code_cache.asString(), 0);
    info.code_cache_size_ = code_cache_int;
  }

  web_process_info_map_.emplace(key, info);
}

std::string WebProcessManager::GetProcessKey(
    const ApplicationDescription* desc) const {
  if (!desc)
    return std::string();

  std::string key;
  std::vector<std::string> id_list, trust_level_list;
  if (maximum_number_of_processes_ == 1)
    key = "system";
  else if (maximum_number_of_processes_ == UINT_MAX) {
    if (desc->TrustLevel() == "default" || desc->TrustLevel() == "trusted")
      key = "system";
    else
      key = desc->Id();
  } else {
    for (size_t i = 0; i < web_process_group_app_id_list_.size(); i++) {
      std::string app_id = web_process_group_app_id_list_.at(i);
      if (app_id.find('*') != std::string::npos) {
        util::ReplaceSubstr(app_id, "*");
        auto l = util::SplitString(app_id, ',');
        id_list.insert(id_list.end(), l.begin(), l.end());
        for (const auto& id : id_list)
          if (!desc->Id().compare(0, id.size(), id))
            key = web_process_group_app_id_list_.at(i);
      } else {
        auto l = util::SplitString(app_id, ',');
        id_list.insert(id_list.end(), l.begin(), l.end());
        for (const auto& id : id_list)
          if (id == desc->Id())
            return web_process_group_app_id_list_.at(i);
      }
    }
    if (!key.empty())
      return key;

    for (size_t i = 0; i < web_process_group_trust_level_list_.size(); i++) {
      std::string trust_level = web_process_group_trust_level_list_.at(i);
      auto l = util::SplitString(trust_level, ',');
      trust_level_list.insert(trust_level_list.end(), l.begin(), l.end());
      for (const auto& trust : trust_level_list) {
        if (trust == desc->TrustLevel()) {
          return web_process_group_trust_level_list_.at(i);
        }
      }
    }
    key = "system";
  }
  return key;
}

void WebProcessManager::KillWebProcess(uint32_t pid) {
  for (auto& it : web_process_info_map_) {
    if (it.second.web_process_pid_ == pid) {
      it.second.request_kill_ = false;
      break;
    }
  }

  LOG_INFO(MSGID_KILL_WEBPROCESS, 1, PMLOGKFV("PID", "%u", pid), "");
  int ret = kill(pid, SIGKILL);
  if (ret == -1)
    LOG_ERROR(MSGID_KILL_WEBPROCESS_FAILED, 1,
              PMLOGKS("ERROR", strerror(errno)), "SystemCall failed");
}

void WebProcessManager::RequestKillWebProcess(uint32_t pid) {
  for (auto& it : web_process_info_map_) {
    if (it.second.web_process_pid_ == pid) {
      LOG_INFO(MSGID_KILL_WEBPROCESS_DELAYED, 1, PMLOGKFV("PID", "%u", pid),
               "");
      it.second.request_kill_ = true;
      return;
    }
  }
}
