const
  cShowAll   = 0;
  cShowInter = 1;
  cShowDiff  = 2;

var
  AGrid: TvxStringGrid;
  row: Integer;

  ATabList: TStringList;
  tabcount: Integer;
  activetab: Integer;

  show: Integer;

  showAllButton: TvxRectangle;
  showInterButton: TvxRectangle;
  showDiffButton: TvxRectangle;

  function TabButtonAtIndex(AIndex: Integer): TvxRectangle;
  begin
    result := ATabList.Objects[AIndex];
  end;

  function IndexedDataPointerFromString(AStr: WideString; AIndex: Integer): TTntStringList;
  var
    ASl: TTntStringList;
  begin
    ASl := TTntStringList.Create;
    try
      ASl.CommaText := AStr;
      result := StrToPtrInt(ASl.Strings[AIndex])
    finally
      ASl.Free;
    end;
  end;

  function FileDataPointerFromString(AStr: WideString): TTntStringList;
  begin
    result := IndexedDataPointerFromString(AStr, 0);
  end;

  function CatDataPointerFromString(AStr: WideString): TTntStringList;
  begin
    result := IndexedDataPointerFromString(AStr, 1);
  end;

  function InterDataPointerFromString(AStr: WideString): TTntStringList;
  begin
    result := IndexedDataPointerFromString(AStr, 2);
  end;

  function DiffDataPointerFromString(AStr: WideString): TTntStringList;
  begin
    result := IndexedDataPointerFromString(AStr, 3);
  end;

  function TabFileDataAtIndex(AIndex: Integer): TTntStringList;
  begin
    result := StrToPtrInt(FileDataPointerFromString(TvxRectangle(TabButtonAtIndex(AIndex)).TagString));
  end;

  function TabCatDataAtIndex(AIndex: Integer): TTntStringList;
  begin
    result := StrToPtrInt(CatDataPointerFromString(TvxRectangle(TabButtonAtIndex(AIndex)).TagString));
  end;

  function TabInterDataAtIndex(AIndex: Integer): TTntStringList;
  begin
    result := StrToPtrInt(InterDataPointerFromString(TvxRectangle(TabButtonAtIndex(AIndex)).TagString));
  end;

  function TabDiffDataAtIndex(AIndex: Integer): TTntStringList;
  begin
    result := StrToPtrInt(DiffDataPointerFromString(TvxRectangle(TabButtonAtIndex(AIndex)).TagString));
  end;

  function TabImageAtIndex(AIndex: Integer): TImageItem;
  begin
    result := TImageItem(TvxRectangle(TabButtonAtIndex(AIndex)).TagObject);
  end;

  function ActiveTabButton: TvxRectangle;
  begin
    result := TabButtonAtIndex(activetab);
  end;

  function ActiveTabFileData: TTntStringList;
  begin
    result := TabFileDataAtIndex(activetab);
  end;

  function ActiveTabCatData: TTntStringList;
  begin
    result := TabCatDataAtIndex(activetab);
  end;

  function ActiveTabInterData: TTntStringList;
  begin
    result := TabInterDataAtIndex(activetab);
  end;

  function ActiveTabDiffData: TTntStringList;
  begin
    result := TabDiffDataAtIndex(activetab);
  end;

  function ActiveTabImage: TImageItem;
  begin
    result := TabImageAtIndex(activetab);
  end;

  procedure DumpXmp(AImg: TImageItem; AGrid: TvxStringGrid; AFileSl, ACatSl, AInterSl, ADiffSl: TTntStrings);
  var
    i, AIdx: Integer;
    AFileXmp, ACatXmp, ATempXmp: TXMP;
    ASize: Int64;
    ALeadSl: TTntStringList;
    ACatItem: TCatalogItem;
  begin
    ACatItem := TCatalogItem.Create(nil);

    ACatXmp := TXMP.Create(False);
    AFileXmp := TXMP.Create(False);
    ATempXmp := TXMP.Create(False);
    try
      PublicCatalog.FindItemCombinedByGUID(AImg.GUID, ACatItem);

      AFileSl.Clear;
      ACatSl.Clear;
      AInterSl.Clear;
      ADiffSl.Clear;

      AFileXmp.Load(AImg.XMPFileName, AImg.ExifFileName, False);
      AFileXmp.DumpToStrings(AFileSl);

      PublicCatalog.LoadXMPForImage(AImg, ACatXmp, PublicOptions.CachedXMP);
      ACatXmp.DumpToStrings(ACatSl);

      ATempXmp.AppendXMP(AFileXmp);
      ATempXmp.IntersectWithXMP(ACatXmp);
      ATempXmp.DumpToStrings(AInterSl);

      ACatXmp.DifferenceWithXMP(AFileXmp);
      ACatXmp.DumpToStrings(ADiffSl);
    finally
      ATempXmp.Free;
      AFileXmp.Free;
      ACatXmp.Free;
    end;

    AGrid.Cells[0, row] := 'FileName';
    AGrid.Cells[1, row] := WideExtractFileName(AImg.FileName);
    AGrid.Cells[2, row] := WideExtractFileName(ACatItem.FileName);

    Inc(row);
    AGrid.Cells[0, row] := 'FilePath';
    AGrid.Cells[1, row] := AImg.FileName;
    AGrid.Cells[2, row] := ACatItem.FileName;

    Inc(row);
    AGrid.Cells[0, row] := 'FileSize';
    ASize := SizeOfFile(AImg.FileName);
    AGrid.Cells[1, row] := IntToStr(ASize) + ' bytes (' + SizeString(ASize) + ')';
    ASize := ACatItem.FileSize;
    AGrid.Cells[2, row] := IntToStr(ASize) + ' bytes (' + SizeString(ASize) + ')';

    Inc(row);
    AGrid.Cells[0, row] := 'FileStamp';
    AGrid.Cells[1, row] := DateTimeToStr(FileStamp(AImg.FileName));
    AGrid.Cells[2, row] := DateTimeToStr(ACatItem.FileStamp);

    Inc(row);
    AGrid.Cells[0, row] := '';
    AGrid.Cells[1, row] := '';
    AGrid.Cells[2, row] := '';

    if show = cShowInter then
      ALeadSl := AInterSl
    else if show = cShowDiff then
      ALeadSl := ADiffSl
    else
      ALeadSl := AFileSl;

    for i := 0 to ALeadSl.Count - 1 do
    begin
      if ALeadSl = AFileSl then
        AIdx := i
      else
        AIdx := AFileSl.IndexOfName(ALeadSl.Names[i]);

      if AIdx <> -1 then
      begin
        Inc(row);

        AGrid.Cells[0, row] := AFileSl.Names[AIdx];
        AGrid.Cells[1, row] := AFileSl.ValueFromIndex[AIdx];

        AIdx := ACatSl.IndexOfName(ALeadSl.Names[i]);
        if AIdx <> -1 then
          AGrid.Cells[2, row] := ACatSl.ValueFromIndex[AIdx]
        else
          AGrid.Cells[2, row] := '';
      end;
    end;

    if ALeadSl = AFileSl then
    begin
      for i := 0 to ACatSl.Count - 1 do
      begin
        AIdx := ALeadSl.IndexOfName(ACatSl.Names[i]);

        if AIdx = -1 then
        begin
          Inc(row);
          AGrid.Cells[0, row] := ACatSl.Names[i];
          AGrid.Cells[1, row] := '';
          AGrid.Cells[2, row] := ACatSl.ValueFromIndex[i]
        end;
      end;
    end;

    Inc(row);
    AGrid.Cells[0, row] := '';
    AGrid.Cells[1, row] := '';
    AGrid.Cells[2, row] := '';

    ACatItem.Free;
  end;

  procedure DumpGridToStringList(ASl: TTntStringList);
  var
    i: Integer;
    ALine: TTntStringList;
  begin
    // add header
    ALine := TTntStringList.Create;
    ALine.Add('Tag');
    ALine.Add('In File');
    ALine.Add('In Catalog');
    ASl.Add(ALine.CommaText);
    ALine.Free;

    for i := 0 to AGrid.RowCount - 1 do
    begin
      ALine := TTntStringList.Create;
      ALine.Add(AGrid.Cells[0, i]);
      ALine.Add(AGrid.Cells[1, i]);
      ALine.Add(AGrid.Cells[2, i]);
      ASl.Add(ALine.CommaText);
      ALine.Free;
    end;
  end;

  function CheckVersion41: Boolean;
  var
    ATokens: TTntStringList;
  begin
    result := False;
    ATokens := TTntStringList.Create;
    Tokenize(xomProductVersion, '.', ATokens);
    if ATokens.Count = 4 then
    begin
      if (IntToStr(ATokens.Strings[0]) > 4) or
         (
           (IntToStr(ATokens.Strings[0]) = 4) and
           (IntToStr(ATokens.Strings[1]) >= 1)
         )
      then
        result := True;
    end;
    ATokens.Free;
  end;

  procedure CheckButtonShowState;
  begin
    showAllButton.Fill.Style   := iif(show = cShowAll, vgBrushSolid, vgBrushNone);
    showInterButton.Fill.Style := iif(show = cShowInter, vgBrushSolid, vgBrushNone);
    showDiffButton.Fill.Style  := iif(show = cShowDiff, vgBrushSolid, vgBrushNone);
  end;

  procedure ActivateTab(ATabIndex: Integer);
  begin
    TvxRectangle(ActiveTabButton).Fill.Style := vgBrushNone;

    // dump the first selected file to the grid
    AGrid.RowCount := 0;
    row := 0;
    activetab := ATabIndex;
    DumpXmp(ActiveTabImage, AGrid, ActiveTabFileData, ActiveTabCatData, ActiveTabInterData, ActiveTabDiffData);

    TvxRectangle(ActiveTabButton).Fill.Style := vgBrushSolid;
  end;

  procedure _SaveToFileClick(Sender: TObject);
  var
    ASl: TTntStringList;
  begin
    //SaveDialog.FileName := WideChangeFileExt(TImageItem(ActiveTabImage).FileNameOnly, '.txt');
    SaveDialog.FileName := TImageItem(ActiveTabImage).FileNameOnly + '.txt';
    if SaveDialog.Execute then
    begin
      ASl := TTntStringList.Create;
      try
        DumpGridToStringList(ASl);
        ASl.SaveToFile(SaveDialog.FileName);
      finally
        ASl.Free;
      end;

      Say('Saved as ' + SaveDialog.FileName);
    end;
  end;

  procedure _ShowAllClick(Sender: TObject);
  begin
    show := cShowAll;
    ActivateTab(activetab);
    CheckButtonShowState;
  end;

  procedure _ShowInterClick(Sender: TObject);
  begin
    show := cShowInter;
    ActivateTab(activetab);
    CheckButtonShowState;
  end;

  procedure _ShowDiffClick(Sender: TObject);
  begin
    show := cShowDiff;
    ActivateTab(activetab);
    CheckButtonShowState;
  end;

  procedure _TabButtonClick(Sender: TObject);
  var
    ATabButton: TvxRectangle;
  begin
    ATabButton := TvxRectangle(Sender);

    ActivateTab(ATabButton.Tag);
  end;

  function CreateTab(AImg: TImageItem; ATabBar: TvxScrollBox): TvxRectangle;
  var
    ATabButton: TvxRectangle;
    ALabel: TvxLabel;
  begin
    ATabButton := TvxRectangle.Create(ATabBar);
    ATabButton.Align := vaLeft;
    ATabButton.Padding.Rect := vgRect(0, 5, 0, 5);
    ALabel := TvxLabel.Create(ATabButton);
    ALabel.Align := vaClient;
    ALabel.Text := AImg.FileNameOnly;
    ATabButton.AddObject(ALabel);

    ATabButton.xRadius := 5;
    ATabButton.yRadius := ATabButton.xRadius;
    ATabButton.Corners := [vgCornerTopLeft, vgCornerTopRight];

    ATabButton.Fill.Color := '#80' + MidStr(GUIDesign.HighlightFill.Color, 4, Length(GUIDesign.HighlightFill.Color));
    ATabButton.Fill.Style := vgBrushNone;
    ATabButton.Stroke.Color := GUIDesign.DarkFill.Color;
    ATabButton.Stroke.Style := vgBrushSolid;

    ATabButton.Width := vgTextWidth(ALabel.Text, ALabel.Font, vgNormal) + 16;

    ATabButton.OnClick := '_TabButtonClick';

    ATabButton.TagString := PtrIntToStr(TTntStringList.Create) + ',' +
                            PtrIntToStr(TTntStringList.Create) + ',' +
                            PtrIntToStr(TTntStringList.Create) + ',' +
                            PtrIntToStr(TTntStringList.Create) + ',' +
                            '';
    ATabButton.TagObject := AImg;
    ATabButton.Tag := tabcount;
    Inc(tabcount);

    ATabBar.AddObject(ATabButton);

    result := ATabButton;
  end;

  function CreateOptionButton(AParent: TvxObject; AText: WideString; AOnClick: WideString): TvxVisualObject;
  var
    ALabel: TvxLabel;
  begin
    result := TvxRectangle.Create(AParent)
    result.Align := vaLeft;
    result.Width := 110;
    result.Padding.Rect := vxRect(1, 8, 5, 8);
    result.OnClick := AOnClick;
    result.xRadius := 5;
    result.yRadius := result.xRadius;
    AParent.AddObject(result);

    result.Fill.Color := '#80' + MidStr(GUIDesign.HighlightFill.Color, 4, Length(GUIDesign.HighlightFill.Color));
    result.Fill.Style := vgBrushNone;
    result.Stroke.Color := '#40' + MidStr(GUIDesign.MidDarkFontFill.Color, 4, Length(GUIDesign.MidDarkFontFill.Color));
    result.Stroke.Style := vgBrushSolid;


    ALabel := TvxLabel.Create(result);
    ALabel.Align := vaClient;
    ALabel.text := AText;
    result.AddObject(ALabel);
  end;


// MAIN
var
  i: Integer;
  AForm: TvxSceneForm;
  ACol: TvxStringColumn;
  AButton: TvxButton;

  ATabBar: TvxScrollBox;
  ATab: TvxRectangle;
begin
  if not CheckVersion41 then
  begin
    Say('This script requires Photo Supreme 4.1 or higher');
    exit;
  end;

  if Selected.Count = 0 then
  begin
    Say ('No image selected');
    exit;
  end;

  AForm := TvxSceneForm.Create(nil);
  if Selected.Count = 1 then
    AForm.Caption := UTF8Encode(WideFormat('Compare XMP [%s]', [WideExtractFileName(Selected.Items[0].ExifFileName)]))
  else
    AForm.Caption := 'Compare XMP: ' + IntToStr(Selected.Count) + ' images';
  AForm.Width := 900 * AForm.DPIFactor;
  AForm.Height := Trunc(Screen.Height * 0.6);
  AForm.BorderStyle := bsSizeable;

  ATabBar := TvxScrollBox.Create(AForm.Root);
  ATabBar.Height := 40;
  ATabBar.Align := vaTop;
  ATabBar.UseSmallScrollBars := True;
  AForm.Root.AddObject(ATabBar);

  AGrid := TvxStringGrid.Create(AForm.Root);
  AForm.Root.AddObject(AGrid);
  AGrid.Align := vaClient;
  AGrid.RowCount := 0;

  ACol := TvxStringColumn.Create(AGrid);
  ACol.Header := 'Tag';
  ACol.Width := 200;
  AGrid.AddObject(ACol);
  ACol := TvxStringColumn.Create(AGrid);
  ACol.Header := 'In File';
  ACol.Width := 300;
  AGrid.AddObject(ACol);
  ACol := TvxStringColumn.Create(AGrid);
  ACol.Header := 'In Catalog';
  ACol.Width := 300;
  AGrid.AddObject(ACol);

  ATabList := TStringList.Create;

  for i := 0 to Selected.Count - 1 do
  begin
    ATab := CreateTab(Selected.Items[i], ATabBar);
    ATabList.AddObject(Selected.Items[i].GUID, ATab);
  end;

  tabcount := 0;
  activetab := 0;
  show := cShowAll;

  ActivateTab(activetab);

  AForm.PrepareModal([mbOK]);

  showAllButton   := CreateOptionButton(AForm.ModalPanel, 'Show All', '_ShowAllClick');
  showInterButton := CreateOptionButton(AForm.ModalPanel, 'Show Identical', '_ShowInterClick');
  showDiffButton  := CreateOptionButton(AForm.ModalPanel, 'Show Differences', '_ShowDiffClick');
  CheckButtonShowState;

  AButton := TvxButton.Create(AForm.ModalPanel)
  AButton.Text := 'Save as text file';
  AButton.Align := vaLeft;
  AButton.Width := 110;
  AButton.Padding.Rect := vxRect(25, 8, 5, 8);
  AButton.OnClick := '_SaveToFileClick';
  AForm.ModalPanel.AddObject(AButton);

  AForm.ShowModal;

  for i := 0 to ATabList.Count - 1 do
  begin
    TTntStringList(TabFileDataAtIndex(i)).Free;
    TTntStringList(TabCatDataAtIndex(i)).Free;
    TTntStringList(TabInterDataAtIndex(i)).Free;
    TTntStringList(TabDiffDataAtIndex(i)).Free;
  end;

  ATabList.Free;

  AForm.Free;
end;
