/*
  ICS HTTPS REST and OAuth Demo - C++Builder translation of the Delphi demo
  OverbyteIcsHttpRestTst2.pas (demos-delphi-vcl), ICS V9.8.

  The Json Object window: shows one Json object or array from the main window's
  response list, and expands nested objects and arrays on double click.

  How this C++ version differs from the Delphi original, apart from syntax:
  - JsonToListView fills a list view for both windows; Delphi has two copies of that
    code, DisplayJson in OverbyteIcsHttpRestTst1.pas and DispJson here.
  - The array element strings (Delphi unit globals JArrayTot and JArrayItems) are a
    member of each form.
*/

#include <vcl.h>
#pragma hdrstop

#include <algorithm>
#include <memory>

#include "OverbyteIcsHttpRestTst2.h"
#include "OverbyteIcsHttpRestTst1.h"

#pragma package(smart_init)
#pragma resource "*.dfm"

TFormObject *FormObject;

namespace {

// Names of the TSuperType values, in enum order.
const wchar_t *const SuperTypeNames[] = {
    L"stNull", L"stBoolean", L"stDouble", L"stCurrency", L"stInt",
    L"stObject", L"stArray", L"stString", L"stMethod" };

void AddColumn(TListView *List, const String &Caption, int Width)
{
    TListColumn *Column = List->Columns->Add();
    Column->Caption = Caption;
    Column->Width = Width;
}

// BeginUpdate/EndUpdate pair for list view items, ended on every exit path.
class TItemsUpdate
{
public:
    explicit TItemsUpdate(TListItems *Items) : FItems(Items) { FItems->BeginUpdate(); }
    ~TItemsUpdate() { FItems->EndUpdate(); }
    TItemsUpdate(const TItemsUpdate &) = delete;
    TItemsUpdate &operator=(const TItemsUpdate &) = delete;
private:
    TListItems *FItems;
};

} // namespace

// Delphi gets these names with GetEnumName(TypeInfo(TSuperType), ...), but the C++ header
// carries no type information for the enum. The double-click handlers test for "stObject"
// and "stArray".
String SuperTypeName(TSuperType Type)
{
    const unsigned I = static_cast<unsigned>(Type);
    return I < sizeof(SuperTypeNames) / sizeof(SuperTypeNames[0]) ? String(SuperTypeNames[I]) : String();
}

bool StartsJson(const String &S)
{
    return S.Pos("{") == 1 || S.Pos("[") == 1;
}

/* Fills List from a Json object (one row per name: Name, Type, Value; values holding
   objects are shown as raw Json) or from a Json array (one row per element, one column
   per value with the first element's names as titles). ArrayItems receives each array
   element as Json text so a double click can display just that element. */
void JsonToListView(TListView *List, _di_ISuperObject Json, std::vector<String> &ArrayItems)
{
    ArrayItems.clear();
    List->Items->Clear();
    if (!Json)
        return;
    // Delphi's main-window copy leaves BeginUpdate unbalanced when an array is empty or
    // raises, so the list stops repainting; the guard always ends it.
    TItemsUpdate Update(List->Items);

    if (Json->DataType == stObject) {
        List->Columns->Clear();
        AddColumn(List, "Name", 100);
        AddColumn(List, "Type", 70);
        AddColumn(List, "Value", 1000);
        AddColumn(List, "", 100);
        // GetEnumerator returns a new iterator that the caller frees.
        std::unique_ptr<TSuperAvlIterator> JsonEnum(Json->AsObject()->GetEnumerator());
        while (JsonEnum->MoveNext()) {
            TSuperAvlEntry *JsonItem = JsonEnum->GetIter();
            TListItem *Item = List->Items->Add();
            Item->Caption = JsonItem->Name;
            Item->SubItems->Add(SuperTypeName(JsonItem->Value->DataType));
            Item->SubItems->Add(JsonItem->Value->AsString());
        }
    }
    else if (Json->DataType == stArray) {
        List->Columns->Clear();
        TSuperArray *JsonArray = Json->AsArray();
        for (int I = 0; I < JsonArray->Length; I++) {
            const bool FirstRow = (I == 0);
            _di_ISuperObject JsonRow = JsonArray->O[I];
            ArrayItems.push_back(JsonRow->AsString());
            TListItem *Item = List->Items->Add();
            // Delphi's main-window copy assumes every element is an object and raises on
            // an array of plain values; both windows now show those in one column.
            if (JsonRow->DataType == stObject) {
                bool FirstCol = true;
                std::unique_ptr<TSuperAvlIterator> JsonEnum(JsonRow->AsObject()->GetEnumerator());
                while (JsonEnum->MoveNext()) {
                    TSuperAvlEntry *JsonItem = JsonEnum->GetIter();
                    if (!JsonItem)
                        continue;
                    const String CVal = JsonItem->Value->AsString();
                    if (FirstRow)
                        AddColumn(List, JsonItem->Name, std::min((CVal.Length() * 5) + 30, 400));
                    if (FirstCol)
                        Item->Caption = CVal;
                    else
                        Item->SubItems->Add(CVal);
                    FirstCol = false;
                }
            }
            else {
                if (FirstRow)
                    AddColumn(List, "Value", 1000);
                Item->Caption = JsonRow->AsString();
            }
        }
    }
}

__fastcall TFormObject::TFormObject(TComponent *Owner)
    : TForm(Owner)
{
}

void __fastcall TFormObject::FormClose(TObject *Sender, TCloseAction &Action)
{
    Action = caHide;
    FJArrayItems.clear();
}

void __fastcall TFormObject::SubRespListDblClick(TObject *Sender)
{
    try {
        const int Index = SubRespList->ItemIndex;
        if (Index < 0)
            return;
        if (!FJArrayItems.empty()) {
            if (Index < static_cast<int>(FJArrayItems.size()))
                DispJson(FJArrayItems[Index]);
            return;
        }
        TListItem *Item = SubRespList->Items->Item[Index];
        if (Item->SubItems->Count >= 2) {
            if (Item->SubItems->Strings[0] == "stArray" || Item->SubItems->Strings[0] == "stObject")
                DispJson(Item->SubItems->Strings[1]);
        }
        else if (StartsJson(Item->Caption))
            DispJson(Item->Caption);
    }
    catch (Exception &E) {
        HttpRestForm->AddLog("Error finding Json: " + E.Message);
    }
}

void TFormObject::DispJson(const String &JsonStr)
{
    try {
        FJArrayItems.clear();
        if (!StartsJson(JsonStr))
            return;
        // ParseString takes a non-const pointer, so parse a local copy.
        String Text = JsonStr;
        _di_ISuperObject JsonObj = TSuperObject::ParseString(Text.c_str(), true);
        SubRespList->Items->Clear();
        if (!JsonObj) {
            HttpRestForm->AddLog("Failed to parse Json");
            return;
        }
        Visible = true;
        BringToFront();
        JsonToListView(SubRespList, JsonObj, FJArrayItems);
    }
    catch (Exception &E) {
        HttpRestForm->AddLog("Error parsing Json: " + E.Message);
    }
}
