// PR4607
template <class T> struct X {};

template <> struct X<char>
{
  static char* g();
};

template <class T> struct X2 {};

template <class U>
struct X2<U*> {
  static void f() {
    X<U>::g();
  }
};

void a(char *a, char *b) {X2<char*>::f();}

namespace WonkyAccess {
  template<typename T>
  struct X {
    int m;
  };

  template<typename U>
  class Y;

  template<typename U>
  struct Y<U*> : X<U> { };

  template<>
  struct Y<float*> : X<float> { };

  int f(Y<int*> y, Y<float*> y2) {
    return y.m + y2.m;
  }
}

namespace rdar9169404 {
  template<typename T, T N> struct X { };
  template<bool C> struct X<bool, C> {
    typedef int type;
  };

  X<bool, -1>::type value;
#if __cplusplus >= 201103L
  // expected-error@-2 {{non-type template argument evaluates to -1, which cannot be narrowed to type 'bool'}}
#else
  // expected-no-diagnostics
#endif
}

namespace rdar39524996 {
  template <typename T, typename U>
  struct enable_if_not_same
  {
    typedef void type;
  };
  template <typename T>
  struct enable_if_not_same<T, T>;

  template <typename T>
  struct Wrapper {
    // Assertion triggered on trying to set twice the same partial specialization
    template <class U>
    Wrapper(const Wrapper<U>& other,
            typename enable_if_not_same<U, T>::type* = 0) {}

    explicit Wrapper(int i) {}
  };

  template <class T>
  struct Container {
    // It is important that the struct has implicit copy and move constructors.

    template <class U>
    Container(const Container<U>& other) : x(static_cast<T>(other.x)) {}

    // Implicit constructors are member-wise, so the field triggers instantiation
    // of T constructors and we instantiate all of them for overloading purposes.
    T x;
  };

  void takesWrapperInContainer(const Container< Wrapper<int> >& c);
  void test() {
    // Type mismatch triggers initialization with conversion which requires
    // implicit constructors to be instantiated.
    Container<int> c;
    takesWrapperInContainer(c);
  }
}


try { info.text = stoi(cm[2].str()); } catch (...) {}

#include <crispy/StackTrace.h>

#include <cctype>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <memory>
#include <optional>
#include <regex>
#include <typeinfo>

#include <fmt/format.h>

#if !defined(_WIN32)
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#if defined(HAVE_CXXABI_H)
#include <cxxabi.h>
#endif

#if defined(HAVE_EXECINFO_H)
#include <execinfo.h>
#endif

#if defined(HAVE_DLFCN_H)
#include <dlfcn.h>
#endif

#if defined(HAVE_UNWIND_H)
#include <unwind.h>
#endif

#include <iostream>

using std::string;
using std::vector;
using std::optional;
using std::nullopt;
using std::regex;

namespace crispy {

constexpr size_t MAX_FRAMES {128};
constexpr size_t SKIP_FRAMES {0};

#if defined(__linux__) || defined(__APPLE__)
struct Pipe // {{{
{
    int pfd[2];

    Pipe(): pfd{-1, -1}
    {
        if (pipe(pfd))
            perror("pipe");
    }

    bool good() const noexcept
    {
        return pfd[0] != -1 && pfd[1] != -1;
    }

    int reader() noexcept { return pfd[0]; }
    int writer() noexcept { return pfd[1]; }

    ~Pipe()
    {
        close();
    }

    void close()
    {
        if (pfd[0] != -1) ::close(pfd[0]);
        if (pfd[1] != -1) ::close(pfd[1]);
    }
};
#endif

vector<void*> StackTrace::getFrames(size_t _skip, size_t _max)
{
    vector<void*> frames;

#if defined(HAVE_BACKTRACE)
    frames.resize(_skip + _max);
    frames.resize(::backtrace(&frames[0], static_cast<int>(_skip + _max)));
    std::copy(std::next(frames.begin(), _skip), frames.end(), frames.begin());
    frames.resize(frames.size() > _skip
            ? frames.size() - _skip
            : std::min(frames.size(), _skip));
#endif

    return frames;
}

optional<DebugInfo> StackTrace::getDebugInfoForFrame(void* p)
{
    if (!p)
        return nullopt;

#if defined(__linux__)
    auto pipe = Pipe();
    if (!pipe.good())
        return nullopt;
    pid_t childPid = fork();
    switch (childPid)
    {
        case -1:
            perror("vfork");
            return nullopt;
        case 0: // in child
        {
            std::string addr2lineExe = "/usr/bin/addr2line";
            char exe[512]{};
            int rv = readlink("/proc/self/exe", exe, sizeof(exe));
            if (rv < 0)
                _exit(EXIT_FAILURE);
            char addr[32];
            snprintf(addr, sizeof(addr), "%p", p);
            char const* const argv[] = {
                addr2lineExe.c_str(),
                "-pe",
                exe,
                addr,
                nullptr
            };
            close(STDIN_FILENO);
            if (pipe.writer() != STDOUT_FILENO)
            {
                dup2(pipe.writer(), STDOUT_FILENO);
                close(pipe.writer());
            }
            close(pipe.reader());
            close(STDERR_FILENO);
            rv = execv(argv[0], (char **) argv);
            perror("execvp");
            _exit(rv);
        }
        default: // in parent
        {
            int status = 0;
            waitpid(childPid, &status, 0);

            char buf[4096];
            ssize_t len = 0;
            len = ::read(pipe.reader(), buf, sizeof(buf));
            while (len > 0 && std::isspace(buf[len - 1]))
                --len;

            // result on success:
            DebugInfo info;
#if 0
            auto static const re = regex(R"(^(.*):\d+$)");
            std::cmatch cm;
            if (!std::regex_search((char const*)buf, (char const*)buf + len, cm, re))
                return nullopt;

            info.text = cm[1].str();

#else
            if (len > 0)
                info.text = string(buf, len);
#endif

            if (info.text == "??:0")
                return nullopt;

            return {std::move(info)};
        }
    }
#else
    return nullopt;
#endif
}

StackTrace::StackTrace():
#if defined(HAVE_BACKTRACE)
    frames_{SKIP_FRAMES + MAX_FRAMES}
#else
    frames_{}
#endif
{
#if defined(HAVE_BACKTRACE)
    frames_.resize(::backtrace(&frames_[0], SKIP_FRAMES + MAX_FRAMES));
#endif
}

string StackTrace::demangleSymbol(const char* symbol)
{
#if defined(__linux__) || defined(__APPLE__)
    int status = 0;
    char* demangled = abi::__cxa_demangle(symbol, nullptr, 0, &status);

    if (demangled) {
        string result(demangled, strlen(demangled));
        free(demangled);
        return result;
    } else {
        return symbol;
    }
#else
    return symbol;
#endif
}

vector<string> StackTrace::symbols() const
{
    if (empty())
        return {};

    auto const frames = getFrames(SKIP_FRAMES, MAX_FRAMES);
    vector<string> output;
    for (size_t i = 0; i < frames.size(); ++i)
    {
#if defined(CONTOUR_STACKTRACE_ADDR2LINE)
        auto debugInfo = getDebugInfoForFrame(frames[i]);
        if (!debugInfo)
            output.emplace_back(fmt::format("{}", frames[i]));
        else
            output.emplace_back(debugInfo->text);
#else
        output.emplace_back(fmt::format("{}", frames[i]));
#endif
    }
    return output;
}

} // namespace xzero


StaticSaliencySpectralResidual::StaticSaliencySpectralResidual()
{
  className = "SPECTRAL_RESIDUAL";
  resImWidth = 64;
  resImHeight = 64;
}

/*M///////////////////////////////////////////////////////////////////////////////////////
 // Third party copyrights are property of their respective owners.

#include "precomp.hpp"

namespace cv
{
namespace saliency
{

/**
 * SaliencySpectralResidual
 */

StaticSaliencySpectralResidual::~StaticSaliencySpectralResidual()
{

}

void StaticSaliencySpectralResidual::read( const cv::FileNode& /*fn*/)
{
}

void StaticSaliencySpectralResidual::write( cv::FileStorage& /*fs*/) const
{
}

bool StaticSaliencySpectralResidual::computeSaliencyImpl( InputArray image, OutputArray saliencyMap )
{
  Mat grayTemp, grayDown;
  std::vector<Mat> mv;
  Size resizedImageSize( resImWidth, resImHeight );

  Mat realImage( resizedImageSize, CV_64F );
  Mat imaginaryImage( resizedImageSize, CV_64F );
  imaginaryImage.setTo( 0 );
  Mat combinedImage( resizedImageSize, CV_64FC2 );
  Mat imageDFT;
  Mat logAmplitude;
  Mat angle( resizedImageSize, CV_64F );
  Mat magnitude( resizedImageSize, CV_64F );
  Mat logAmplitude_blur, imageGR;

  if( image.channels() == 3 )
  {
    cvtColor( image, imageGR, COLOR_BGR2GRAY );
    resize( imageGR, grayDown, resizedImageSize, 0, 0, INTER_LINEAR_EXACT );
  }
  else
  {
    resize( image, grayDown, resizedImageSize, 0, 0, INTER_LINEAR_EXACT );
  }

  grayDown.convertTo( realImage, CV_64F );

  mv.push_back( realImage );
  mv.push_back( imaginaryImage );
  merge( mv, combinedImage );
  dft( combinedImage, imageDFT );
  split( imageDFT, mv );

  //-- Get magnitude and phase of frequency spectrum --//
  cartToPolar( mv.at( 0 ), mv.at( 1 ), magnitude, angle, false );
  log( magnitude + Scalar( 1 ), logAmplitude );
  //-- Blur log amplitude with averaging filter --//
  blur( logAmplitude, logAmplitude_blur, Size( 3, 3 ), Point( -1, -1 ), BORDER_DEFAULT );

  exp( logAmplitude - logAmplitude_blur, magnitude );
  //-- Back to cartesian frequency domain --//
  polarToCart( magnitude, angle, mv.at( 0 ), mv.at( 1 ), false );
  merge( mv, imageDFT );
  dft( imageDFT, combinedImage, DFT_INVERSE );
  split( combinedImage, mv );

  cartToPolar( mv.at( 0 ), mv.at( 1 ), magnitude, angle, false );
  GaussianBlur( magnitude, magnitude, Size( 5, 5 ), 8, 0, BORDER_DEFAULT );
  magnitude = magnitude.mul( magnitude );

  double minVal, maxVal;
  minMaxLoc( magnitude, &minVal, &maxVal );

  magnitude = magnitude / maxVal;
  magnitude.convertTo( magnitude, CV_32F );

  resize( magnitude, saliencyMap, image.size(), 0, 0, INTER_LINEAR_EXACT );

#ifdef SALIENCY_DEBUG
  // visualize saliency map
  imshow( "Saliency Map Interna", saliencyMap );
#endif

  return true;

}

} /* namespace saliency */
}/* namespace cv */


void UFlareQuest::StartObjectiveTracking()
{
	FLOG("UFlareQuest::StartObjectiveTracking");

	if (TrackObjectives)
	{
		return; // No change
	}

	FLOG("UFlareQuest::StartObjectiveTracking : tracking");
	TrackObjectives = true;
	QuestManager->GetGame()->GetPC()->CompleteObjective();
	UpdateObjectiveTracker();
}

#include "HeliumRain/Quests/FlareQuest.h"
#include "HeliumRain/Flare.h"

#include "../Game/FlareGame.h"
#include "../Game/FlareGameTools.h"

#include "../Player/FlareMenuManager.h"
#include "../Player/FlarePlayerController.h"

#include "HeliumRain/Quests/FlareQuestStep.h"
#include "HeliumRain/Quests/FlareQuestCondition.h"
#include "HeliumRain/Quests/FlareQuestAction.h"

#include <GameFramework/InputSettings.h>

#define LOCTEXT_NAMESPACE "FlareQuest"

/*----------------------------------------------------
	Constructor
----------------------------------------------------*/

UFlareQuest::UFlareQuest(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer),
	  TrackObjectives(false),
	  Client(NULL)
{
	Accepted = false;
	QuestData.AvailableDate = 0;
	QuestData.AcceptationDate = 0;
}

/*----------------------------------------------------
	Save
----------------------------------------------------*/

void UFlareQuest::LoadInternal(UFlareQuestManager* Parent)
{
	QuestManager = Parent;
	QuestStatus = EFlareQuestStatus::PENDING;

	TriggerCondition = UFlareQuestConditionAndGroup::Create(this, true);
	ExpirationCondition = UFlareQuestConditionOrGroup::Create(this, false);
}

void UFlareQuest::Restore(const FFlareQuestProgressSave& Data)
{
	QuestData = Data;
	QuestStatus = Data.Status;

	for (UFlareQuestCondition* Condition: TriggerCondition->GetAllConditions())
	{
		Condition->Restore(UFlareQuestCondition::GetStepConditionBundle(Condition, Data.TriggerConditionsSave));
	}

	for (UFlareQuestCondition* Condition: ExpirationCondition->GetAllConditions())
	{
		Condition->Restore(UFlareQuestCondition::GetStepConditionBundle(Condition, Data.ExpirationConditionsSave));
	}

	CurrentStep = NULL;

	// Init current step
	for(UFlareQuestStep* Step : Steps)
	{
		if(Data.SuccessfullSteps.Contains(Step->GetIdentifier()))
		{
			SuccessfullSteps.Add(Step);
			Step->SetStatus(EFlareQuestStepStatus::COMPLETED);
		}
		else if(CurrentStep == NULL)
		{
			CurrentStep = Step;
			Step->Restore(Data.CurrentStepProgress);
		}

	}

	NextStep(true);
}

FFlareQuestProgressSave* UFlareQuest::Save()
{
	QuestData.SuccessfullSteps.Empty();
	QuestData.CurrentStepProgress.Empty();
	QuestData.TriggerConditionsSave.Empty();
	QuestData.ExpirationConditionsSave.Empty();

	QuestData.QuestIdentifier = GetIdentifier();
	QuestData.Status = QuestStatus;

	for(UFlareQuestStep* Step : SuccessfullSteps)
	{
		QuestData.SuccessfullSteps.Add(Step->GetIdentifier());
	}

	for (UFlareQuestCondition* Condition: TriggerCondition->GetAllConditions())
	{
		Condition->AddSave(QuestData.TriggerConditionsSave);
	}

	for (UFlareQuestCondition* Condition: ExpirationCondition->GetAllConditions())
	{
		Condition->AddSave(QuestData.ExpirationConditionsSave);
	}

	if(CurrentStep)
	{
		CurrentStep->Save(QuestData.CurrentStepProgress);
	}
	return &QuestData;
}

void UFlareQuest::SetupIndexes()
{
	int32 StepIndex = 0;
	for(UFlareQuestStep* Step : GetSteps())
	{
		Step->SetupStepIndexes(StepIndex++);
	}

	// Setup condition indexes
	int32 ConditionIndex = 0;
	for (UFlareQuestCondition* Condition: TriggerCondition->GetAllConditions())
	{
		Condition->SetConditionIndex(ConditionIndex++);
	}

	// Setup condition indexes
	ConditionIndex = 0;
	for (UFlareQuestCondition* Condition: ExpirationCondition->GetAllConditions())
	{
		Condition->SetConditionIndex(ConditionIndex++);
	}
}

/*----------------------------------------------------
	Gameplay
----------------------------------------------------*/

void UFlareQuest::SetStatus(EFlareQuestStatus::Type Status)
{
	QuestStatus = Status;
}

void UFlareQuest::UpdateState()
{
	switch(QuestStatus)
	{
		case EFlareQuestStatus::PENDING:
		{
			bool ConditionsStatus = TriggerCondition->IsCompleted();
			if (ConditionsStatus)
			{
				MakeAvailable();
				UpdateState();
			}
			break;
		}
		case EFlareQuestStatus::AVAILABLE:
		{
			bool ConditionsStatus = ExpirationCondition->IsCompleted();
			if (ConditionsStatus)
			{
				Abandon(true);
				break;
			}

			if (IsAccepted() || HasAutoAccept())
			{
				MakeOngoing();
				UpdateState();
			}
			break;
		}
		case EFlareQuestStatus::ONGOING:
		{
			if(!CurrentStep)
			{
				FLOGV("ERROR: Ongoing quest '%s'without current Step", *Identifier.ToString());
				return;
			}

			CurrentStep->UpdateState();

			if (CurrentStep->IsFailed())
			{
				Fail();
			}
			else if (CurrentStep->IsCompleted())
			{
				EndStep();
			}

			UpdateObjectiveTracker();
			break;
		}
	}
}

void UFlareQuest::EndStep()
{
	SuccessfullSteps.Add(CurrentStep);
	FLOGV("Quest %s step %s end", *GetIdentifier().ToString(), *CurrentStep->GetIdentifier().ToString());

	CurrentStep->PerformEndActions();

	CurrentStep = NULL;
	if (TrackObjectives)
	{
		QuestManager->GetGame()->GetPC()->CompleteObjective();
	}

	// Activate next step
	NextStep();
}

void UFlareQuest::NextStep(bool Silent)
{
	// Clear step progress

	CurrentStep = NULL;

	if (Steps.Num() == 0)
	{
		FLOGV("WARNING: The quest %s have no step", *GetIdentifier().ToString());
	}
	else
	{
		for(UFlareQuestStep* Step : Steps)
		{
			if (!SuccessfullSteps.Contains(Step))
			{
				CurrentStep = Step;

				FLOGV("Quest %s step %s begin", *GetIdentifier().ToString(), *Step->GetIdentifier().ToString());
				Step->Init();
				Step->PerformInitActions();

				if (!Silent)
				{
					FText MessageText = FormatTags(Step->GetStepDescription());
					SendQuestNotification(MessageText, GetQuestNotificationTag(), GetQuestCategory() == EFlareQuestCategory::TUTORIAL);
				}

				QuestManager->LoadCallbacks(this);
				UpdateState();
				return;
			}
		}
	}

	// All quest step are done. Success
	Success();
}

void UFlareQuest::Success()
{
	if (QuestStatus == EFlareQuestStatus::SUCCESSFUL)
	{
		// Already successful
		return;
	}

	QuestManager->GetGame()->GetPC()->GetMenuManager()->ClearNotifications(GetQuestNotificationTag());
	SetStatus(EFlareQuestStatus::SUCCESSFUL);
	UFlareQuestAction::PerformActions(SuccessActions);
	QuestManager->OnQuestSuccess(this);
}

void UFlareQuest::Fail()
{
	if (QuestStatus == EFlareQuestStatus::FAILED)
	{
		// Already failed
		return;
	}

	QuestManager->GetGame()->GetPC()->GetMenuManager()->ClearNotifications(GetQuestNotificationTag());

	SetStatus(EFlareQuestStatus::FAILED);
	UFlareQuestAction::PerformActions(FailActions);
	QuestManager->OnQuestFail(this, true);
}

void UFlareQuest::Abandon(bool Expired)
{
	if (QuestStatus == EFlareQuestStatus::ABANDONED)
	{
		// Already abandoned
		return;
	}

	QuestManager->GetGame()->GetPC()->GetMenuManager()->ClearNotifications(GetQuestNotificationTag());

	SetStatus(EFlareQuestStatus::ABANDONED);
	if(!Expired)
	{
		UFlareQuestAction::PerformActions(FailActions);
	}
	QuestManager->OnQuestFail(this, Expired ? false : true);
}

void UFlareQuest::MakeAvailable()
{
	if (QuestStatus == EFlareQuestStatus::AVAILABLE)
	{
		// Already available
		return;
	}

	QuestData.AvailableDate = QuestManager->GetGame()->GetGameWorld()->GetDate();
	SetStatus(EFlareQuestStatus::AVAILABLE);

	if (QuestStatus == EFlareQuestStatus::AVAILABLE)
	{
		QuestManager->OnQuestAvailable(this);
	}
}

void UFlareQuest::Accept()
{
	Accepted = true;
	UpdateState();
}

void UFlareQuest::MakeOngoing()
{
	if (QuestStatus == EFlareQuestStatus::ONGOING)
	{
		// Already ongoing
		return;
	}

	QuestData.AcceptationDate = QuestManager->GetGame()->GetGameWorld()->GetDate();
	SetStatus(EFlareQuestStatus::ONGOING);
	// Activate next step
	NextStep();

	// Don't notify ongoing if quest is not ongoing after first NextStep
	if (QuestStatus == EFlareQuestStatus::ONGOING)
	{
		QuestManager->OnQuestOngoing(this);
	}
}

FText UFlareQuest::FormatTags(FText Message)
{
	// Replace input tags
	bool Found = false;
	FString MessageString = Message.ToString();

	// input-axis
	do {
		Found = false;
		FString StartTag = TEXT("<input-axis:");
		FString EndTag = TEXT(">");

		int StartIndex = MessageString.Find(StartTag);
		if (StartIndex > 0)
		{
			int EndIndex = MessageString.Find(EndTag,ESearchCase::CaseSensitive, ESearchDir::FromStart, StartIndex);
			if (EndIndex > 0)
			{
				// Tag found, replace it.
				Found = true;

				FString TagValue = MessageString.Mid(StartIndex + StartTag.Len(), EndIndex - (StartIndex + StartTag.Len()));

				FString AxisString;
				FString ScaleString;
				TagValue.Split(",", &AxisString, &ScaleString);
				float Scale = FCString::Atof(*ScaleString);
				FName AxisName = FName(*AxisString);

				FString Mapping;

				UInputSettings* InputSettings = UInputSettings::StaticClass()->GetDefaultObject<UInputSettings>();

				for (int32 i = 0; i < InputSettings->GetAxisMappings().Num(); i++)
				{
					FInputAxisKeyMapping Axis = InputSettings->GetAxisMappings()[i];
					if (Axis.AxisName == AxisName && Axis.Scale == Scale && !Axis.Key.IsGamepadKey())
					{
						Mapping = Axis.Key.ToString();
					}
				}

				FString TagString = TEXT("<")+ Mapping + TEXT(">");

				MessageString = MessageString.Left(StartIndex) + TagString + MessageString.RightChop(EndIndex+1);
			}
			else
			{
				FLOGV("ERROR None closed tag at offset %d for quest %s: %s", StartIndex, *GetIdentifier().ToString(), *MessageString);
			}
		}

	} while(Found);

	// input-action
	do {
		Found = false;
		FString StartTag = TEXT("<input-action:");
		FString EndTag = TEXT(">");

		int StartIndex = MessageString.Find(StartTag);
		if (StartIndex > 0)
		{
			int EndIndex = MessageString.Find(EndTag,ESearchCase::CaseSensitive, ESearchDir::FromStart, StartIndex);
			if (EndIndex > 0)
			{
				// Tag found, replace it.
				Found = true;

				FString TagValue = MessageString.Mid(StartIndex + StartTag.Len(), EndIndex - (StartIndex + StartTag.Len()));

				FName ActionName = FName(*TagValue);
				FString Mapping = AFlareMenuManager::GetKeyNameFromActionName(ActionName);

				FString TagString = TEXT("<")+ Mapping + TEXT(">");

				MessageString = MessageString.Left(StartIndex) + TagString + MessageString.RightChop(EndIndex+1);
			}
			else
			{
				FLOGV("ERROR None closed tag at offset %d for quest %s: %s", StartIndex, *GetIdentifier().ToString(), *MessageString);
			}
		}

	} while(Found);

	MessageString = MessageString.Replace(TEXT("<br>"), TEXT("\n"));

	return FText::FromString(MessageString);
}

void UFlareQuest::SendQuestNotification(FText Message, FName Tag, bool Pinned)
{
	FText Text = GetQuestName();
	FLOGV("UFlareQuest::SendQuestNotification : %s", *Message.ToString());

	FFlareMenuParameterData Data;
	Data.Quest = this;

	QuestManager->GetGame()->GetPC()->Notify(Text, Message, Tag, EFlareNotification::NT_Quest, Pinned, EFlareMenu::MENU_Quest, Data);
}

bool UFlareQuest::HasAutoAccept()
{
	return GetQuestCategory() == EFlareQuestCategory::TUTORIAL;
}

bool UFlareQuest::IsActive()
{
	return QuestStatus == EFlareQuestStatus::PENDING || QuestStatus == EFlareQuestStatus::AVAILABLE || QuestStatus == EFlareQuestStatus::ONGOING;
}

UFlareSimulatedSector* UFlareQuest::FindSector(FName SectorIdentifier)
{
	UFlareSimulatedSector* Sector = QuestManager->GetGame()->GetGameWorld()->FindSector(SectorIdentifier);

	if(!Sector)
	{
		FLOGV("ERROR: Fail to find sector '%s' for quest '%s'",
		  *SectorIdentifier.ToString(),
		  *GetIdentifier().ToString());
	}
	return Sector;
}

/*----------------------------------------------------
	Objective tracking
----------------------------------------------------*/

void UFlareQuest::StopObjectiveTracking()
{
	FLOG("UFlareQuest::StopObjectiveTracking");

	if (!TrackObjectives)
	{
		return; // No change
	}

	FLOG("UFlareQuest::StopObjectiveTracking : not tracking anymore");
	TrackObjectives = false;
	QuestManager->GetGame()->GetPC()->CompleteObjective();
}

void UFlareQuest::UpdateObjectiveTracker()
{
	if (!TrackObjectives)
	{
		return;
	}

	FFlarePlayerObjectiveData Objective;
	if (CurrentStep)
	{
		Objective.StepsDone = GetSuccessfullStepCount();
		Objective.StepsCount = GetStepCount();
		Objective.Description = GetQuestDescription();
		Objective.Name = GetQuestName();

		CurrentStep->AddEndConditionObjectives(&Objective);
	}

	QuestManager->GetGame()->GetPC()->StartObjective(Objective.Name, Objective);
}

/*----------------------------------------------------
	Callbacks
----------------------------------------------------*/

TArray<UFlareQuestCondition*> UFlareQuest::GetCurrentConditions()
{
	TArray<UFlareQuestCondition*> Conditions;

	switch(QuestStatus)
	{
		case EFlareQuestStatus::PENDING:
			// Use trigger conditions
			Conditions += TriggerCondition->GetAllConditions();
			break;
		case EFlareQuestStatus::ONGOING:
		 {
			// Use current step conditions
			if (CurrentStep)
			{
				Conditions += CurrentStep->GetEnableCondition()->GetAllConditions();
				Conditions += CurrentStep->GetEndCondition()->GetAllConditions();
				Conditions += CurrentStep->GetFailCondition()->GetAllConditions();
				Conditions += CurrentStep->GetBlockCondition()->GetAllConditions();
			}
			else
			{
				FLOGV("WARNING: The quest %s have no step", *GetIdentifier().ToString());
			}
			break;
		}
		default:
			// Don't add callback in others cases
			break;
	}

	return Conditions;
}

TArray<EFlareQuestCallback::Type> UFlareQuest::GetCurrentCallbacks()
{
	TArray<EFlareQuestCallback::Type> Callbacks;

	// TODO Cache and return reference
	switch(QuestStatus)
	{
		case EFlareQuestStatus::PENDING:
			// Use trigger conditions
			UFlareQuestCondition::AddConditionCallbacks(Callbacks, TriggerCondition->GetAllConditions());

			if (Callbacks.Contains(EFlareQuestCallback::TICK_FLYING))
			{
				FLOGV("WARNING: The quest %s need a TICK_FLYING callback as trigger", *GetIdentifier().ToString());
			}
			break;
		case EFlareQuestStatus::AVAILABLE:
			// Use expiration conditions
			UFlareQuestCondition::AddConditionCallbacks(Callbacks, ExpirationCondition->GetAllConditions());
			break;
		case EFlareQuestStatus::ONGOING:
		 {
			// Use current step conditions
			if (CurrentStep)
			{
				UFlareQuestCondition::AddConditionCallbacks(Callbacks, CurrentStep->GetEnableCondition()->GetAllConditions());
				UFlareQuestCondition::AddConditionCallbacks(Callbacks, CurrentStep->GetEndCondition()->GetAllConditions());
				UFlareQuestCondition::AddConditionCallbacks(Callbacks, CurrentStep->GetFailCondition()->GetAllConditions());
				UFlareQuestCondition::AddConditionCallbacks(Callbacks, CurrentStep->GetBlockCondition()->GetAllConditions());
			}
			else
			{
				FLOGV("WARNING: The quest %s have no step", *GetIdentifier().ToString());
			}
			break;
		}
		default:
			// Don't add callback in others cases
			break;
	}

	return Callbacks;
}

void UFlareQuest::OnTradeDone(UFlareSimulatedSpacecraft* SourceSpacecraft, UFlareSimulatedSpacecraft* DestinationSpacecraft, FFlareResourceDescription* Resource, int32 Quantity)
{
	for (UFlareQuestCondition* Condition : GetCurrentConditions())
	{
		Condition->OnTradeDone(SourceSpacecraft, DestinationSpacecraft, Resource, Quantity);
	}
}

void UFlareQuest::OnSpacecraftDestroyed(UFlareSimulatedSpacecraft* Spacecraft, bool Uncontrollable, DamageCause Cause)
{
	for (UFlareQuestCondition* Condition : GetCurrentConditions())
	{
		Condition->OnSpacecraftDestroyed(Spacecraft, Uncontrollable, Cause);
	}
}

void UFlareQuest::OnSpacecraftCaptured(UFlareSimulatedSpacecraft* CapturedSpacecraftBefore, UFlareSimulatedSpacecraft* CapturedSpacecraftAfter)
{
	for (UFlareQuestCondition* Condition : GetCurrentConditions())
	{
		Condition->OnSpacecraftCaptured(CapturedSpacecraftBefore, CapturedSpacecraftAfter);
	}
}

void UFlareQuest::OnTravelStarted(UFlareTravel* Travel)
{
	for (UFlareQuestCondition* Condition : GetCurrentConditions())
	{
		Condition->OnTravelStarted(Travel);
	}
}

void UFlareQuest::OnEvent(FFlareBundle& Bundle)
{
	for (UFlareQuestCondition* Condition : GetCurrentConditions())
	{
		Condition->OnEvent(Bundle);
	}
}

/*----------------------------------------------------
	Getters
----------------------------------------------------*/

FText UFlareQuest::GetQuestReward()
{
	TArray<UFlareQuestAction*> Actions = GetSuccessActions();

	FString Result;

	for (auto Action : Actions)
	{
		UFlareQuestActionGiveMoney* MoneyAction = Cast<UFlareQuestActionGiveMoney>(Action);
		if (MoneyAction)
		{
			Result += FText::Format(LOCTEXT("QuestRewardMoneyFormat", "\u2022 Payment of {0} credits\n"), FText::AsNumber(UFlareGameTools::DisplayMoney(MoneyAction->GetAmount()))).ToString();
		}

		UFlareQuestActionGiveResearch* ResearchAction = Cast<UFlareQuestActionGiveResearch>(Action);
		if (ResearchAction)
		{
			Result += FText::Format(LOCTEXT("QuestRewardResearchFormat", "\u2022 Gain of {0} research\n"), FText::AsNumber(ResearchAction->GetAmount())).ToString();
		}

		UFlareQuestActionDiscoverSector* SectorAction = Cast<UFlareQuestActionDiscoverSector>(Action);
		if (SectorAction)
		{
			Result += FText::Format(LOCTEXT("QuestRewardSectorFormat", "\u2022 Coordinates to sector {0}\n"), SectorAction->GetSector()->GetSectorName()).ToString();
		}

		UFlareQuestActionReputationChange* ReputationAction = Cast<UFlareQuestActionReputationChange>(Action);
		if (ReputationAction)
		{
			Result += FText::Format(LOCTEXT("QuestRewardReputationFormat", "\u2022 Gain of {0} reputation\n"), FText::AsNumber(ReputationAction->GetAmount())).ToString();
		}
	}

	if (Result.Len() == 0)
	{
		if (Actions.Num() == 0)
		{
			return LOCTEXT("QuestRewardNone", "\u2022 No rewards");
		}
		else
		{
			return LOCTEXT("QuestRewardUnknown", "\u2022 Unknown rewards");
		}
	}

	return FText::FromString(Result);
}

FText UFlareQuest::GetQuestPenalty()
{
	TArray<UFlareQuestAction*> Actions = GetFailActions();

	FString Result;

	for (auto Action : Actions)
	{
		UFlareQuestActionGiveMoney* MoneyAction = Cast<UFlareQuestActionGiveMoney>(Action);
		if (MoneyAction)
		{
			Result += FText::Format(LOCTEXT("QuestPenaltyMoneyFormat", "\u2022 Fine of {0} credits\n"), FText::AsNumber(UFlareGameTools::DisplayMoney(-MoneyAction->GetAmount()))).ToString();
		}

		UFlareQuestActionReputationChange* ReputationAction = Cast<UFlareQuestActionReputationChange>(Action);
		if (ReputationAction)
		{
			Result += FText::Format(LOCTEXT("QuestPenaltyReputationFormat", "\u2022 Loss of {0} reputation\n"), FText::AsNumber(-ReputationAction->GetAmount())).ToString();
		}
	}

	if (Result.Len() == 0)
	{
		if (Actions.Num() == 0)
		{
			return LOCTEXT("QuestPenaltyNone", "\u2022 No penalties");
		}
		else
		{
			return LOCTEXT("QuestPenaltyUnknown", "\u2022 Unknown penalties");
		}
	}

	return FText::FromString(Result);
}

FText UFlareQuest::GetQuestExpiration()
{
	FString Result;

	for (auto Condition : ExpirationCondition->GetAllConditions())
	{
		if (Result.Len())
		{
			Result += "\n";
		}
		Result += LOCTEXT("QuestExpirationSymbol", "\u2022 ").ToString() + Condition->GetInitialLabel().ToString();
	}

	if (Result.Len())
	{
		return FText::FromString(Result);
	}
	else
	{
		return LOCTEXT("QuestExpirationNone", "\u2022 This contract never expires.");
	}
}

TArray<UFlareQuestCondition*> UFlareQuest::GetGlobalFailConditions()
{
	TArray<UFlareQuestCondition*> GlobalFailConditions;

	for(UFlareQuestStep* Step : Steps)
	{
		TArray<UFlareQuestCondition*> StepFailConditions = Step->GetFailCondition()->GetAllConditions();

		if(Step == Steps[0])
		{
			GlobalFailConditions.Append(StepFailConditions);
		}
		else
		{
			TArray<UFlareQuestCondition*> ConditionsToRemove;

			// Remove missing conditions
			for (auto Condition : GlobalFailConditions)
			{
				if(!StepFailConditions.Contains(Condition))
				{
					ConditionsToRemove.Add(Condition);
				}
			}

			for (auto Condition : ConditionsToRemove)
			{
				GlobalFailConditions.Remove(Condition);
			}
		}
	}
	return GlobalFailConditions;
}

FText UFlareQuest::GetQuestFailure()
{
	FString Result;

	TArray<UFlareQuestCondition*> FailConditions;
	// TODO: uncomment if you want failure to be constant instead of exact

	// TODO: comment if you want failure to be constant instead of exact
	if(CurrentStep)
	{
		for(auto Condition: CurrentStep->GetFailCondition()->GetAllConditions())
		{
			FailConditions.AddUnique(Condition);
		}
	}
	else
	{
		FailConditions = GetGlobalFailConditions();
	}

	for (auto Condition : FailConditions)
	{
		if (Result.Len())
		{
			Result += "\n";
		}
		Result += LOCTEXT("QuestFailureSymbol", "\u2022 ").ToString() + Condition->GetInitialLabel().ToString();
	}

	if (Result.Len())
	{
		return FText::FromString(Result);
	}
	else
	{
		return LOCTEXT("QuestFailureNone", "\u2022 This contract cannot fail.");
	}
}

FName UFlareQuest::GetQuestNotificationTag() const
{
	return FName(*(FString("quest-") + GetIdentifier().ToString() + "-message"));
}

FText UFlareQuest::GetStatusText() const
{
	switch (QuestStatus)
	{
		case EFlareQuestStatus::PENDING:      return LOCTEXT("QuestPending", "Pending");       break;
		case EFlareQuestStatus::AVAILABLE:    return LOCTEXT("QuestAvailable", "Available");   break;
		case EFlareQuestStatus::ONGOING:       return LOCTEXT("QuestOngoing", "Ongoing");         break;
		case EFlareQuestStatus::SUCCESSFUL:   return LOCTEXT("QuestCompleted", "Completed");   break;
		case EFlareQuestStatus::ABANDONED:    return LOCTEXT("QuestAbandoned", "Abandoned");   break;
		case EFlareQuestStatus::FAILED:       return LOCTEXT("QuestFailed", "Failed");         break;
		default:                              return LOCTEXT("QuestUnknown", "Unknown");       break;
	}
}

#undef LOCTEXT_NAMESPACE


PdmUiFieldHandle* PdmFieldHandle::uiCapability()
{
    PdmUiFieldHandle* uiField = capability<PdmUiFieldHandle>();
    CAF_ASSERT( uiField );

    return uiField;
}

#include "cafPdmUiFieldHandle.h"

#include "cafAssert.h"
#include "cafPdmFieldHandle.h"
#include "cafPdmUiEditorHandle.h"
#include "cafPdmUiModelChangeDetector.h"
#include "cafPdmUiObjectHandle.h"

namespace caf
{
PdmUiFieldHandle::PdmUiFieldHandle( PdmFieldHandle* owner, bool giveOwnership )
    : m_isAutoAddingOptionFromValue( true )
{
    m_owner = owner;
    owner->addCapability( this, giveOwnership );
}

PdmUiFieldHandle::~PdmUiFieldHandle()
{
}

caf::PdmFieldHandle* PdmUiFieldHandle::fieldHandle()
{
    return m_owner;
}

QVariant PdmUiFieldHandle::uiValue() const
{
    return QVariant();
}

QList<caf::PdmOptionItemInfo> PdmUiFieldHandle::valueOptions( bool* useOptionsOnly ) const
{
    return QList<PdmOptionItemInfo>();
}

void PdmUiFieldHandle::notifyFieldChanged( const QVariant& oldFieldValue, const QVariant& newFieldValue )
{
    if ( !this->isQVariantDataEqual( oldFieldValue, newFieldValue ) )
    {
        PdmFieldHandle* fieldHandle = this->fieldHandle();
        CAF_ASSERT( fieldHandle && fieldHandle->ownerObject() );

        PdmObjectHandle* ownerObjectHandle = fieldHandle->ownerObject();

        {
            bool noOwnerObject = true;

            // Object editors

            PdmUiObjectHandle* uiObjHandle = uiObj( ownerObjectHandle );
            if ( uiObjHandle )
            {
                uiObjHandle->fieldChangedByUi( fieldHandle, oldFieldValue, newFieldValue );
                uiObjHandle->updateConnectedEditors();

                noOwnerObject = false;
            }

            // Field editors

            for ( const auto& editorForThisField : m_editors )
            {
                PdmUiEditorHandle* editorContainingThisField = editorForThisField->topMostContainingEditor();

                bool editorContainingThisFieldIsNotUpdated = !uiObjHandle->hasEditor( editorContainingThisField );

                if ( noOwnerObject || editorContainingThisFieldIsNotUpdated )
                {
                    editorContainingThisField->updateUi();
                }
            }
        }

        if ( ownerObjectHandle->parentField() && ownerObjectHandle->parentField()->ownerObject() )
        {
            PdmUiObjectHandle* uiObjHandle = uiObj( ownerObjectHandle->parentField()->ownerObject() );
            if ( uiObjHandle )
            {
                uiObjHandle->childFieldChangedByUi( ownerObjectHandle->parentField() );

                // If updateConnectedEditors() is required, this has to be called in childFieldChangedByUi()
            }
        }

        PdmUiModelChangeDetector::instance()->setModelChanged();
    }
}

bool PdmUiFieldHandle::isAutoAddingOptionFromValue() const
{
    return m_isAutoAddingOptionFromValue;
}

void PdmUiFieldHandle::setAutoAddingOptionFromValue( bool isAddingValue )
{
    m_isAutoAddingOptionFromValue = isAddingValue;
}

void PdmUiFieldHandle::setValueFromUiEditor( const QVariant& uiValue )
{
}

bool PdmUiFieldHandle::isQVariantDataEqual( const QVariant& oldUiBasedQVariant, const QVariant& newUiBasedQVariant ) const
{
    CAF_ASSERT( false );
    return false;
}

/// Implementation of uiCapability() defined in cafPdmFieldHandle.h


std::optional<bool> AdaptiveCard::GetRtl() const
{
    return m_rtl;
}

#include "pch.h"
#include "SharedAdaptiveCard.h"
#include "ParseUtil.h"
#include "Util.h"
#include "ShowCardAction.h"
#include "TextBlock.h"
#include "AdaptiveCardParseWarning.h"
#include "SemanticVersion.h"
#include "ParseContext.h"
#include "BackgroundImage.h"

using namespace AdaptiveCards;

AdaptiveCard::AdaptiveCard() :
    AdaptiveCard("", "", std::shared_ptr<BackgroundImage>(), ContainerStyle::None, "", "", VerticalContentAlignment::Top, HeightType::Auto, 0)
{
}

AdaptiveCard::AdaptiveCard(std::string const& version,
                           std::string const& fallbackText,
                           std::string const& backgroundImageUrl,
                           ContainerStyle style,
                           std::string const& speak,
                           std::string const& language,
                           VerticalContentAlignment verticalContentAlignment,
                           HeightType height,
                           unsigned int minHeight) :
    AdaptiveCard(version, fallbackText, std::make_shared<BackgroundImage>(backgroundImageUrl), style, speak, language, verticalContentAlignment, height, minHeight)
{
}

AdaptiveCard::AdaptiveCard(std::string const& version,
                           std::string const& fallbackText,
                           std::string const& backgroundImageUrl,
                           ContainerStyle style,
                           std::string const& speak,
                           std::string const& language,
                           VerticalContentAlignment verticalContentAlignment,
                           HeightType height,
                           unsigned int minHeight,
                           std::vector<std::shared_ptr<BaseCardElement>>& body,
                           std::vector<std::shared_ptr<BaseActionElement>>& actions) :
    AdaptiveCard(version, fallbackText, std::make_shared<BackgroundImage>(backgroundImageUrl), style, speak, language, verticalContentAlignment, height, minHeight, body, actions)
{
}

AdaptiveCard::AdaptiveCard(std::string const& version,
                           std::string const& fallbackText,
                           std::shared_ptr<BackgroundImage> backgroundImage,
                           ContainerStyle style,
                           std::string const& speak,
                           std::string const& language,
                           VerticalContentAlignment verticalContentAlignment,
                           HeightType height,
                           unsigned int minHeight) :
    m_version(version),
    m_fallbackText(fallbackText), m_backgroundImage(backgroundImage), m_speak(speak), m_style(style),
    m_language(language), m_verticalContentAlignment(verticalContentAlignment), m_height(height),
    m_minHeight(minHeight), m_internalId{InternalId::Next()}, m_additionalProperties{}
{
    PopulateKnownPropertiesSet();
}

AdaptiveCard::AdaptiveCard(std::string const& version,
                           std::string const& fallbackText,
                           std::shared_ptr<BackgroundImage> backgroundImage,
                           ContainerStyle style,
                           std::string const& speak,
                           std::string const& language,
                           VerticalContentAlignment verticalContentAlignment,
                           HeightType height,
                           unsigned int minHeight,
                           std::vector<std::shared_ptr<BaseCardElement>>& body,
                           std::vector<std::shared_ptr<BaseActionElement>>& actions) :
    AdaptiveCard(version,
                 fallbackText,
                 backgroundImage,
                 std::shared_ptr<Refresh>(),
                 std::shared_ptr<Authentication>(),
                 style,
                 speak,
                 language,
                 verticalContentAlignment,
                 height,
                 minHeight,
                 body,
                 actions)
{
}

AdaptiveCard::AdaptiveCard(std::string const& version,
                           std::string const& fallbackText,
                           std::shared_ptr<BackgroundImage> backgroundImage,
                           std::shared_ptr<Refresh> refresh,
                           std::shared_ptr<Authentication> authentication,
                           ContainerStyle style,
                           std::string const& speak,
                           std::string const& language,
                           VerticalContentAlignment verticalContentAlignment,
                           HeightType height,
                           unsigned int minHeight,
                           std::vector<std::shared_ptr<BaseCardElement>>& body,
                           std::vector<std::shared_ptr<BaseActionElement>>& actions) :
    m_version(version),
    m_fallbackText(fallbackText), m_backgroundImage(backgroundImage), m_refresh(refresh),
    m_authentication(authentication), m_speak(speak), m_style(style), m_language(language),
    m_verticalContentAlignment(verticalContentAlignment), m_height(height),
    m_minHeight(minHeight), m_internalId{InternalId::Next()}, m_body(body), m_actions(actions), m_additionalProperties{}
{
    PopulateKnownPropertiesSet();
}

#ifdef __ANDROID__
std::shared_ptr<ParseResult> AdaptiveCard::DeserializeFromFile(const std::string& jsonFile,
                                                               std::string rendererVersion) throw(AdaptiveCards::AdaptiveCardParseException)
#else
std::shared_ptr<ParseResult> AdaptiveCard::DeserializeFromFile(const std::string& jsonFile, const std::string& rendererVersion)
#endif // __ANDROID__
{
    ParseContext context;
    return AdaptiveCard::DeserializeFromFile(jsonFile, rendererVersion, context);
}

#ifdef __ANDROID__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdynamic-exception-spec"
std::shared_ptr<ParseResult> AdaptiveCard::DeserializeFromFile(const std::string& jsonFile,
                                                               std::string rendererVersion,
                                                               ParseContext& context) throw(AdaptiveCards::AdaptiveCardParseException)
#else
std::shared_ptr<ParseResult> AdaptiveCard::DeserializeFromFile(const std::string& jsonFile,
                                                               const std::string& rendererVersion,
                                                               ParseContext& context)
#endif // __ANDROID__
{
    std::ifstream jsonFileStream(jsonFile);

    Json::Value root;
    jsonFileStream >> root;

    return AdaptiveCard::Deserialize(root, rendererVersion, context);
}

void AdaptiveCard::_ValidateLanguage(const std::string& language, std::vector<std::shared_ptr<AdaptiveCardParseWarning>>& warnings)
{
    try
    {
        if (language.empty() || language.length() == 2 || language.length() == 3)
        {
            auto locale = std::locale(language.c_str());
        }
        else
        {
            warnings.push_back(std::make_shared<AdaptiveCardParseWarning>(AdaptiveCards::WarningStatusCode::InvalidLanguage,
                                                                          "Invalid language identifier: " + language));
        }
    }
    catch (std::runtime_error)
    {
        warnings.push_back(std::make_shared<AdaptiveCardParseWarning>(AdaptiveCards::WarningStatusCode::InvalidLanguage,
                                                                      "Invalid language identifier: " + language));
    }
}

#ifdef __ANDROID__
std::shared_ptr<ParseResult> AdaptiveCard::Deserialize(const Json::Value& json,
                                                       std::string rendererVersion,
                                                       ParseContext& context) throw(AdaptiveCards::AdaptiveCardParseException)
#else
std::shared_ptr<ParseResult> AdaptiveCard::Deserialize(const Json::Value& json, const std::string& rendererVersion, ParseContext& context)
#endif // __ANDROID__
{
    ParseUtil::ThrowIfNotJsonObject(json);

    const bool enforceVersion = !rendererVersion.empty();

    // Verify this is an adaptive card
    ParseUtil::ExpectTypeString(json, CardElementType::AdaptiveCard);

    std::string version = ParseUtil::GetString(json, AdaptiveCardSchemaKey::Version, enforceVersion);
    std::string fallbackText = ParseUtil::GetString(json, AdaptiveCardSchemaKey::FallbackText);
    std::string language = ParseUtil::GetString(json, AdaptiveCardSchemaKey::Language);
    std::string speak = ParseUtil::GetString(json, AdaptiveCardSchemaKey::Speak);

    // check if language is valid
    _ValidateLanguage(language, context.warnings);

    if (language.size())
    {
        context.SetLanguage(language);
    }
    else
    {
        language = context.GetLanguage();
    }

    // Perform version validation
    if (enforceVersion)
    {
        const SemanticVersion rendererMaxVersion(rendererVersion);
        const SemanticVersion cardVersion(version);

        if (rendererVersion < cardVersion)
        {
            if (fallbackText.empty())
            {
                fallbackText = "We're sorry, this card couldn't be displayed";
            }

            if (speak.empty())
            {
                speak = fallbackText;
            }

            context.warnings.push_back(std::make_shared<AdaptiveCardParseWarning>(AdaptiveCards::WarningStatusCode::UnsupportedSchemaVersion,
                                                                                "Schema version not supported"));
            return std::make_shared<ParseResult>(MakeFallbackTextCard(fallbackText, language, speak), context.warnings);
        }
    }

    auto backgroundImage =
        ParseUtil::DeserializeValue<BackgroundImage>(json, AdaptiveCardSchemaKey::BackgroundImage, BackgroundImage::Deserialize);
    auto refresh = ParseUtil::DeserializeValue<Refresh>(context, json, AdaptiveCardSchemaKey::Refresh, Refresh::Deserialize);
    auto authentication =
        ParseUtil::DeserializeValue<Authentication>(context, json, AdaptiveCardSchemaKey::Authentication, Authentication::Deserialize);

    ContainerStyle style =
        ParseUtil::GetEnumValue<ContainerStyle>(json, AdaptiveCardSchemaKey::Style, ContainerStyle::None, ContainerStyleFromString);
    context.SetParentalContainerStyle(style);

    VerticalContentAlignment verticalContentAlignment =
        ParseUtil::GetEnumValue<VerticalContentAlignment>(json,
                                                          AdaptiveCardSchemaKey::VerticalContentAlignment,
                                                          VerticalContentAlignment::Top,
                                                          VerticalContentAlignmentFromString);
    HeightType height =
        ParseUtil::GetEnumValue<HeightType>(json, AdaptiveCardSchemaKey::Height, HeightType::Auto, HeightTypeFromString);

    unsigned int minHeight =
        ParseSizeForPixelSize(ParseUtil::GetString(json, AdaptiveCardSchemaKey::MinHeight), &context.warnings).value_or(0);

    // Parse body
    auto body = ParseUtil::GetElementCollection<BaseCardElement>(true, context, json, AdaptiveCardSchemaKey::Body, false);
    // Parse actions if present
    auto actions = ParseUtil::GetActionCollection(context, json, AdaptiveCardSchemaKey::Actions, false);

    EnsureShowCardVersions(actions, version);

    auto result = std::make_shared<AdaptiveCard>(
        version, fallbackText, backgroundImage, refresh, authentication, style, speak, language, verticalContentAlignment, height, minHeight, body, actions);
    result->SetLanguage(language);
    result->SetRtl(ParseUtil::GetOptionalBool(json, AdaptiveCardSchemaKey::Rtl));

    // Parse optional selectAction
    result->SetSelectAction(ParseUtil::GetAction(context, json, AdaptiveCardSchemaKey::SelectAction, false));

    Json::Value additionalProperties;
    HandleUnknownProperties(json, result->GetKnownProperties(), additionalProperties);
    result->SetAdditionalProperties(additionalProperties);

    return std::make_shared<ParseResult>(result, context.warnings);
}

#ifdef __ANDROID__
std::shared_ptr<ParseResult> AdaptiveCard::DeserializeFromString(const std::string& jsonString,
                                                                 std::string rendererVersion) throw(AdaptiveCards::AdaptiveCardParseException)
#else
std::shared_ptr<ParseResult> AdaptiveCard::DeserializeFromString(const std::string& jsonString, const std::string& rendererVersion)
#endif // __ANDROID__
{
    ParseContext context;
    return AdaptiveCard::DeserializeFromString(jsonString, rendererVersion, context);
}

#ifdef __ANDROID__
std::shared_ptr<ParseResult> AdaptiveCard::DeserializeFromString(const std::string& jsonString,
                                                                 std::string rendererVersion,
                                                                 ParseContext& context) throw(AdaptiveCards::AdaptiveCardParseException)
#else
std::shared_ptr<ParseResult> AdaptiveCard::DeserializeFromString(const std::string& jsonString,
                                                                 const std::string& rendererVersion,
                                                                 ParseContext& context)
#endif // __ANDROID__
{
    return AdaptiveCard::Deserialize(ParseUtil::GetJsonValueFromString(jsonString), rendererVersion, context);
}

Json::Value AdaptiveCard::SerializeToJsonValue() const
{
    Json::Value root = GetAdditionalProperties();
    root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Type)] = CardElementTypeToString(CardElementType::AdaptiveCard);

    if (!m_version.empty())
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Version)] = m_version;
    }
    else
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Version)] = "1.0";
    }

    if (!m_fallbackText.empty())
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::FallbackText)] = m_fallbackText;
    }
    if (m_backgroundImage != nullptr && m_backgroundImage->ShouldSerialize())
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::BackgroundImage)] = m_backgroundImage->SerializeToJsonValue();
    }
    if (m_refresh != nullptr && m_refresh->ShouldSerialize())
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Refresh)] = m_refresh->SerializeToJsonValue();
    }
    if (m_authentication != nullptr && m_authentication->ShouldSerialize())
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Authentication)] = m_authentication->SerializeToJsonValue();
    }
    if (!m_speak.empty())
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Speak)] = m_speak;
    }
    if (!m_language.empty())
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Language)] = m_language;
    }
    if (m_style != ContainerStyle::None)
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Style)] = ContainerStyleToString(m_style);
    }
    if (m_verticalContentAlignment != VerticalContentAlignment::Top)
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::VerticalContentAlignment)] =
            VerticalContentAlignmentToString(m_verticalContentAlignment);
    }

    if (m_minHeight)
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::MinHeight)] = std::to_string(GetMinHeight()) + "px";
    }

    if (m_rtl.has_value())
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Rtl)] = m_rtl.value_or("");
    }

    const HeightType height = GetHeight();
    if (height != HeightType::Auto)
    {
        root[AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Height)] = HeightTypeToString(GetHeight());
    }

    const std::string& bodyPropertyName = AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Body);
    root[bodyPropertyName] = Json::Value(Json::arrayValue);
    for (const auto& cardElement : GetBody())
    {
        root[bodyPropertyName].append(cardElement->SerializeToJsonValue());
    }

    const std::string& actionsPropertyName = AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Actions);
    root[actionsPropertyName] = Json::Value(Json::arrayValue);
    for (const auto& action : GetActions())
    {
        root[actionsPropertyName].append(action->SerializeToJsonValue());
    }

    return root;
}

#ifdef __ANDROID__
std::shared_ptr<AdaptiveCard> AdaptiveCard::MakeFallbackTextCard(const std::string& fallbackText,
                                                                 const std::string& language,
                                                                 const std::string& speak) throw(AdaptiveCards::AdaptiveCardParseException)
#else
std::shared_ptr<AdaptiveCard> AdaptiveCard::MakeFallbackTextCard(const std::string& fallbackText,
                                                                 const std::string& language,
                                                                 const std::string& speak)
#endif // __ANDROID__
{
    std::shared_ptr<AdaptiveCard> fallbackCard = std::make_shared<AdaptiveCard>(
        "1.0", fallbackText, "", ContainerStyle::Default, speak, language, VerticalContentAlignment::Top, HeightType::Auto, 0);

    std::shared_ptr<TextBlock> textBlock = std::make_shared<TextBlock>();
    textBlock->SetText(fallbackText);
    textBlock->SetLanguage(language);

    fallbackCard->GetBody().push_back(textBlock);

    return fallbackCard;
}

std::string AdaptiveCard::Serialize() const
{
    return ParseUtil::JsonToString(SerializeToJsonValue());
}

std::string AdaptiveCard::GetVersion() const
{
    return m_version;
}

void AdaptiveCard::SetVersion(const std::string& value)
{
    m_version = value;
}

std::string AdaptiveCard::GetFallbackText() const
{
    return m_fallbackText;
}

void AdaptiveCard::SetFallbackText(const std::string& value)
{
    m_fallbackText = value;
}

std::shared_ptr<BackgroundImage> AdaptiveCard::GetBackgroundImage() const
{
    return m_backgroundImage;
}

void AdaptiveCard::SetBackgroundImage(const std::shared_ptr<BackgroundImage> value)
{
    m_backgroundImage = value;
}

std::shared_ptr<Refresh> AdaptiveCard::GetRefresh() const
{
    return m_refresh;
}

void AdaptiveCard::SetRefresh(const std::shared_ptr<Refresh> value)
{
    m_refresh = value;
}

std::shared_ptr<Authentication> AdaptiveCard::GetAuthentication() const
{
    return m_authentication;
}

void AdaptiveCard::SetAuthentication(const std::shared_ptr<Authentication> value)
{
    m_authentication = value;
}

std::string AdaptiveCard::GetSpeak() const
{
    return m_speak;
}

void AdaptiveCard::SetSpeak(const std::string& value)
{
    m_speak = value;
}

ContainerStyle AdaptiveCard::GetStyle() const
{
    return m_style;
}

void AdaptiveCard::SetStyle(const ContainerStyle value)
{
    m_style = value;
}

const std::string& AdaptiveCard::GetLanguage() const
{
    return m_language;
}

void AdaptiveCard::SetLanguage(const std::string& value)
{
    m_language = value;
}

HeightType AdaptiveCard::GetHeight() const
{
    return m_height;
}

void AdaptiveCard::SetHeight(const HeightType value)
{
    m_height = value;
}

CardElementType AdaptiveCard::GetElementType() const
{
    return CardElementType::AdaptiveCard;
}

std::vector<std::shared_ptr<BaseCardElement>>& AdaptiveCard::GetBody()
{
    return m_body;
}

const std::vector<std::shared_ptr<BaseCardElement>>& AdaptiveCard::GetBody() const
{
    return m_body;
}

std::vector<std::shared_ptr<BaseActionElement>>& AdaptiveCard::GetActions()
{
    return m_actions;
}

const std::vector<std::shared_ptr<BaseActionElement>>& AdaptiveCard::GetActions() const
{
    return m_actions;
}

std::shared_ptr<BaseActionElement> AdaptiveCard::GetSelectAction() const
{
    return m_selectAction;
}

void AdaptiveCard::SetSelectAction(const std::shared_ptr<BaseActionElement> action)
{
    m_selectAction = action;
}

VerticalContentAlignment AdaptiveCard::GetVerticalContentAlignment() const
{
    return m_verticalContentAlignment;
}

void AdaptiveCard::SetVerticalContentAlignment(const VerticalContentAlignment value)
{
    m_verticalContentAlignment = value;
}

unsigned int AdaptiveCard::GetMinHeight() const
{
    return m_minHeight;
}

void AdaptiveCard::SetMinHeight(const unsigned int value)
{
    m_minHeight = value;
}

// value is present if and only if "rtl" property is explicitly set

void AdaptiveCard::SetRtl(const std::optional<bool>& value)
{
    m_rtl = value;
}

void AdaptiveCard::PopulateKnownPropertiesSet()
{
    m_knownProperties.insert({AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Type),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Version),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Body),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Actions),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::FallbackText),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::BackgroundImage),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Refresh),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Authentication),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::MinHeight),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Speak),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Language),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::VerticalContentAlignment),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Style),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::SelectAction),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Height),
                              AdaptiveCardSchemaKeyToString(AdaptiveCardSchemaKey::Schema)});
}

const std::unordered_set<std::string>& AdaptiveCard::GetKnownProperties() const
{
    return m_knownProperties;
}

const Json::Value& AdaptiveCard::GetAdditionalProperties() const
{
    return m_additionalProperties;
}

void AdaptiveCard::SetAdditionalProperties(Json::Value&& value)
{
    m_additionalProperties = std::move(value);
}
void AdaptiveCard::SetAdditionalProperties(const Json::Value& value)
{
    m_additionalProperties = value;
}

std::vector<RemoteResourceInformation> AdaptiveCard::GetResourceInformation()
{
    auto resourceVector = std::vector<RemoteResourceInformation>();

    auto backgroundImage = GetBackgroundImage();
    if (backgroundImage != nullptr)
    {
        RemoteResourceInformation backgroundImageInfo;
        backgroundImageInfo.url = backgroundImage->GetUrl();
        backgroundImageInfo.mimeType = "image";
        resourceVector.push_back(backgroundImageInfo);
    }

    for (auto item : m_body)
    {
        item->GetResourceInformation(resourceVector);
    }

    for (auto item : m_actions)
    {
        item->GetResourceInformation(resourceVector);
    }

    return resourceVector;
}


static inline int KeyFromString(const char *charBytes, size_t len) {
	PLATFORM_ASSERT(len <= 4);
	int k=0;
	for (size_t i=0; i<len && charBytes[i]; i++) {
		k = k * 0x100;
		k += static_cast<unsigned char>(charBytes[i]);
	}
	return k;
}

// Scintilla source code edit control
/** @file PositionCache.cxx
 ** Classes for caching layout information.
 **/

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>

#include <string>
#include <vector>
#include <map>
#include <algorithm>

#include "Platform.h"

#include "Scintilla.h"

#include "SplitVector.h"
#include "Partitioning.h"
#include "RunStyles.h"
#include "ContractionState.h"
#include "CellBuffer.h"
#include "KeyMap.h"
#include "Indicator.h"
#include "XPM.h"
#include "LineMarker.h"
#include "Style.h"
#include "ViewStyle.h"
#include "CharClassify.h"
#include "Decoration.h"
#include "ILexer.h"
#include "CaseFolder.h"
#include "Document.h"
#include "UniConversion.h"
#include "Selection.h"
#include "PositionCache.h"

#ifdef SCI_NAMESPACE
using namespace Scintilla;
#endif

static inline bool IsControlCharacter(int ch) {
	// iscntrl returns true for lots of chars > 127 which are displayable
	return ch >= 0 && ch < ' ';
}

LineLayout::LineLayout(int maxLineLength_) :
	lineStarts(0),
	lenLineStarts(0),
	lineNumber(-1),
	inCache(false),
	maxLineLength(-1),
	numCharsInLine(0),
	numCharsBeforeEOL(0),
	validity(llInvalid),
	xHighlightGuide(0),
	highlightColumn(0),
	psel(NULL),
	containsCaret(false),
	edgeColumn(0),
	chars(0),
	styles(0),
	styleBitsSet(0),
	indicators(0),
	positions(0),
	hsStart(0),
	hsEnd(0),
	widthLine(wrapWidthInfinite),
	lines(1),
	wrapIndent(0) {
	bracePreviousStyles[0] = 0;
	bracePreviousStyles[1] = 0;
	Resize(maxLineLength_);
}

LineLayout::~LineLayout() {
	Free();
}

void LineLayout::Resize(int maxLineLength_) {
	if (maxLineLength_ > maxLineLength) {
		Free();
		chars = new char[maxLineLength_ + 1];
		styles = new unsigned char[maxLineLength_ + 1];
		indicators = new char[maxLineLength_ + 1];
		// Extra position allocated as sometimes the Windows
		// GetTextExtentExPoint API writes an extra element.
		positions = new XYPOSITION[maxLineLength_ + 1 + 1];
		maxLineLength = maxLineLength_;
	}
}

void LineLayout::Free() {
	delete []chars;
	chars = 0;
	delete []styles;
	styles = 0;
	delete []indicators;
	indicators = 0;
	delete []positions;
	positions = 0;
	delete []lineStarts;
	lineStarts = 0;
}

void LineLayout::Invalidate(validLevel validity_) {
	if (validity > validity_)
		validity = validity_;
}

int LineLayout::LineStart(int line) const {
	if (line <= 0) {
		return 0;
	} else if ((line >= lines) || !lineStarts) {
		return numCharsInLine;
	} else {
		return lineStarts[line];
	}
}

int LineLayout::LineLastVisible(int line) const {
	if (line < 0) {
		return 0;
	} else if ((line >= lines-1) || !lineStarts) {
		return numCharsBeforeEOL;
	} else {
		return lineStarts[line+1];
	}
}

bool LineLayout::InLine(int offset, int line) const {
	return ((offset >= LineStart(line)) && (offset < LineStart(line + 1))) ||
		((offset == numCharsInLine) && (line == (lines-1)));
}

void LineLayout::SetLineStart(int line, int start) {
	if ((line >= lenLineStarts) && (line != 0)) {
		int newMaxLines = line + 20;
		int *newLineStarts = new int[newMaxLines];
		for (int i = 0; i < newMaxLines; i++) {
			if (i < lenLineStarts)
				newLineStarts[i] = lineStarts[i];
			else
				newLineStarts[i] = 0;
		}
		delete []lineStarts;
		lineStarts = newLineStarts;
		lenLineStarts = newMaxLines;
	}
	lineStarts[line] = start;
}

void LineLayout::SetBracesHighlight(Range rangeLine, Position braces[],
                                    char bracesMatchStyle, int xHighlight, bool ignoreStyle) {
	if (!ignoreStyle && rangeLine.ContainsCharacter(braces[0])) {
		int braceOffset = braces[0] - rangeLine.start;
		if (braceOffset < numCharsInLine) {
			bracePreviousStyles[0] = styles[braceOffset];
			styles[braceOffset] = bracesMatchStyle;
		}
	}
	if (!ignoreStyle && rangeLine.ContainsCharacter(braces[1])) {
		int braceOffset = braces[1] - rangeLine.start;
		if (braceOffset < numCharsInLine) {
			bracePreviousStyles[1] = styles[braceOffset];
			styles[braceOffset] = bracesMatchStyle;
		}
	}
	if ((braces[0] >= rangeLine.start && braces[1] <= rangeLine.end) ||
	        (braces[1] >= rangeLine.start && braces[0] <= rangeLine.end)) {
		xHighlightGuide = xHighlight;
	}
}

void LineLayout::RestoreBracesHighlight(Range rangeLine, Position braces[], bool ignoreStyle) {
	if (!ignoreStyle && rangeLine.ContainsCharacter(braces[0])) {
		int braceOffset = braces[0] - rangeLine.start;
		if (braceOffset < numCharsInLine) {
			styles[braceOffset] = bracePreviousStyles[0];
		}
	}
	if (!ignoreStyle && rangeLine.ContainsCharacter(braces[1])) {
		int braceOffset = braces[1] - rangeLine.start;
		if (braceOffset < numCharsInLine) {
			styles[braceOffset] = bracePreviousStyles[1];
		}
	}
	xHighlightGuide = 0;
}

int LineLayout::FindBefore(XYPOSITION x, int lower, int upper) const {
	do {
		int middle = (upper + lower + 1) / 2; 	// Round high
		XYPOSITION posMiddle = positions[middle];
		if (x < posMiddle) {
			upper = middle - 1;
		} else {
			lower = middle;
		}
	} while (lower < upper);
	return lower;
}

int LineLayout::EndLineStyle() const {
	return styles[numCharsBeforeEOL > 0 ? numCharsBeforeEOL-1 : 0];
}

LineLayoutCache::LineLayoutCache() :
	level(0),
	allInvalidated(false), styleClock(-1), useCount(0) {
	Allocate(0);
}

LineLayoutCache::~LineLayoutCache() {
	Deallocate();
}

void LineLayoutCache::Allocate(size_t length_) {
	PLATFORM_ASSERT(cache.empty());
	allInvalidated = false;
	cache.resize(length_);
}

void LineLayoutCache::AllocateForLevel(int linesOnScreen, int linesInDoc) {
	PLATFORM_ASSERT(useCount == 0);
	size_t lengthForLevel = 0;
	if (level == llcCaret) {
		lengthForLevel = 1;
	} else if (level == llcPage) {
		lengthForLevel = linesOnScreen + 1;
	} else if (level == llcDocument) {
		lengthForLevel = linesInDoc;
	}
	if (lengthForLevel > cache.size()) {
		Deallocate();
		Allocate(lengthForLevel);
	} else {
		if (lengthForLevel < cache.size()) {
			for (size_t i = lengthForLevel; i < cache.size(); i++) {
				delete cache[i];
				cache[i] = 0;
			}
		}
		cache.resize(lengthForLevel);
	}
	PLATFORM_ASSERT(cache.size() == lengthForLevel);
}

void LineLayoutCache::Deallocate() {
	PLATFORM_ASSERT(useCount == 0);
	for (size_t i = 0; i < cache.size(); i++)
		delete cache[i];
	cache.clear();
}

void LineLayoutCache::Invalidate(LineLayout::validLevel validity_) {
	if (!cache.empty() && !allInvalidated) {
		for (size_t i = 0; i < cache.size(); i++) {
			if (cache[i]) {
				cache[i]->Invalidate(validity_);
			}
		}
		if (validity_ == LineLayout::llInvalid) {
			allInvalidated = true;
		}
	}
}

void LineLayoutCache::SetLevel(int level_) {
	allInvalidated = false;
	if ((level_ != -1) && (level != level_)) {
		level = level_;
		Deallocate();
	}
}

LineLayout *LineLayoutCache::Retrieve(int lineNumber, int lineCaret, int maxChars, int styleClock_,
                                      int linesOnScreen, int linesInDoc) {
	AllocateForLevel(linesOnScreen, linesInDoc);
	if (styleClock != styleClock_) {
		Invalidate(LineLayout::llCheckTextAndStyle);
		styleClock = styleClock_;
	}
	allInvalidated = false;
	int pos = -1;
	LineLayout *ret = 0;
	if (level == llcCaret) {
		pos = 0;
	} else if (level == llcPage) {
		if (lineNumber == lineCaret) {
			pos = 0;
		} else if (cache.size() > 1) {
			pos = 1 + (lineNumber % (cache.size() - 1));
		}
	} else if (level == llcDocument) {
		pos = lineNumber;
	}
	if (pos >= 0) {
		PLATFORM_ASSERT(useCount == 0);
		if (!cache.empty() && (pos < static_cast<int>(cache.size()))) {
			if (cache[pos]) {
				if ((cache[pos]->lineNumber != lineNumber) ||
				        (cache[pos]->maxLineLength < maxChars)) {
					delete cache[pos];
					cache[pos] = 0;
				}
			}
			if (!cache[pos]) {
				cache[pos] = new LineLayout(maxChars);
			}
			cache[pos]->lineNumber = lineNumber;
			cache[pos]->inCache = true;
			ret = cache[pos];
			useCount++;
		}
	}

	if (!ret) {
		ret = new LineLayout(maxChars);
		ret->lineNumber = lineNumber;
	}

	return ret;
}

void LineLayoutCache::Dispose(LineLayout *ll) {
	allInvalidated = false;
	if (ll) {
		if (!ll->inCache) {
			delete ll;
		} else {
			useCount--;
		}
	}
}

// Simply pack the (maximum 4) character bytes into an int

SpecialRepresentations::SpecialRepresentations() {
	std::fill(startByteHasReprs, startByteHasReprs+0x100, 0);
}

void SpecialRepresentations::SetRepresentation(const char *charBytes, const char *value) {
	MapRepresentation::iterator it = mapReprs.find(KeyFromString(charBytes, UTF8MaxBytes));
	if (it == mapReprs.end()) {
		// New entry so increment for first byte
		startByteHasReprs[static_cast<unsigned char>(charBytes[0])]++;
	}
	mapReprs[KeyFromString(charBytes, UTF8MaxBytes)] = value;
}

void SpecialRepresentations::ClearRepresentation(const char *charBytes) {
	MapRepresentation::iterator it = mapReprs.find(KeyFromString(charBytes, UTF8MaxBytes));
	if (it != mapReprs.end()) {
		mapReprs.erase(it);
		startByteHasReprs[static_cast<unsigned char>(charBytes[0])]--;
	}
}

Representation *SpecialRepresentations::RepresentationFromCharacter(const char *charBytes, size_t len) {
	PLATFORM_ASSERT(len <= 4);
	if (!startByteHasReprs[static_cast<unsigned char>(charBytes[0])])
		return 0;
	MapRepresentation::iterator it = mapReprs.find(KeyFromString(charBytes, len));
	if (it != mapReprs.end()) {
		return &(it->second);
	}
	return 0;
}

bool SpecialRepresentations::Contains(const char *charBytes, size_t len) const {
	PLATFORM_ASSERT(len <= 4);
	if (!startByteHasReprs[static_cast<unsigned char>(charBytes[0])])
		return false;
	MapRepresentation::const_iterator it = mapReprs.find(KeyFromString(charBytes, len));
	return it != mapReprs.end();
}

void SpecialRepresentations::Clear() {
	mapReprs.clear();
	std::fill(startByteHasReprs, startByteHasReprs+0x100, 0);
}

void BreakFinder::Insert(int val) {
	if (val > nextBreak) {
		const std::vector<int>::iterator it = std::lower_bound(selAndEdge.begin(), selAndEdge.end(), val);
		if (it == selAndEdge.end()) {
			selAndEdge.push_back(val);
		} else if (*it != val) {
			selAndEdge.insert(it, 1, val);
		}
	}
}

BreakFinder::BreakFinder(LineLayout *ll_, int lineStart_, int lineEnd_, int posLineStart_,
	int xStart, bool breakForSelection, Document *pdoc_, SpecialRepresentations *preprs_) :
	ll(ll_),
	lineStart(lineStart_),
	lineEnd(lineEnd_),
	posLineStart(posLineStart_),
	nextBreak(lineStart_),
	saeCurrentPos(0),
	saeNext(0),
	subBreak(-1),
	pdoc(pdoc_),
	encodingFamily(pdoc_->CodePageFamily()),
	preprs(preprs_) {

	// Search for first visible break
	// First find the first visible character
	if (xStart > 0.0f)
		nextBreak = ll->FindBefore(xStart, lineStart, lineEnd);
	// Now back to a style break
	while ((nextBreak > lineStart) && (ll->styles[nextBreak] == ll->styles[nextBreak - 1])) {
		nextBreak--;
	}

	if (breakForSelection) {
		SelectionPosition posStart(posLineStart);
		SelectionPosition posEnd(posLineStart + lineEnd);
		SelectionSegment segmentLine(posStart, posEnd);
		for (size_t r=0; r<ll->psel->Count(); r++) {
			SelectionSegment portion = ll->psel->Range(r).Intersect(segmentLine);
			if (!(portion.start == portion.end)) {
				if (portion.start.IsValid())
					Insert(portion.start.Position() - posLineStart);
				if (portion.end.IsValid())
					Insert(portion.end.Position() - posLineStart);
			}
		}
	}

	Insert(ll->edgeColumn);
	Insert(lineEnd);
	saeNext = (!selAndEdge.empty()) ? selAndEdge[0] : -1;
}

BreakFinder::~BreakFinder() {
}

TextSegment BreakFinder::Next() {
	if (subBreak == -1) {
		int prev = nextBreak;
 		while (nextBreak < lineEnd) {
			int charWidth = 1;
			if (encodingFamily == efUnicode)
				charWidth = UTF8DrawBytes(reinterpret_cast<unsigned char *>(ll->chars) + nextBreak, lineEnd - nextBreak);
			else if (encodingFamily == efDBCS)
				charWidth = pdoc->IsDBCSLeadByte(ll->chars[nextBreak]) ? 2 : 1;
			Representation *repr = preprs->RepresentationFromCharacter(ll->chars + nextBreak, charWidth);
			if (((nextBreak > 0) && (ll->styles[nextBreak] != ll->styles[nextBreak - 1])) ||
					repr ||
					(nextBreak == saeNext)) {
				while ((nextBreak >= saeNext) && (saeNext < lineEnd)) {
					saeCurrentPos++;
					saeNext = (saeCurrentPos < selAndEdge.size()) ? selAndEdge[saeCurrentPos] : lineEnd;
				}
				if ((nextBreak > prev) || repr) {
					// Have a segment to report
					if (nextBreak == prev) {
						nextBreak += charWidth;
					} else {
						repr = 0;	// Optimize -> should remember repr
					}
					if ((nextBreak - prev) < lengthStartSubdivision) {
						return TextSegment(prev, nextBreak - prev, repr);
					} else {
						break;
					}
				}
			}
			nextBreak += charWidth;
		}
		if ((nextBreak - prev) < lengthStartSubdivision) {
			return TextSegment(prev, nextBreak - prev);
		}
		subBreak = prev;
	}
	// Splitting up a long run from prev to nextBreak in lots of approximately lengthEachSubdivision.
	// For very long runs add extra breaks after spaces or if no spaces before low punctuation.
	int startSegment = subBreak;
	if ((nextBreak - subBreak) <= lengthEachSubdivision) {
		subBreak = -1;
		return TextSegment(startSegment, nextBreak - startSegment);
	} else {
		subBreak += pdoc->SafeSegment(ll->chars + subBreak, nextBreak-subBreak, lengthEachSubdivision);
		if (subBreak >= nextBreak) {
			subBreak = -1;
			return TextSegment(startSegment, nextBreak - startSegment);
		} else {
			return TextSegment(startSegment, subBreak - startSegment);
		}
	}
}

bool BreakFinder::More() const {
	return (subBreak >= 0) || (nextBreak < lineEnd);
}

PositionCacheEntry::PositionCacheEntry() :
	styleNumber(0), len(0), clock(0), positions(0) {
}

void PositionCacheEntry::Set(unsigned int styleNumber_, const char *s_,
	unsigned int len_, XYPOSITION *positions_, unsigned int clock_) {
	Clear();
	styleNumber = styleNumber_;
	len = len_;
	clock = clock_;
	if (s_ && positions_) {
		positions = new XYPOSITION[len + (len / 4) + 1];
		for (unsigned int i=0; i<len; i++) {
			positions[i] = static_cast<XYPOSITION>(positions_[i]);
		}
		memcpy(reinterpret_cast<char *>(positions + len), s_, len);
	}
}

PositionCacheEntry::~PositionCacheEntry() {
	Clear();
}

void PositionCacheEntry::Clear() {
	delete []positions;
	positions = 0;
	styleNumber = 0;
	len = 0;
	clock = 0;
}

bool PositionCacheEntry::Retrieve(unsigned int styleNumber_, const char *s_,
	unsigned int len_, XYPOSITION *positions_) const {
	if ((styleNumber == styleNumber_) && (len == len_) &&
		(memcmp(reinterpret_cast<char *>(positions + len), s_, len)== 0)) {
		for (unsigned int i=0; i<len; i++) {
			positions_[i] = positions[i];
		}
		return true;
	} else {
		return false;
	}
}

int PositionCacheEntry::Hash(unsigned int styleNumber_, const char *s, unsigned int len_) {
	unsigned int ret = s[0] << 7;
	for (unsigned int i=0; i<len_; i++) {
		ret *= 1000003;
		ret ^= s[i];
	}
	ret *= 1000003;
	ret ^= len_;
	ret *= 1000003;
	ret ^= styleNumber_;
	return ret;
}

bool PositionCacheEntry::NewerThan(const PositionCacheEntry &other) const {
	return clock > other.clock;
}

void PositionCacheEntry::ResetClock() {
	if (clock > 0) {
		clock = 1;
	}
}

PositionCache::PositionCache() {
	clock = 1;
	pces.resize(0x400);
	allClear = true;
}

PositionCache::~PositionCache() {
	Clear();
}

void PositionCache::Clear() {
	if (!allClear) {
		for (size_t i=0; i<pces.size(); i++) {
			pces[i].Clear();
		}
	}
	clock = 1;
	allClear = true;
}

void PositionCache::SetSize(size_t size_) {
	Clear();
	pces.resize(size_);
}

void PositionCache::MeasureWidths(Surface *surface, ViewStyle &vstyle, unsigned int styleNumber,
	const char *s, unsigned int len, XYPOSITION *positions, Document *pdoc) {

	allClear = false;
	int probe = -1;
	if ((!pces.empty()) && (len < 30)) {
		// Only store short strings in the cache so it doesn't churn with
		// long comments with only a single comment.

		// Two way associative: try two probe positions.
		int hashValue = PositionCacheEntry::Hash(styleNumber, s, len);
		probe = static_cast<int>(hashValue % pces.size());
		if (pces[probe].Retrieve(styleNumber, s, len, positions)) {
			return;
		}
		int probe2 = static_cast<int>((hashValue * 37) % pces.size());
		if (pces[probe2].Retrieve(styleNumber, s, len, positions)) {
			return;
		}
		// Not found. Choose the oldest of the two slots to replace
		if (pces[probe].NewerThan(pces[probe2])) {
			probe = probe2;
		}
	}
	if (len > BreakFinder::lengthStartSubdivision) {
		// Break up into segments
		unsigned int startSegment = 0;
		XYPOSITION xStartSegment = 0;
		while (startSegment < len) {
			unsigned int lenSegment = pdoc->SafeSegment(s + startSegment, len - startSegment, BreakFinder::lengthEachSubdivision);
			surface->MeasureWidths(vstyle.styles[styleNumber].font, s + startSegment, lenSegment, positions + startSegment);
			for (unsigned int inSeg = 0; inSeg < lenSegment; inSeg++) {
				positions[startSegment + inSeg] += xStartSegment;
			}
			xStartSegment = positions[startSegment + lenSegment - 1];
			startSegment += lenSegment;
		}
	} else {
		surface->MeasureWidths(vstyle.styles[styleNumber].font, s, len, positions);
	}
	if (probe >= 0) {
		clock++;
		if (clock > 60000) {
			// Since there are only 16 bits for the clock, wrap it round and
			// reset all cache entries so none get stuck with a high clock.
			for (size_t i=0; i<pces.size(); i++) {
				pces[i].ResetClock();
			}
			clock = 2;
		}
		pces[probe].Set(styleNumber, s, len, positions, clock);
	}
}


static int arrayindex(double key)
{
    int i;
    luai_num2int(i, key);

    return luai_numeq(cast_num(i), key) ? i : -1;
}

/*
** Implementation of tables (aka arrays, objects, or hash tables).
** Tables keep its elements in two parts: an array part and a hash part.
** Non-negative integer keys are all candidates to be kept in the array
** part. The actual size of the array is the largest `n' such that at
** least half the slots between 0 and n are in use.
** Hash uses a mix of chained scatter table with Brent's variation.
** A main invariant of these tables is that, if an element is not
** in its main position (i.e. the `original' position that its hash gives
** to it), then the colliding element is in its own main position.
** Hence even when the load factor reaches 100%, performance remains good.
*/

#include "ltable.h"

#include "lstate.h"
#include "ldebug.h"
#include "lgc.h"
#include "lmem.h"
#include "lnumutils.h"

#include <string.h>

LUAU_FASTFLAGVARIABLE(LuauArrayBoundary, false)

// max size of both array and hash part is 2^MAXBITS
#define MAXBITS 26
#define MAXSIZE (1 << MAXBITS)

static_assert(offsetof(LuaNode, val) == 0, "Unexpected Node memory layout, pointer cast in gval2slot is incorrect");
// TKey is bitpacked for memory efficiency so we need to validate bit counts for worst case
static_assert(TKey{{NULL}, 0, LUA_TDEADKEY, 0}.tt == LUA_TDEADKEY, "not enough bits for tt");
static_assert(TKey{{NULL}, 0, LUA_TNIL, MAXSIZE - 1}.next == MAXSIZE - 1, "not enough bits for next");
static_assert(TKey{{NULL}, 0, LUA_TNIL, -(MAXSIZE - 1)}.next == -(MAXSIZE - 1), "not enough bits for next");

// reset cache of absent metamethods, cache is updated in luaT_gettm
#define invalidateTMcache(t) t->flags = 0

// empty hash data points to dummynode so that we can always dereference it
const LuaNode luaH_dummynode = {
    {{NULL}, 0, LUA_TNIL},   /* value */
    {{NULL}, 0, LUA_TNIL, 0} /* key */
};

#define dummynode (&luaH_dummynode)

// hash is always reduced mod 2^k
#define hashpow2(t, n) (gnode(t, lmod((n), sizenode(t))))

#define hashstr(t, str) hashpow2(t, (str)->hash)
#define hashboolean(t, p) hashpow2(t, p)

static LuaNode* hashpointer(const Table* t, const void* p)
{
    // we discard the high 32-bit portion of the pointer on 64-bit platforms as it doesn't carry much entropy anyway
    unsigned int h = unsigned(uintptr_t(p));

    h ^= h >> 16;
    h *= 0x85ebca6bu;
    h ^= h >> 13;
    h *= 0xc2b2ae35u;
    h ^= h >> 16;

    return hashpow2(t, h);
}

static LuaNode* hashnum(const Table* t, double n)
{
    static_assert(sizeof(double) == sizeof(unsigned int) * 2, "expected a 8-byte double");
    unsigned int i[2];
    memcpy(i, &n, sizeof(i));

    // mask out sign bit to make sure -0 and 0 hash to the same value
    uint32_t h1 = i[0];
    uint32_t h2 = i[1] & 0x7fffffff;

    // finalizer from MurmurHash64B
    const uint32_t m = 0x5bd1e995;

    h1 ^= h2 >> 18;
    h1 *= m;
    h2 ^= h1 >> 22;
    h2 *= m;
    h1 ^= h2 >> 17;
    h1 *= m;
    h2 ^= h1 >> 19;
    h2 *= m;

    // ... truncated to 32-bit output (normally hash is equal to (uint64_t(h1) << 32) | h2, but we only really need the lower 32-bit half)
    return hashpow2(t, h2);
}

static LuaNode* hashvec(const Table* t, const float* v)
{
    unsigned int i[3];
    memcpy(i, v, sizeof(i));

    // convert -0 to 0 to make sure they hash to the same value
    i[0] = (i[0] == 0x8000000) ? 0 : i[0];
    i[1] = (i[1] == 0x8000000) ? 0 : i[1];
    i[2] = (i[2] == 0x8000000) ? 0 : i[2];

    // scramble bits to make sure that integer coordinates have entropy in lower bits
    i[0] ^= i[0] >> 17;
    i[1] ^= i[1] >> 17;
    i[2] ^= i[2] >> 17;

    // Optimized Spatial Hashing for Collision Detection of Deformable Objects
    unsigned int h = (i[0] * 73856093) ^ (i[1] * 19349663) ^ (i[2] * 83492791);

    return hashpow2(t, h);
}

/*
** returns the `main' position of an element in a table (that is, the index
** of its hash value)
*/
static LuaNode* mainposition(const Table* t, const TValue* key)
{
    switch (ttype(key))
    {
    case LUA_TNUMBER:
        return hashnum(t, nvalue(key));
    case LUA_TVECTOR:
        return hashvec(t, vvalue(key));
    case LUA_TSTRING:
        return hashstr(t, tsvalue(key));
    case LUA_TBOOLEAN:
        return hashboolean(t, bvalue(key));
    case LUA_TLIGHTUSERDATA:
        return hashpointer(t, pvalue(key));
    default:
        return hashpointer(t, gcvalue(key));
    }
}

/*
** returns the index for `key' if `key' is an appropriate key to live in
** the array part of the table, -1 otherwise.
*/

/*
** returns the index of a `key' for table traversals. First goes all
** elements in the array part, then elements in the hash part. The
** beginning of a traversal is signalled by -1.
*/
static int findindex(lua_State* L, Table* t, StkId key)
{
    int i;
    if (ttisnil(key))
        return -1; /* first iteration */
    i = ttisnumber(key) ? arrayindex(nvalue(key)) : -1;
    if (0 < i && i <= t->sizearray) /* is `key' inside array part? */
        return i - 1;               /* yes; that's the index (corrected to C) */
    else
    {
        LuaNode* n = mainposition(t, key);
        for (;;)
        { /* check whether `key' is somewhere in the chain */
            /* key may be dead already, but it is ok to use it in `next' */
            if (luaO_rawequalKey(gkey(n), key) || (ttype(gkey(n)) == LUA_TDEADKEY && iscollectable(key) && gcvalue(gkey(n)) == gcvalue(key)))
            {
                i = cast_int(n - gnode(t, 0)); /* key index in hash table */
                /* hash elements are numbered after array ones */
                return i + t->sizearray;
            }
            if (gnext(n) == 0)
                break;
            n += gnext(n);
        }
        luaG_runerror(L, "invalid key to 'next'"); /* key not found */
    }
}

int luaH_next(lua_State* L, Table* t, StkId key)
{
    int i = findindex(L, t, key); /* find original element */
    for (i++; i < t->sizearray; i++)
    { /* try first array part */
        if (!ttisnil(&t->array[i]))
        { /* a non-nil value? */
            setnvalue(key, cast_num(i + 1));
            setobj2s(L, key + 1, &t->array[i]);
            return 1;
        }
    }
    for (i -= t->sizearray; i < sizenode(t); i++)
    { /* then hash part */
        if (!ttisnil(gval(gnode(t, i))))
        { /* a non-nil value? */
            getnodekey(L, key, gnode(t, i));
            setobj2s(L, key + 1, gval(gnode(t, i)));
            return 1;
        }
    }
    return 0; /* no more elements */
}

/*
** {=============================================================
** Rehash
** ==============================================================
*/

#define maybesetaboundary(t, boundary) \
    { \
        if (FFlag::LuauArrayBoundary && t->aboundary <= 0) \
            t->aboundary = -int(boundary); \
    }

#define getaboundary(t) (t->aboundary < 0 ? -t->aboundary : t->sizearray)

static int computesizes(int nums[], int* narray)
{
    int i;
    int twotoi; /* 2^i */
    int a = 0;  /* number of elements smaller than 2^i */
    int na = 0; /* number of elements to go to array part */
    int n = 0;  /* optimal size for array part */
    for (i = 0, twotoi = 1; twotoi / 2 < *narray; i++, twotoi *= 2)
    {
        if (nums[i] > 0)
        {
            a += nums[i];
            if (a > twotoi / 2)
            {               /* more than half elements present? */
                n = twotoi; /* optimal size (till now) */
                na = a;     /* all elements smaller than n will go to array part */
            }
        }
        if (a == *narray)
            break; /* all elements already counted */
    }
    *narray = n;
    LUAU_ASSERT(*narray / 2 <= na && na <= *narray);
    return na;
}

static int countint(double key, int* nums)
{
    int k = arrayindex(key);
    if (0 < k && k <= MAXSIZE)
    {                        /* is `key' an appropriate array index? */
        nums[ceillog2(k)]++; /* count as such */
        return 1;
    }
    else
        return 0;
}

static int numusearray(const Table* t, int* nums)
{
    int lg;
    int ttlg;     /* 2^lg */
    int ause = 0; /* summation of `nums' */
    int i = 1;    /* count to traverse all array keys */
    for (lg = 0, ttlg = 1; lg <= MAXBITS; lg++, ttlg *= 2)
    {               /* for each slice */
        int lc = 0; /* counter */
        int lim = ttlg;
        if (lim > t->sizearray)
        {
            lim = t->sizearray; /* adjust upper limit */
            if (i > lim)
                break; /* no more elements to count */
        }
        /* count elements in range (2^(lg-1), 2^lg] */
        for (; i <= lim; i++)
        {
            if (!ttisnil(&t->array[i - 1]))
                lc++;
        }
        nums[lg] += lc;
        ause += lc;
    }
    return ause;
}

static int numusehash(const Table* t, int* nums, int* pnasize)
{
    int totaluse = 0; /* total number of elements */
    int ause = 0;     /* summation of `nums' */
    int i = sizenode(t);
    while (i--)
    {
        LuaNode* n = &t->node[i];
        if (!ttisnil(gval(n)))
        {
            if (ttisnumber(gkey(n)))
                ause += countint(nvalue(gkey(n)), nums);
            totaluse++;
        }
    }
    *pnasize += ause;
    return totaluse;
}

static void setarrayvector(lua_State* L, Table* t, int size)
{
    if (size > MAXSIZE)
        luaG_runerror(L, "table overflow");
    luaM_reallocarray(L, t->array, t->sizearray, size, TValue, t->memcat);
    TValue* array = t->array;
    for (int i = t->sizearray; i < size; i++)
        setnilvalue(&array[i]);
    t->sizearray = size;
}

static void setnodevector(lua_State* L, Table* t, int size)
{
    int lsize;
    if (size == 0)
    {                                           /* no elements to hash part? */
        t->node = cast_to(LuaNode*, dummynode); /* use common `dummynode' */
        lsize = 0;
    }
    else
    {
        int i;
        lsize = ceillog2(size);
        if (lsize > MAXBITS)
            luaG_runerror(L, "table overflow");
        size = twoto(lsize);
        t->node = luaM_newarray(L, size, LuaNode, t->memcat);
        for (i = 0; i < size; i++)
        {
            LuaNode* n = gnode(t, i);
            gnext(n) = 0;
            setnilvalue(gkey(n));
            setnilvalue(gval(n));
        }
    }
    t->lsizenode = cast_byte(lsize);
    t->nodemask8 = cast_byte((1 << lsize) - 1);
    t->lastfree = size; /* all positions are free */
}

static void resize(lua_State* L, Table* t, int nasize, int nhsize)
{
    if (nasize > MAXSIZE || nhsize > MAXSIZE)
        luaG_runerror(L, "table overflow");
    int oldasize = t->sizearray;
    int oldhsize = t->lsizenode;
    LuaNode* nold = t->node; /* save old hash ... */
    if (nasize > oldasize)   /* array part must grow? */
        setarrayvector(L, t, nasize);
    /* create new hash part with appropriate size */
    setnodevector(L, t, nhsize);
    if (nasize < oldasize)
    { /* array part must shrink? */
        t->sizearray = nasize;
        /* re-insert elements from vanishing slice */
        for (int i = nasize; i < oldasize; i++)
        {
            if (!ttisnil(&t->array[i]))
                setobjt2t(L, luaH_setnum(L, t, i + 1), &t->array[i]);
        }
        /* shrink array */
        luaM_reallocarray(L, t->array, oldasize, nasize, TValue, t->memcat);
    }
    /* re-insert elements from hash part */
    for (int i = twoto(oldhsize) - 1; i >= 0; i--)
    {
        LuaNode* old = nold + i;
        if (!ttisnil(gval(old)))
        {
            TValue ok;
            getnodekey(L, &ok, old);
            setobjt2t(L, luaH_set(L, t, &ok), gval(old));
        }
    }
    if (nold != dummynode)
        luaM_freearray(L, nold, twoto(oldhsize), LuaNode, t->memcat); /* free old array */
}

void luaH_resizearray(lua_State* L, Table* t, int nasize)
{
    int nsize = (t->node == dummynode) ? 0 : sizenode(t);
    resize(L, t, nasize, nsize);
}

void luaH_resizehash(lua_State* L, Table* t, int nhsize)
{
    resize(L, t, t->sizearray, nhsize);
}

static void rehash(lua_State* L, Table* t, const TValue* ek)
{
    int nums[MAXBITS + 1]; /* nums[i] = number of keys between 2^(i-1) and 2^i */
    for (int i = 0; i <= MAXBITS; i++)
        nums[i] = 0;                          /* reset counts */
    int nasize = numusearray(t, nums);        /* count keys in array part */
    int totaluse = nasize;                    /* all those keys are integer keys */
    totaluse += numusehash(t, nums, &nasize); /* count keys in hash part */
    /* count extra key */
    if (ttisnumber(ek))
        nasize += countint(nvalue(ek), nums);
    totaluse++;
    /* compute new size for array part */
    int na = computesizes(nums, &nasize);
    /* resize the table to new computed sizes */
    resize(L, t, nasize, totaluse - na);
}

/*
** }=============================================================
*/

Table* luaH_new(lua_State* L, int narray, int nhash)
{
    Table* t = luaM_new(L, Table, sizeof(Table), L->activememcat);
    luaC_link(L, t, LUA_TTABLE);
    t->metatable = NULL;
    t->flags = cast_byte(~0);
    t->array = NULL;
    t->sizearray = 0;
    t->lastfree = 0;
    t->lsizenode = 0;
    t->readonly = 0;
    t->safeenv = 0;
    t->nodemask8 = 0;
    t->node = cast_to(LuaNode*, dummynode);
    if (narray > 0)
        setarrayvector(L, t, narray);
    if (nhash > 0)
        setnodevector(L, t, nhash);
    return t;
}

void luaH_free(lua_State* L, Table* t)
{
    if (t->node != dummynode)
        luaM_freearray(L, t->node, sizenode(t), LuaNode, t->memcat);
    luaM_freearray(L, t->array, t->sizearray, TValue, t->memcat);
    luaM_free(L, t, sizeof(Table), t->memcat);
}

static LuaNode* getfreepos(Table* t)
{
    while (t->lastfree > 0)
    {
        t->lastfree--;

        LuaNode* n = gnode(t, t->lastfree);
        if (ttisnil(gkey(n)))
            return n;
    }
    return NULL; /* could not find a free place */
}

/*
** inserts a new key into a hash table; first, check whether key's main
** position is free. If not, check whether colliding node is in its main
** position or not: if it is not, move colliding node to an empty place and
** put new key in its main position; otherwise (colliding node is in its main
** position), new key goes to an empty position.
*/
static TValue* newkey(lua_State* L, Table* t, const TValue* key)
{
    LuaNode* mp = mainposition(t, key);
    if (!ttisnil(gval(mp)) || mp == dummynode)
    {
        LuaNode* othern;
        LuaNode* n = getfreepos(t); /* get a free place */
        if (n == NULL)
        {                               /* cannot find a free place? */
            rehash(L, t, key);          /* grow table */
            return luaH_set(L, t, key); /* re-insert key into grown table */
        }
        LUAU_ASSERT(n != dummynode);
        TValue mk;
        getnodekey(L, &mk, mp);
        othern = mainposition(t, &mk);
        if (othern != mp)
        { /* is colliding node out of its main position? */
            /* yes; move colliding node into free position */
            while (othern + gnext(othern) != mp)
                othern += gnext(othern);          /* find previous */
            gnext(othern) = cast_int(n - othern); /* redo the chain with `n' in place of `mp' */
            *n = *mp;                             /* copy colliding node into free pos. (mp->next also goes) */
            if (gnext(mp) != 0)
            {
                gnext(n) += cast_int(mp - n); /* correct 'next' */
                gnext(mp) = 0;                /* now 'mp' is free */
            }
            setnilvalue(gval(mp));
        }
        else
        { /* colliding node is in its own main position */
            /* new node will go into free position */
            if (gnext(mp) != 0)
                gnext(n) = cast_int((mp + gnext(mp)) - n); /* chain new position */
            else
                LUAU_ASSERT(gnext(n) == 0);
            gnext(mp) = cast_int(n - mp);
            mp = n;
        }
    }
    setnodekey(L, mp, key);
    luaC_barriert(L, t, key);
    LUAU_ASSERT(ttisnil(gval(mp)));
    return gval(mp);
}

/*
** search function for integers
*/
const TValue* luaH_getnum(Table* t, int key)
{
    /* (1 <= key && key <= t->sizearray) */
    if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray))
        return &t->array[key - 1];
    else if (t->node != dummynode)
    {
        double nk = cast_num(key);
        LuaNode* n = hashnum(t, nk);
        for (;;)
        { /* check whether `key' is somewhere in the chain */
            if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk))
                return gval(n); /* that's it */
            if (gnext(n) == 0)
                break;
            n += gnext(n);
        }
        return luaO_nilobject;
    }
    else
        return luaO_nilobject;
}

/*
** search function for strings
*/
const TValue* luaH_getstr(Table* t, TString* key)
{
    LuaNode* n = hashstr(t, key);
    for (;;)
    { /* check whether `key' is somewhere in the chain */
        if (ttisstring(gkey(n)) && tsvalue(gkey(n)) == key)
            return gval(n); /* that's it */
        if (gnext(n) == 0)
            break;
        n += gnext(n);
    }
    return luaO_nilobject;
}

/*
** main search function
*/
const TValue* luaH_get(Table* t, const TValue* key)
{
    switch (ttype(key))
    {
    case LUA_TNIL:
        return luaO_nilobject;
    case LUA_TSTRING:
        return luaH_getstr(t, tsvalue(key));
    case LUA_TNUMBER:
    {
        int k;
        double n = nvalue(key);
        luai_num2int(k, n);
        if (luai_numeq(cast_num(k), nvalue(key))) /* index is int? */
            return luaH_getnum(t, k);             /* use specialized version */
                                                  /* else go through */
    }
    default:
    {
        LuaNode* n = mainposition(t, key);
        for (;;)
        { /* check whether `key' is somewhere in the chain */
            if (luaO_rawequalKey(gkey(n), key))
                return gval(n); /* that's it */
            if (gnext(n) == 0)
                break;
            n += gnext(n);
        }
        return luaO_nilobject;
    }
    }
}

TValue* luaH_set(lua_State* L, Table* t, const TValue* key)
{
    const TValue* p = luaH_get(t, key);
    invalidateTMcache(t);
    if (p != luaO_nilobject)
        return cast_to(TValue*, p);
    else
    {
        if (ttisnil(key))
            luaG_runerror(L, "table index is nil");
        else if (ttisnumber(key) && luai_numisnan(nvalue(key)))
            luaG_runerror(L, "table index is NaN");
        else if (ttisvector(key) && luai_vecisnan(vvalue(key)))
            luaG_runerror(L, "table index contains NaN");
        return newkey(L, t, key);
    }
}

TValue* luaH_setnum(lua_State* L, Table* t, int key)
{
    /* (1 <= key && key <= t->sizearray) */
    if (cast_to(unsigned int, key - 1) < cast_to(unsigned int, t->sizearray))
        return &t->array[key - 1];
    /* hash fallback */
    const TValue* p = luaH_getnum(t, key);
    if (p != luaO_nilobject)
        return cast_to(TValue*, p);
    else
    {
        TValue k;
        setnvalue(&k, cast_num(key));
        return newkey(L, t, &k);
    }
}

TValue* luaH_setstr(lua_State* L, Table* t, TString* key)
{
    const TValue* p = luaH_getstr(t, key);
    invalidateTMcache(t);
    if (p != luaO_nilobject)
        return cast_to(TValue*, p);
    else
    {
        TValue k;
        setsvalue(L, &k, key);
        return newkey(L, t, &k);
    }
}

static LUAU_NOINLINE int unbound_search(Table* t, unsigned int j)
{
    unsigned int i = j; /* i is zero or a present index */
    j++;
    /* find `i' and `j' such that i is present and j is not */
    while (!ttisnil(luaH_getnum(t, j)))
    {
        i = j;
        j *= 2;
        if (j > cast_to(unsigned int, INT_MAX))
        { /* overflow? */
            /* table was built with bad purposes: resort to linear search */
            i = 1;
            while (!ttisnil(luaH_getnum(t, i)))
                i++;
            return i - 1;
        }
    }
    /* now do a binary search between them */
    while (j - i > 1)
    {
        unsigned int m = (i + j) / 2;
        if (ttisnil(luaH_getnum(t, m)))
            j = m;
        else
            i = m;
    }
    return i;
}

static int updateaboundary(Table* t, int boundary)
{
    if (boundary < t->sizearray && ttisnil(&t->array[boundary - 1]))
    {
        if (boundary >= 2 && !ttisnil(&t->array[boundary - 2]))
        {
            maybesetaboundary(t, boundary - 1);
            return boundary - 1;
        }
    }
    else if (boundary + 1 < t->sizearray && !ttisnil(&t->array[boundary]) && ttisnil(&t->array[boundary + 1]))
    {
        maybesetaboundary(t, boundary + 1);
        return boundary + 1;
    }

    return 0;
}

/*
** Try to find a boundary in table `t'. A `boundary' is an integer index
** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil).
*/
int luaH_getn(Table* t)
{
    int boundary = getaboundary(t);

    if (FFlag::LuauArrayBoundary && boundary > 0)
    {
        if (!ttisnil(&t->array[t->sizearray - 1]) && t->node == dummynode)
            return t->sizearray; /* fast-path: the end of the array in `t' already refers to a boundary */
        if (boundary < t->sizearray && !ttisnil(&t->array[boundary - 1]) && ttisnil(&t->array[boundary]))
            return boundary; /* fast-path: boundary already refers to a boundary in `t' */

        int foundboundary = updateaboundary(t, boundary);
        if (foundboundary > 0)
            return foundboundary;
    }

    int j = t->sizearray;

    if (j > 0 && ttisnil(&t->array[j - 1]))
    {
        // "branchless" binary search from Array Layouts for Comparison-Based Searching, Paul Khuong, Pat Morin, 2017.
        // note that clang is cmov-shy on cmovs around memory operands, so it will compile this to a branchy loop.
        TValue* base = t->array;
        int rest = j;
        while (int half = rest >> 1)
        {
            base = ttisnil(&base[half]) ? base : base + half;
            rest -= half;
        }
        int boundary = !ttisnil(base) + int(base - t->array);
        maybesetaboundary(t, boundary);
        return boundary;
    }
    /* else must find a boundary in hash part */
    else if (t->node == dummynode) /* hash part is empty? */
        return j;                  /* that is easy... */
    else
        return unbound_search(t, j);
}

Table* luaH_clone(lua_State* L, Table* tt)
{
    Table* t = luaM_new(L, Table, sizeof(Table), L->activememcat);
    luaC_link(L, t, LUA_TTABLE);
    t->metatable = tt->metatable;
    t->flags = tt->flags;
    t->array = NULL;
    t->sizearray = 0;
    t->lsizenode = 0;
    t->nodemask8 = 0;
    t->readonly = 0;
    t->safeenv = 0;
    t->node = cast_to(LuaNode*, dummynode);
    t->lastfree = 0;

    if (tt->sizearray)
    {
        t->array = luaM_newarray(L, tt->sizearray, TValue, t->memcat);
        maybesetaboundary(t, getaboundary(tt));
        t->sizearray = tt->sizearray;

        memcpy(t->array, tt->array, t->sizearray * sizeof(TValue));
    }

    if (tt->node != dummynode)
    {
        int size = 1 << tt->lsizenode;
        t->node = luaM_newarray(L, size, LuaNode, t->memcat);
        t->lsizenode = tt->lsizenode;
        t->nodemask8 = tt->nodemask8;
        memcpy(t->node, tt->node, size * sizeof(LuaNode));
        t->lastfree = tt->lastfree;
    }

    return t;
}

void luaH_clear(Table* tt)
{
    /* clear array part */
    for (int i = 0; i < tt->sizearray; ++i)
    {
        setnilvalue(&tt->array[i]);
    }

    maybesetaboundary(tt, 0);

    /* clear hash part */
    if (tt->node != dummynode)
    {
        int size = sizenode(tt);
        tt->lastfree = size;
        for (int i = 0; i < size; ++i)
        {
            LuaNode* n = gnode(tt, i);
            setnilvalue(gkey(n));
            setnilvalue(gval(n));
            gnext(n) = 0;
        }
    }

    /* back to empty -> no tag methods present */
    tt->flags = cast_byte(~0);
}