/*
  ICS SSL Json Object Signing (Jose) Demos - C++Builder translation of the Delphi demo
  OverbyteIcsJoseTst2.pas (demos-delphi-vcl), ICS V9.8.

  The Json Object window: shows one Json object or array from the main window's grid,
  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, in doParseClick (OverbyteIcsJoseTst1.pas) and DispJson here.
  - The array element strings (Delphi unit globals JArrayTot and JArrayItems, declared
    separately in both units) are a member of each form.
*/

#include <vcl.h>
#pragma hdrstop

#include <algorithm>
#include <memory>

#include "OverbyteIcsJoseTst2.h"
#include "OverbyteIcsJoseTst1.h"

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

TFormObject *FormObject;

namespace {

// Names of the TSuperType values, in enum order; the double-click handlers test for
// "stObject" and "stArray", as Delphi's GetEnumName(TypeInfo(TSuperType), ...) returns.
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

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 for an empty array or when it
    // 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, fails on an array
            // of plain values and leaks its iterator; 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::SubJsonGridDblClick(TObject *Sender)
{
    const int Index = SubJsonGrid->ItemIndex;
    if (Index < 0)
        return;
    if (!FJArrayItems.empty()) {
        if (Index < static_cast<int>(FJArrayItems.size()))
            DispJson(FJArrayItems[Index]);
        return;
    }
    TListItem *Item = SubJsonGrid->Items->Item[Index];
    // Delphi goes on to read Item.Caption after DispJson has cleared the list and freed Item.
    if (Item->SubItems->Count >= 2 &&
        (Item->SubItems->Strings[0] == "stArray" || Item->SubItems->Strings[0] == "stObject"))
        DispJson(Item->SubItems->Strings[1]);
    else if (StartsJson(Item->Caption))
        DispJson(Item->Caption);
}

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(), false);
        SubJsonGrid->Items->Clear();
        if (!JsonObj) {
            JsonDemoForm->AddLog("Failed to parse Json");
            return;
        }
        Visible = true;
        BringToFront();
        JsonToListView(SubJsonGrid, JsonObj, FJArrayItems);
    }
    catch (Exception &E) {
        JsonDemoForm->AddLog("Error parsing Json: " + E.Message);
    }
}
