/*
 * RSessionContext.hpp
 *
 * Copyright (C) 2022 by Posit Software, PBC
 *
 * Unless you have received this program directly from Posit Software pursuant
 * to the terms of a commercial license agreement with Posit Software, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#ifndef CORE_R_UTIL_R_SESSION_CONTEXT_HPP
#define CORE_R_UTIL_R_SESSION_CONTEXT_HPP

#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
#include <iostream>

#include <boost/function.hpp>

#include <core/http/Util.hpp>

#define kProjectNone               "none"
#define kUserIdLen                 5
#define kProjectIdLen              8
#define kProjectNoneId             "cfc78a31"
#define kJupyterLabId              "21f2ed72"
#define kJupyterNotebookId         "2cb256d2"
#define kWorkspacesId              "3c286bd3"
#define kVSCodeId                  "3c9ab5a7"
#define kPositronId                "6ae9dc1b"
// Project ID f468a750 is reserved for R Scripts launched as jobs.
#define kProjectScript             "f468a750"

#define kWorkbenchRStudio          "RStudio"
#define kWorkbenchJupyterLab       "JupyterLab"
#define kWorkbenchJupyterNotebook  "Jupyter Notebook"
#define kWorkbenchVSCode           "VS Code"
#define kWorkbenchPositron         "Positron"
#define kJupyterExecutableName     "jupyter"
#define kVSCodeExecutableName      "code-server"
#define kPositronExecutableName    "positron"
#define kJupyterPathEnvVar         "WORKBENCH_JUPYTER_PATH"
#define kVSCodePathDefault         "/usr/lib/rstudio-server/bin/pwb-code-server/bin/code-server"
#define kVSCodePathEnvVar          "WORKBENCH_VSCODE_PATH"
#define kPositronPathDefault       "/usr/lib/rstudio-server/bin/positron-server/bin/positron-server"
#define kPositronPathEnvVar        "WORKBENCH_POSITRON_PATH"

#ifdef _WIN32
typedef unsigned int uid_t;
inline uid_t geteuid()
{
   return 0;
}
#endif

namespace rstudio {
namespace core {

class FilePath;

namespace r_util {

class IActiveSessionsStorage;

enum SessionScopeState
{
   ScopeValid, 
   ScopeInvalidSession,
   ScopeInvalidProject,
   ScopeMissingProject,
};

void setMinUid(uid_t minUid);
std::string obfuscatedUserId(uid_t uid);

class ProjectId
{
public:
   ProjectId()
   {}

   ProjectId(const std::string &id)
   {
      if (id.length() == kProjectIdLen)
      {
         id_ = id;
         userId_ = obfuscatedUserId(::geteuid());
      }
      else if (id.length() == (kProjectIdLen + kUserIdLen))
      {
         userId_ = id.substr(0, kUserIdLen);
         id_ = id.substr(kUserIdLen);
      }
   }

   ProjectId(const std::string& id, const std::string& userId):
      id_(id), userId_(userId)
   {}

   ProjectId(const std::string& id, uid_t userId)
   {
      id_ = std::string();
      if (id.length() == kProjectIdLen)
      {
         id_ = id;
      }
      else if (id.length() == (kProjectIdLen + kUserIdLen))
      {
         id_ = id.substr(kUserIdLen);
      }
      userId_ = obfuscatedUserId(userId);
   }

   std::string asString() const
   {
      return userId_ + id_;
   }

   bool operator==(const ProjectId &other) const
   {
      return id_  == other.id_ && userId_ == other.userId_;
   }

   bool operator<(const ProjectId &other) const
   {
       return id_ < other.id_ ||
              (id_  == other.id_  && userId_  < other.userId_);
   }

   const std::string& id() const
   {
      return id_;
   }

   const std::string& userId() const
   {
      return userId_;
   }

private:
   std::string id_;
   std::string userId_;
};

typedef boost::function<std::string(const ProjectId&)> ProjectIdToFilePath;
typedef boost::function<ProjectId(const std::string&)> FilePathToProjectId;

class SessionScope
{
private:
   SessionScope(const ProjectId& project, const std::string& id)
      : project_(project), id_(id)
   {
   }

public:

   static SessionScope fromProject(
                           const std::string& project,
                           const std::string& id,
                           const FilePathToProjectId& filePathToProjectId);

   static std::string projectPathForScope(
                           const SessionScope& scope,
                           const ProjectIdToFilePath& projectIdToFilePath);

   static SessionScope fromProjectId(const ProjectId& project,
                                     const std::string& id);

   static SessionScope projectNone(const std::string& id);

   static SessionScope jupyterLabSession(const std::string& id);
   static SessionScope jupyterNotebookSession(const std::string& id);

   static SessionScope vscodeSession(const std::string& id);

   static SessionScope positronSession(const std::string& id);

   static SessionScope projectNone(const std::string& id, uid_t uid);

   static SessionScope jupyterLabSession(const std::string& id, uid_t uid);
   static SessionScope jupyterNotebookSession(const std::string& id, uid_t uid);

   static SessionScope vscodeSession(const std::string& id, uid_t uid);

   static SessionScope positronSession(const std::string& id, uid_t uid);

   // Only use this if you are in the session user's process as it uses the effective uid
   static SessionScope fromSessionId(const std::string &id, const std::string& editor);

   static SessionScope fromSessionId(const std::string &id, const std::string& editor, uid_t uid);

   SessionScope()
   {
   }

   bool isProjectNone() const;

   bool isWorkspaces() const;

   bool isJupyter() const;
   bool isJupyterLab() const;
   bool isJupyterNotebook() const;
   bool isVSCode() const;
   bool isPositron() const;


   std::string workbench() const;

   const std::string project() const { return project_.asString(); }

   const std::string& id() const { return id_; }

   const ProjectId& projectId() const { return project_; }

   bool empty() const { return project_.id().empty(); }

   bool operator==(const SessionScope &other) const {
      return project_ == other.project_ && id_ == other.id_;
   }

   bool operator!=(const SessionScope &other) const {
      return !(*this == other);
   }

   bool operator<(const SessionScope &other) const {
       return project_ < other.project_ ||
              (project_ == other.project_ && id_ < other.id_);
   }

   const std::string sessionId() const
   {
      return core::http::util::urlEncode(projectId().asString()) + id();
   }

private:
   ProjectId project_;
   std::string id_;
};

SessionScopeState validateSessionScope(
   std::shared_ptr<IActiveSessionsStorage> storage,
   const SessionScope& scope,
   const core::FilePath& userHomePath,
   const core::FilePath& userScratchPath,
   core::r_util::ProjectIdToFilePath projectIdToFilePath,
   bool projectSharingEnabled,
   std::string* pProjectFilePath,
   std::string* pErrorMsg);

bool isSharedPath(const std::string& projectPath,
                  const core::FilePath& userHomePath);

std::string urlPathForSessionScope(const SessionScope& scope);

std::string createSessionUrl(const std::string& hostPageUrl,
                             const SessionScope& scope);

void parseSessionUrl(const std::string& url,
                     SessionScope* pScope,
                     std::string* pUrlPrefix,
                     std::string* pUrlWithoutPrefix,
                     std::string* pBaseUrl = nullptr,
                     std::string* pQueryParams = nullptr);


struct SessionContext
{
   SessionContext()
   {
   }

   explicit SessionContext(const std::string& username,
                           const SessionScope& scope = SessionScope())
      : username(username), scope(scope)
   {
   }
   std::string username;
   SessionScope scope;

   bool empty() const { return username.empty(); }

   bool operator==(const SessionContext &other) const {
      return username == other.username && scope == other.scope;
   }

   bool operator<(const SessionContext &other) const {
       return username < other.username ||
              (username == other.username && scope < other.scope);
   }
};


std::ostream& operator<< (std::ostream& os, const SessionContext& context);

std::string sessionScopeFile(std::string prefix,
                             const SessionScope& scope);

std::string sessionScopePrefix(const std::string& username);

std::string sessionScopesPrefix(const std::string& username);

std::string sessionContextFile(const SessionContext& context);

std::string generateScopeId();
std::string generateScopeId(const std::vector<std::string>& reserved);

r_util::SessionScope getSessionScope(const std::string& workbench, const std::string& sessionId, const std::string& projectId, const uid_t uid);

std::string getSessionUrlForScope(const r_util::SessionScope& scope, const std::string& hostPageUrl);

} // namespace r_util
} // namespace core 
} // namespace rstudio


#endif // CORE_R_UTIL_R_SESSION_CONTEXT_HPP
