// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "impeller/toolkit/interop/fragment_program.h"

#include "impeller/base/validation.h"

namespace impeller::interop {

FragmentProgram::FragmentProgram(const std::shared_ptr<fml::Mapping>& data) {
  if (data == nullptr || data->GetSize() == 0) {
    VALIDATION_LOG << "No data provided to create fragment program.";
    return;
  }

  auto stages = RuntimeStage::DecodeRuntimeStages(data);
  if (!stages.ok()) {
    VALIDATION_LOG << "Failed to decode runtime stages: " << stages.status();
    return;
  }

  for (const auto& stage : stages.value()) {
    if (auto data = stage.second) {
      stages_[stage.first] = std::move(data);
    }
  }

  if (stages_.empty()) {
    VALIDATION_LOG << "No valid runtime stages present in fragment program.";
    return;
  }

  is_valid_ = true;
}

FragmentProgram::~FragmentProgram() = default;

bool FragmentProgram::IsValid() const {
  return is_valid_;
}

static std::string AvailableStagesAsString(
    const std::set<RuntimeStageBackend>& stages) {
  std::stringstream stream;
  size_t count = 0;
  for (const auto& stage : stages) {
    stream << RuntimeStageBackendToString(stage);
    count++;
    if (count != stages.size()) {
      stream << ", ";
    }
  }
  return stream.str();
}

std::shared_ptr<RuntimeStage> FragmentProgram::FindRuntimeStage(
    RuntimeStageBackend backend) const {
  if (backend == RuntimeStageBackend::kOpenGLES3) {
    return FindRuntimeStage(RuntimeStageBackend::kOpenGLES);
  }
  auto found = stages_.find(backend);
  if (found == stages_.end()) {
    VALIDATION_LOG << "Could not find runtime shader for backend: "
                   << RuntimeStageBackendToString(backend)
                   << ". Shaders were packaged for "
                   << AvailableStagesAsString(GetAvailableStages())
                   << ". Check your shader compiler options.";
    return nullptr;
  }
  return found->second;
}

const char* RuntimeStageBackendToString(RuntimeStageBackend backend) {
  switch (backend) {
    case RuntimeStageBackend::kSkSL:
      return "SKSL";
    case RuntimeStageBackend::kMetal:
      return "Metal";
    case RuntimeStageBackend::kOpenGLES:
      return "OpenGL ES2";
    case RuntimeStageBackend::kOpenGLES3:
      return "OpenGL ES3";
    case RuntimeStageBackend::kVulkan:
      return "Vulkan";
  }
  return "Unknown";
}

std::set<RuntimeStageBackend> FragmentProgram::GetAvailableStages() const {
  std::set<RuntimeStageBackend> stages;
  for (const auto& stage : stages_) {
    stages.insert(stage.first);
  }
  return stages;
}

}  // namespace impeller::interop
