#include "compiler/signature.h"

#include <algorithm>
#include <iomanip>
#include <sstream>

#include "common/sha256.h"

namespace cf {

namespace {

std::string json_escape(const std::string& value) {
  std::ostringstream out;
  for (unsigned char c : value) {
    switch (c) {
      case '"': out << "\\\""; break;
      case '\\': out << "\\\\"; break;
      case '\b': out << "\\b"; break;
      case '\f': out << "\\f"; break;
      case '\n': out << "\\n"; break;
      case '\r': out << "\\r"; break;
      case '\t': out << "\\t"; break;
      default:
        if (c < 0x20) {
          out << "\\u" << std::uppercase << std::hex << std::setw(4) << std::setfill('0')
              << static_cast<int>(c) << std::nouppercase << std::dec;
        } else {
          out << static_cast<char>(c);
        }
        break;
    }
  }
  return out.str();
}

void append_json_array(std::ostringstream& out, const std::vector<std::string>& values) {
  out << '[';
  for (size_t i = 0; i < values.size(); ++i) {
    if (i > 0) out << ',';
    out << '"' << json_escape(values[i]) << '"';
  }
  out << ']';
}

}  // namespace

SignatureSpec build_signature_spec(const StepDef& step) {
  SignatureSpec spec;
  spec.step_iri = step.iri;
  spec.parameter_keys.reserve(step.parameters.size());
  for (const auto& param : step.parameters) {
    spec.parameter_keys.push_back(param.name);
  }
  spec.input_keys.reserve(step.inputs.size());
  for (const auto& input : step.inputs) {
    spec.input_keys.push_back(input.key);
  }
  spec.output_keys.reserve(step.outputs.size());
  for (const auto& output : step.outputs) {
    spec.output_keys.push_back(output.key);
  }

  std::sort(spec.parameter_keys.begin(), spec.parameter_keys.end());
  std::sort(spec.input_keys.begin(), spec.input_keys.end());
  std::sort(spec.output_keys.begin(), spec.output_keys.end());
  return spec;
}

std::string signature_spec_json(const SignatureSpec& spec) {
  std::ostringstream out;
  out << '{';
  out << "\"step\":\"" << json_escape(spec.step_iri) << "\",";
  out << "\"parameters\":";
  append_json_array(out, spec.parameter_keys);
  out << ",\"inputs\":";
  append_json_array(out, spec.input_keys);
  out << ",\"outputs\":";
  append_json_array(out, spec.output_keys);
  out << '}';
  return out.str();
}

std::string signature_hash(const SignatureSpec& spec) {
  std::string payload = signature_spec_json(spec);
  return sha256_hex(payload);
}

}  // namespace cf
