/**
 * SPDX-FileCopyrightText: 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

/**
 * Orthanc for Education
 * Copyright (C) 2024-2026 Sebastien Jodogne, EPL UCLouvain, Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, either version 3 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
 * Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 **/


#include "EducationToolbox.h"

#include "HttpToolbox.h"

#include <OrthancException.h>
#include <SerializationToolbox.h>
#include <Toolbox.h>

#include <cassert>


static std::string FormatGetArgument(const std::string& key,
                                     const std::string& value)
{
  assert(!key.empty());

  if (value.empty())
  {
    return "";
  }
  else
  {
    /**
     * The following implementation is correct if value is an UTF-8
     * string: "When a new URI scheme defines a component that
     * represents textual data consisting of characters from the
     * Universal Character Set [UCS], the data should first be encoded
     * as octets according to the UTF-8 character encoding [STD63];
     * then only those octets that do not correspond to characters in
     * the unreserved set should be percent- encoded."
     * https://www.rfc-editor.org/rfc/rfc3986
     **/
    std::string tmp;
    Orthanc::Toolbox::UriEncode(tmp, value);
    return "&" + key + "=" + tmp;
  }
}


static std::string GenerateStudyViewerUrl(const std::string& projectId,
                                          ViewerType viewer,
                                          const std::string& studyId,
                                          const std::string& studyInstanceUid,
                                          const std::string& title)
{
  switch (viewer)
  {
    case ViewerType_StoneWebViewer:
      return "stone-webviewer/index.html?study=" + studyInstanceUid;

    case ViewerType_VolView:
      return "volview/index.html?names=[archive.zip]&urls=[../studies/" + studyId + "/archive]";

    case ViewerType_OHIF_Basic:
      return "ohif/viewer?StudyInstanceUIDs=" + studyInstanceUid;

    case ViewerType_OHIF_VolumeRendering:
      return "ohif/viewer?hangingprotocolId=mprAnd3DVolumeViewport&StudyInstanceUIDs=" + studyInstanceUid;

    case ViewerType_OHIF_TumorVolume:
      return "ohif/tmtv?StudyInstanceUIDs=" + studyInstanceUid;

    case ViewerType_OHIF_Segmentation:
      return "ohif/segmentation?StudyInstanceUIDs=" + studyInstanceUid;

    default:
      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
  }
}


static std::string GenerateSeriesViewerUrl(const std::string& projectId,
                                           ViewerType viewer,
                                           const std::string& seriesId,
                                           const std::string& studyInstanceUid,
                                           const std::string& seriesInstanceUid,
                                           const std::string& title)
{
  switch (viewer)
  {
    case ViewerType_StoneWebViewer:
      return "stone-webviewer/index.html?study=" + studyInstanceUid + "&series=" + seriesInstanceUid;

    case ViewerType_WholeSlideImaging:
      return ("wsi/app/viewer.html?series=" + seriesId +
              FormatGetArgument("project", projectId) +
              FormatGetArgument("title", title));

    case ViewerType_VolView:
      return "volview/index.html?names=[archive.zip]&urls=[../series/" + seriesId + "/archive]";

    default:
      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
  }
}


static std::string GenerateInstanceViewerUrl(const std::string& projectId,
                                             ViewerType viewer,
                                             const std::string& instanceId,
                                             const std::string& studyInstanceUid,
                                             const std::string& seriesInstanceUid,
                                             const std::string& sopInstanceUid,
                                             const std::string& title)
{
  switch (viewer)
  {
    case ViewerType_WholeSlideImaging:
      return ("wsi/app/viewer.html?instance=" + instanceId +
              FormatGetArgument("project", projectId) +
              FormatGetArgument("title", title));

    default:
      throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
  }
}


static const char* const KEY_LEVEL = "level";
static const char* const KEY_PROJECT = "project";
static const char* const KEY_RESOURCE_ID = "resource-id";
static const char* const KEY_SERIES_INSTANCE_UID = "series-instance-uid";
static const char* const KEY_SOP_INSTANCE_UID = "sop-instance-uid";
static const char* const KEY_STUDY_INSTANCE_UID = "study-instance-uid";
static const char* const KEY_TITLE = "title";


namespace EducationToolbox
{
  std::string GenerateViewerUrlForDeepLinking(ViewerType viewer,
                                              const std::map<std::string, std::string>& resource)
  {
    const std::string projectId = HttpToolbox::ReadMandatoryString(resource, KEY_PROJECT);
    if (projectId.empty())
    {
      throw Orthanc::OrthancException(Orthanc::ErrorCode_BadFileFormat);
    }

    const std::string levelString = HttpToolbox::ReadMandatoryString(resource, KEY_LEVEL);
    const Orthanc::ResourceType level = Orthanc::StringToResourceType(levelString.c_str());

    switch (level)
    {
      case Orthanc::ResourceType_Study:
        return GenerateStudyViewerUrl(projectId, viewer,
                                      HttpToolbox::ReadMandatoryString(resource, KEY_RESOURCE_ID),
                                      HttpToolbox::ReadMandatoryString(resource, KEY_STUDY_INSTANCE_UID),
                                      HttpToolbox::ReadMandatoryString(resource, KEY_TITLE));

      case Orthanc::ResourceType_Series:
        return GenerateSeriesViewerUrl(projectId, viewer,
                                       HttpToolbox::ReadMandatoryString(resource, KEY_RESOURCE_ID),
                                       HttpToolbox::ReadMandatoryString(resource, KEY_STUDY_INSTANCE_UID),
                                       HttpToolbox::ReadMandatoryString(resource, KEY_SERIES_INSTANCE_UID),
                                       HttpToolbox::ReadMandatoryString(resource, KEY_TITLE));

      case Orthanc::ResourceType_Instance:
        return GenerateInstanceViewerUrl(projectId, viewer,
                                         HttpToolbox::ReadMandatoryString(resource, KEY_RESOURCE_ID),
                                         HttpToolbox::ReadMandatoryString(resource, KEY_STUDY_INSTANCE_UID),
                                         HttpToolbox::ReadMandatoryString(resource, KEY_SERIES_INSTANCE_UID),
                                         HttpToolbox::ReadMandatoryString(resource, KEY_SOP_INSTANCE_UID),
                                         HttpToolbox::ReadMandatoryString(resource, KEY_TITLE));

      default:
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
    }
  }


  std::string GenerateViewerUrlGeneric(const std::string& projectId,
                                       ViewerType viewer,
                                       const Json::Value& resource)
  {
    const std::string levelString = Orthanc::SerializationToolbox::ReadString(resource, KEY_LEVEL);
    const Orthanc::ResourceType level = Orthanc::StringToResourceType(levelString.c_str());

    switch (level)
    {
      case Orthanc::ResourceType_Study:
        return GenerateStudyViewerUrl(projectId, viewer,
                                      Orthanc::SerializationToolbox::ReadString(resource, KEY_RESOURCE_ID),
                                      Orthanc::SerializationToolbox::ReadString(resource, KEY_STUDY_INSTANCE_UID),
                                      Orthanc::SerializationToolbox::ReadString(resource, KEY_TITLE));

      case Orthanc::ResourceType_Series:
        return GenerateSeriesViewerUrl(projectId, viewer,
                                       Orthanc::SerializationToolbox::ReadString(resource, KEY_RESOURCE_ID),
                                       Orthanc::SerializationToolbox::ReadString(resource, KEY_STUDY_INSTANCE_UID),
                                       Orthanc::SerializationToolbox::ReadString(resource, KEY_SERIES_INSTANCE_UID),
                                       Orthanc::SerializationToolbox::ReadString(resource, KEY_TITLE));

      case Orthanc::ResourceType_Instance:
        return GenerateInstanceViewerUrl(projectId, viewer,
                                         Orthanc::SerializationToolbox::ReadString(resource, KEY_RESOURCE_ID),
                                         Orthanc::SerializationToolbox::ReadString(resource, KEY_STUDY_INSTANCE_UID),
                                         Orthanc::SerializationToolbox::ReadString(resource, KEY_SERIES_INSTANCE_UID),
                                         Orthanc::SerializationToolbox::ReadString(resource, KEY_SOP_INSTANCE_UID),
                                         Orthanc::SerializationToolbox::ReadString(resource, KEY_TITLE));

      default:
        throw Orthanc::OrthancException(Orthanc::ErrorCode_NotImplemented);
    }
  }
}
