/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 */

#include "./shtype-from-gst-caps.hpp"

#include <regex>
#include <sstream>
#include <string>

#include "../utils/any.hpp"

namespace sh4lt::shtype {

auto escape(const std::string& str) -> std::string {
  static const std::regex coma_rgx("\\\\,");
  static const std::regex equal_rgx("\\\\=");
  return std::regex_replace(std::regex_replace(str, coma_rgx, "__COMA__"), equal_rgx, "__EQUAL__");
}

auto unescape(const std::string& str) -> std::string {
  static const std::regex coma_rgx("__COMA__");
  static const std::regex equal_rgx("__EQUAL__");
  static const std::regex quot_rgx("\\\"");
  static const std::regex lparent_rgx(R"_(\\\()_");
  static const std::regex rparent_rgx(R"_(\\\))_");
  return std::regex_replace(
      std::regex_replace(
          std::regex_replace(
              std::regex_replace(std::regex_replace(str, coma_rgx, "\\,"), quot_rgx, ""),
              equal_rgx,
              "\\="),
          lparent_rgx,
          "\\("),
      rparent_rgx,
      "\\)");
}

auto quote(const std::string& str) -> std::string {
  auto res = str;
  if (res.find(',') != std::string::npos || res.find(' ') != std::string::npos ||
      res.find('=') != std::string::npos) {
    res = std::string("\"").append(res).append("\"");
  }
  return res;
}

auto shtype_from_gst_caps(const std::string& gst_caps, const std::string& label) -> ShType {
  return shtype_from_gst_caps(gst_caps, label, "Default", "");
}

auto shtype_from_gst_caps(const std::string& gst_caps,
                          const std::string& label,
                          const std::string& group) -> ShType {
  return shtype_from_gst_caps(gst_caps, label, group, "");
}

auto shtype_from_gst_caps(const std::string& gst_caps,
                          const std::string& label,
                          const std::string& group_label,
                          const std::string& description) -> ShType {
  auto type = escape(gst_caps);
  ShType res;
  std::string parsing_errors;
  // split by ','
  static const std::regex rgx(" *, *");
  std::sregex_token_iterator iter(type.begin(), type.end(), rgx, -1);
  // searching for the type name
  std::string media;
  while (media.empty() && iter != std::sregex_token_iterator()) {
    media = static_cast<std::string>(*iter);
    ++iter;
  }
  if (media.empty()) {
    res.add_log("media specification is missing");
  }
  res.set_media(media);
  res.set_label(label);
  res.set_group(group_label);
  res.set_description(description);
  for (; iter != std::sregex_token_iterator(); ++iter) {
    auto prop = static_cast<std::string>(*iter);
    if (prop.empty()) continue;
    // removing space around '='
    std::regex space_rgx("\\s*=\\s*");
    prop = std::regex_replace(prop, space_rgx, "=");
    // ensure we have a 'key=value' description
    static const std::regex keyval_rgx("[\\w-]+=[^=]+");
    if (!std::regex_match(prop, keyval_rgx)) {
      res.add_log(std::string("wrong parameter syntax (expecting "
                              "param_name=param_value, but got ") +
                  static_cast<std::string>(*iter) + '\n');
      continue;
    }
    // key and value
    auto equal_pos = prop.find('=');
    auto key = std::string(prop, 0, equal_pos);
    std::string value = std::string(prop, equal_pos + 1, std::string::npos);
    // check if value contains a literal type description
    static const std::regex literal_type_regex(R"_(\([\w]+\))_");
    std::smatch sm;
    auto unescaped = unescape(value);
    // check if unescaped value is an integer and fill the any value with the
    // appropriate type
    Any any_value;
    bool is_negative = false;
    if (*unescaped.begin() == '-') is_negative = true;
    if (!unescaped.empty() && unescaped != "-" &&
        std::find_if(unescaped.begin() + (is_negative ? 1 : 0), unescaped.end(), [](char c) {
          return !std::isdigit(c);
        }) == unescaped.end()) {
      any_value = std::stoi(unescaped);
    } else {
      any_value = Any(std::string(unescaped));
    }
    // search if the value is prefixed with a type name
    if (regex_search(value, sm, literal_type_regex)) {
      if (sm.str() == "(int)") {
        any_value = std::stoi(std::string(value, sm.str().size(), std::string::npos));
      } else if (sm.str() == "(float)") {
        any_value = std::stod(std::string(value, sm.str().size(), std::string::npos));
      } else {
        any_value = unescape(std::string(value, sm.str().size(), std::string::npos));
        // saving non literal types only
        res.set_custom_type(key, std::string(sm.str(), 1, sm.str().size() - 2));
      }
    }
    // saving resulting key and value
    res.set_prop(key, any_value);
  }
  return res;
}

auto shtype_to_gst_caps(const ShType& shtype) -> std::string {
  auto res = shtype.media();
  auto str_types = shtype.get_custom_types();
  for (auto& it : shtype.get_properties()) {
    auto value = it.second;
    auto found = str_types.find(it.first);
    if (found != str_types.end()) {
      res += ", " + it.first + "=(" + found->second + ")" + quote(unescape(Any::to_string(value)));
    } else {
      res += ", " + it.first + "=" + quote(unescape(Any::to_string(value)));
    }
  }
  return res;
}

}  // namespace sh4lt::shtype
