/*============================================================================

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center (DKFZ)
All rights reserved.

Use of this source code is governed by a 3-clause BSD license that can be
found in the LICENSE file.

============================================================================*/

#include <mitkDICOMTagPath.h>

#include <mitkTestFixture.h>
#include <mitkTestingMacros.h>

#include <regex>

class mitkDICOMTagPathTestSuite : public mitk::TestFixture
{
  CPPUNIT_TEST_SUITE(mitkDICOMTagPathTestSuite);

  MITK_TEST(DICOMTagPathToPropertyRegEx);
  MITK_TEST(DICOMTagPathToPersistenceKeyRegEx);
  MITK_TEST(DICOMTagPathToPersistenceKeyTemplate);
  MITK_TEST(DICOMTagPathToPersistenceNameTemplate);
  MITK_TEST(DICOMTagPathToDCMTKSearchPath);
  MITK_TEST(PropertyNameToDICOMTagPath);
  MITK_TEST(DICOMTagPathToPropertyName);
  MITK_TEST(ExecutePropertyRegEx);
  MITK_TEST(AnySelectionIndexSurvivesTemplateRoundTrip);

  MITK_TEST(TestOperatorPlusWithTwoPaths);
  MITK_TEST(TestOperatorPlusWithString);
  MITK_TEST(TestOperatorPlusReverse);
  MITK_TEST(TestOperatorPlusEquals);
  MITK_TEST(TestOperatorPlusChaining);
  MITK_TEST(TestOperatorPlusWithWildcards);


  CPPUNIT_TEST_SUITE_END();

private:

  mitk::DICOMTagPath simplePath;
  mitk::DICOMTagPath deepPath;
  mitk::DICOMTagPath deepPath_withAnyElement;
  mitk::DICOMTagPath deepPath_withAnySelection;
  mitk::DICOMTagPath deepPath_withSelection;
  mitk::DICOMTagPath verydeepPath;

  mitk::DICOMTagPath emptyPath;

public:

  void setUp() override
  {
    simplePath.AddElement(0x0010, 0x0011);

    deepPath.AddElement(0x0010, 0x0011);
    deepPath.AddElement(0x0020, 0x0022);
    deepPath.AddElement(0x003A, 0x0033);

    deepPath_withAnyElement.AddElement(0x0010, 0x0011);
    deepPath_withAnyElement.AddAnyElement();
    deepPath_withAnyElement.AddElement(0x003a, 0x003f);

    deepPath_withAnySelection.AddElement(0x0010, 0x0011);
    deepPath_withAnySelection.AddAnySelection(0x002B, 0x002E);
    deepPath_withAnySelection.AddElement(0x0030, 0x0033);

    deepPath_withSelection.AddElement(0x0010, 0x0011);
    deepPath_withSelection.AddSelection(0x0020, 0x0022, 6);
    deepPath_withSelection.AddElement(0x003b, 0x003e);

    verydeepPath.AddAnySelection(0x0010, 0x0011);
    verydeepPath.AddAnyElement();
    verydeepPath.AddElement(0x0030, 0x0033);
    verydeepPath.AddSelection(0x004c, 0x004d, 4);
    verydeepPath.AddElement(0x0050, 0x0055);
  }

  void tearDown() override
  {
  }

  void DICOMTagPathToPropertyRegEx()
  {
    std::string result = mitk::DICOMTagPathToPropertyRegEx(simplePath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyRegEx() with '(0010,0011)'", std::string("DICOM\\.0010\\.0011"), result);
    result = mitk::DICOMTagPathToPropertyRegEx(deepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyRegEx() with '(0010,0011).(0020,0022).(003A,0033)'", std::string("DICOM\\.0010\\.0011\\.0020\\.0022\\.(?:003a|003A)\\.0033"), result);
    result = mitk::DICOMTagPathToPropertyRegEx(deepPath_withAnyElement);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyRegEx() with '(0010,0011).*.(003a,003f)'", std::string("DICOM\\.0010\\.0011\\.([A-Fa-f\\d]{4})\\.([A-Fa-f\\d]{4})\\.(?:003a|003A)\\.(?:003f|003F)"), result);
    result = mitk::DICOMTagPathToPropertyRegEx(deepPath_withAnySelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyRegEx() with '(0010,0011).(002B,002E)[*].(0030,0033)'", std::string("DICOM\\.0010\\.0011\\.(?:002b|002B)\\.(?:002e|002E)\\.\\[(\\d*)\\]\\.0030\\.0033"), result);
    result = mitk::DICOMTagPathToPropertyRegEx(deepPath_withSelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyRegEx() with '(0010,0011).(0020,0022)[6].(003b,003e)'", std::string("DICOM\\.0010\\.0011\\.0020\\.0022\\.\\[6\\]\\.(?:003b|003B)\\.(?:003e|003E)"), result);
    result = mitk::DICOMTagPathToPropertyRegEx(verydeepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyRegEx() with '(0010,0011)[*].*.(0030,0033).(004c,004d)[4].(0050,0055)'", std::string("DICOM\\.0010\\.0011\\.\\[(\\d*)\\]\\.([A-Fa-f\\d]{4})\\.([A-Fa-f\\d]{4})\\.0030\\.0033\\.(?:004c|004C)\\.(?:004d|004D)\\.\\[4\\]\\.0050\\.0055"), result);
  }

  void DICOMTagPathToPersistenceKeyRegEx()
  {
    std::string result = mitk::DICOMTagPathToPersistenceKeyRegEx(simplePath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0011)'", std::string("DICOM_0010_0011"), result);
    result = mitk::DICOMTagPathToPersistenceKeyRegEx(deepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0011).(0020,0022).(003A,0033)'", std::string("DICOM_0010_0011_0020_0022_(?:003a|003A)_0033"), result);
    result = mitk::DICOMTagPathToPersistenceKeyRegEx(deepPath_withAnyElement);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0011).*.(003a,003f)'", std::string("DICOM_0010_0011_([A-Fa-f\\d]{4})_([A-Fa-f\\d]{4})_(?:003a|003A)_(?:003f|003F)"), result);
    result = mitk::DICOMTagPathToPersistenceKeyRegEx(deepPath_withAnySelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0011).(002B,002E)[*].(0030,0033)'", std::string("DICOM_0010_0011_(?:002b|002B)_(?:002e|002E)_\\[(\\d*)\\]_0030_0033"), result);
    result = mitk::DICOMTagPathToPersistenceKeyRegEx(deepPath_withSelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0011).(0020,0022)[6].(003b,003e)'", std::string("DICOM_0010_0011_0020_0022_\\[6\\]_(?:003b|003B)_(?:003e|003E)"), result);
    result = mitk::DICOMTagPathToPersistenceKeyRegEx(verydeepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyRegEx() with '(0010,0011)[*].*.(0030,0033).(004c,004d)[4].(0050,0055)'", std::string("DICOM_0010_0011_\\[(\\d*)\\]_([A-Fa-f\\d]{4})_([A-Fa-f\\d]{4})_0030_0033_(?:004c|004C)_(?:004d|004D)_\\[4\\]_0050_0055"), result);
  }

  void DICOMTagPathToPersistenceKeyTemplate()
  {
    std::string result = mitk::DICOMTagPathToPersistenceKeyTemplate(simplePath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0011)'", std::string("DICOM_0010_0011"), result);
    result = mitk::DICOMTagPathToPersistenceKeyTemplate(deepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0011).(0020,0022).(003A,0033)'", std::string("DICOM_0010_0011_0020_0022_003A_0033"), result);
    result = mitk::DICOMTagPathToPersistenceKeyTemplate(deepPath_withAnyElement);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0011).*.(003a,003f)'", std::string("DICOM_0010_0011_$1_$2_003A_003F"), result);
    result = mitk::DICOMTagPathToPersistenceKeyTemplate(deepPath_withAnySelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0011).(002B,002E)[*].(0030,0033)'", std::string("DICOM_0010_0011_002B_002E_[$1]_0030_0033"), result);
    result = mitk::DICOMTagPathToPersistenceKeyTemplate(deepPath_withSelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0011).(0020,0022)[6].(003b,003e)'", std::string("DICOM_0010_0011_0020_0022_[6]_003B_003E"), result);
    result = mitk::DICOMTagPathToPersistenceKeyTemplate(verydeepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceKeyTemplate() with '(0010,0011)[*].*.(0030,0033).(004c,004d)[4].(0050,0055)'", std::string("DICOM_0010_0011_[$1]_$2_$3_0030_0033_004C_004D_[4]_0050_0055"), result);
  }

  void DICOMTagPathToPersistenceNameTemplate()
  {
    std::string result = mitk::DICOMTagPathToPersistenceNameTemplate(simplePath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0011)'", std::string("DICOM.0010.0011"), result);
    result = mitk::DICOMTagPathToPersistenceNameTemplate(deepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0011).(0020,0022).(003A,0033)'", std::string("DICOM.0010.0011.0020.0022.003A.0033"), result);
    result = mitk::DICOMTagPathToPersistenceNameTemplate(deepPath_withAnyElement);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0011).*.(003a,003f)'", std::string("DICOM.0010.0011.$1.$2.003A.003F"), result);
    result = mitk::DICOMTagPathToPersistenceNameTemplate(deepPath_withAnySelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0011).(002B,002E)[*].(0030,0033)'", std::string("DICOM.0010.0011.002B.002E.[$1].0030.0033"), result);
    result = mitk::DICOMTagPathToPersistenceNameTemplate(deepPath_withSelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0011).(0020,0022)[6].(003b,003e)'", std::string("DICOM.0010.0011.0020.0022.[6].003B.003E"), result);
    result = mitk::DICOMTagPathToPersistenceNameTemplate(verydeepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPersistenceNameTemplate() with '(0010,0011)[*].*.(0030,0033).(004c,004d)[4].(0050,0055)'", std::string("DICOM.0010.0011.[$1].$2.$3.0030.0033.004C.004D.[4].0050.0055"), result);
  }

  void DICOMTagPathToDCMTKSearchPath()
  {
    std::string result = mitk::DICOMTagPathToDCMTKSearchPath(simplePath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToDCMTKSearchPath() with '(0010,0011)'", std::string("(0010,0011)"), result);
    result = mitk::DICOMTagPathToDCMTKSearchPath(deepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToDCMTKSearchPath() with '(0010,0011).(0020,0022).(003A,0033)'", std::string("(0010,0011).(0020,0022).(003A,0033)"), result);
    CPPUNIT_ASSERT_THROW(mitk::DICOMTagPathToDCMTKSearchPath(deepPath_withAnyElement), mitk::Exception);
    result = mitk::DICOMTagPathToDCMTKSearchPath(deepPath_withAnySelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToDCMTKSearchPath() with '(0010,0011).(002B,002E)[*].(0030,0033)'", std::string("(0010,0011).(002B,002E)[*].(0030,0033)"), result);
    result = mitk::DICOMTagPathToDCMTKSearchPath(deepPath_withSelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToDCMTKSearchPath() with '(0010,0011).(0020,0022)[6].(003b,003e)'", std::string("(0010,0011).(0020,0022)[6].(003B,003E)"), result);
    CPPUNIT_ASSERT_THROW(mitk::DICOMTagPathToDCMTKSearchPath(verydeepPath), mitk::Exception);
  }

  void PropertyNameToDICOMTagPath()
  {
    mitk::DICOMTagPath result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0011)'", simplePath, result);
    result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.0020.0022.003A.0033");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0011).(0020,0022).(003A,0033)'", deepPath, result);
    result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.*.003a.003f");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0011).*.(003a,003f)'", deepPath_withAnyElement, result);
    result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.002B.002E.[*].0030.0033");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0011).(002B,002E)[*].(0030,0033)'", deepPath_withAnySelection, result);
    result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.0020.0022.[6].003b.003e");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0011).(0020,0022)[6].(003b,003e)'", deepPath_withSelection, result);
    result = mitk::PropertyNameToDICOMTagPath("DICOM.0010.0011.[*].*.0030.0033.004c.004d.[4].0050.0055");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with '(0010,0011)[*].*.(0030,0033).(004c,004d)[4].(0050,0055)'", verydeepPath, result);

    result = mitk::PropertyNameToDICOMTagPath("WRONG.0010.0011.0020.0022.0030.0033");
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing PropertyNameToDICOMTagPath() with wrong path", emptyPath, result);
  }

  void DICOMTagPathToPropertyName()
  {
    std::string result = mitk::DICOMTagPathToPropertyName(simplePath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0011)'", result, std::string("DICOM.0010.0011"));
    result = mitk::DICOMTagPathToPropertyName(deepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0011).(0020,0022).(003A,0033)'", result, std::string("DICOM.0010.0011.0020.0022.003A.0033"));
    result = mitk::DICOMTagPathToPropertyName(deepPath_withAnyElement);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0011).*.(003a,003f)'", result, std::string("DICOM.0010.0011.*.003A.003F"));
    result = mitk::DICOMTagPathToPropertyName(deepPath_withAnySelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0011).(002B,002E)[*].(0030,0033)'", result, std::string("DICOM.0010.0011.002B.002E.[*].0030.0033"));
    result = mitk::DICOMTagPathToPropertyName(deepPath_withSelection);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0011).(0020,0022)[6].(003b,003e)'", result, std::string("DICOM.0010.0011.0020.0022.[6].003B.003E"));
    result = mitk::DICOMTagPathToPropertyName(verydeepPath);
    CPPUNIT_ASSERT_EQUAL_MESSAGE("Testing DICOMTagPathToPropertyName() with '(0010,0011)[*].*.(0030,0033).(004c,004d)[4].(0050,0055)'", result, std::string("DICOM.0010.0011.[*].*.0030.0033.004C.004D.[4].0050.0055"));
  }

  void ExecutePropertyRegEx()
  {
    std::regex regEx(mitk::DICOMTagPathToPropertyRegEx(simplePath));
    std::string result = mitk::DICOMTagPathToPropertyName(simplePath);
    CPPUNIT_ASSERT(std::regex_match(result, regEx));
    regEx = std::regex(mitk::DICOMTagPathToPropertyRegEx(deepPath));
    result = mitk::DICOMTagPathToPropertyName(deepPath);
    CPPUNIT_ASSERT(std::regex_match(result, regEx));
    regEx = std::regex(mitk::DICOMTagPathToPropertyRegEx(deepPath_withAnyElement));
    result = mitk::DICOMTagPathToPropertyName(deepPath_withAnyElement);
    auto position = result.find("*");
    if (std::string::npos != position)
    {
      result.replace(position, 1, "1234.ABCD");
      CPPUNIT_ASSERT(std::regex_match(result, regEx));
    }
    regEx = std::regex(mitk::DICOMTagPathToPropertyRegEx(deepPath_withAnySelection));
    result = mitk::DICOMTagPathToPropertyName(deepPath_withAnySelection);
    position = result.find("[*]");
    if (std::string::npos != position)
    {
      result.replace(position, 3, "[10]");
      CPPUNIT_ASSERT(std::regex_match(result, regEx));
    }
    regEx = std::regex(mitk::DICOMTagPathToPropertyRegEx(deepPath_withSelection));
    result = mitk::DICOMTagPathToPropertyName(deepPath_withSelection);
    CPPUNIT_ASSERT(std::regex_match(result, regEx));
    regEx = std::regex(mitk::DICOMTagPathToPropertyRegEx(verydeepPath));
    result = mitk::DICOMTagPathToPropertyName(verydeepPath);
    position = result.find("[*]");
    if (std::string::npos != position)
    {
      result.replace(position, 3, "[1]");
      position = result.find("*");
      if (std::string::npos != position)
      {
        result.replace(position, 1, "abcd.1234");
        CPPUNIT_ASSERT(std::regex_match(result, regEx));
      }
    }
  }

  // Regression guard for a defect in the persistence template machinery
  // where GenerateRegExForNumber emits a *capturing* alternation
  // (e.g. "(a170|A170)") for tags whose hex element id contains letters,
  // while DICOMTagPathToPersistenceNameTemplate (and KeyTemplate) assigns
  // $N placeholders assuming only AnySelection / AnyElement nodes
  // produce captures. The placeholder numbering then shifts and the
  // template's selection slot resolves to the element id instead of the
  // numeric index.
  //
  // Concrete repro: AddAnySelection(0x0040, 0xa170) builds a regex with
  // two captures - (a170|A170) plus (\d*) - but the template carries
  // only one placeholder [$1]. Feeding the input "DICOM.0040.a170.[7]"
  // through the regex + template substitution lands "[a170]" in the
  // selection slot, dropping the actual index 7.
  //
  // The test mirrors GenerateFromTemplate's substitution loop in
  // PropertyPersistenceInfo so the check stays close to how the
  // persistence layer actually reconstructs the property name.
  void AnySelectionIndexSurvivesTemplateRoundTrip()
  {
    mitk::DICOMTagPath path;
    path.AddAnySelection(0x0040, 0xa170).AddElement(0x0008, 0x0104);

    const std::string regexStr = mitk::DICOMTagPathToPropertyRegEx(path);
    const std::string templateStr = mitk::DICOMTagPathToPersistenceNameTemplate(path);
    const std::string input = "DICOM.0040.a170.[7].0008.0104";

    std::regex ex(regexStr);
    std::smatch sm;
    CPPUNIT_ASSERT_MESSAGE("Input must match the path's property regex",
                           std::regex_match(input, sm, ex));

    // Replicate GenerateFromTemplate's substitution: replace $1, $2, ...
    // in the template with the corresponding capture group values from
    // the regex match.
    std::string substituted = templateStr;
    for (std::size_t groupID = 1; groupID < sm.size(); ++groupID)
    {
      const std::string placeholder = "\\$" + std::to_string(groupID);
      substituted = std::regex_replace(substituted, std::regex(placeholder), sm[groupID].str());
    }

    CPPUNIT_ASSERT_EQUAL_MESSAGE(
      "The AnySelection's numeric index must survive the regex -> template "
      "round trip and not be overwritten by the element-id capture.",
      std::string("DICOM.0040.A170.[7].0008.0104"),
      substituted);
  }

  void TestOperatorPlusWithTwoPaths()
  {
    mitk::DICOMTagPath path1(0x0010, 0x0011);
    mitk::DICOMTagPath path2(0x0020, 0x0022);

    mitk::DICOMTagPath combined = path1 + path2;

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Combined path should have size 2",
      static_cast<mitk::DICOMTagPath::PathIndexType>(2),
      combined.Size());

    CPPUNIT_ASSERT_EQUAL(0x0010u, combined.GetNode(0).tag.GetGroup());
    CPPUNIT_ASSERT_EQUAL(0x0011u, combined.GetNode(0).tag.GetElement());
    CPPUNIT_ASSERT_EQUAL(0x0020u, combined.GetNode(1).tag.GetGroup());
    CPPUNIT_ASSERT_EQUAL(0x0022u, combined.GetNode(1).tag.GetElement());

    // Original paths should remain unchanged
    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(1), path1.Size());
    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(1), path2.Size());
  }

  void TestOperatorPlusWithString()
  {
    mitk::DICOMTagPath basePath(0x0010, 0x0011); // Patient Name

    // Add another element using string
    mitk::DICOMTagPath extended = basePath + "(0020,0022)"; // Patient ID

    CPPUNIT_ASSERT_EQUAL_MESSAGE("Extended path should have size 2",
      static_cast<mitk::DICOMTagPath::PathIndexType>(2),
      extended.Size());

    CPPUNIT_ASSERT_EQUAL(0x0010u, extended.GetNode(0).tag.GetGroup());
    CPPUNIT_ASSERT_EQUAL(0x0011u, extended.GetNode(0).tag.GetElement());
    CPPUNIT_ASSERT_EQUAL(0x0020u, extended.GetNode(1).tag.GetGroup());
    CPPUNIT_ASSERT_EQUAL(0x0022u, extended.GetNode(1).tag.GetElement());

    // Test with sequence selection
    mitk::DICOMTagPath withSelection = basePath + "(0003,0033)[0]";
    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(2), withSelection.Size());
    CPPUNIT_ASSERT_EQUAL(mitk::DICOMTagPath::NodeInfo::NodeType::SequenceSelection,
      withSelection.GetNode(1).type);
    CPPUNIT_ASSERT_EQUAL(0, withSelection.GetNode(1).selection);
  }

  void TestOperatorPlusReverse()
  {
    mitk::DICOMTagPath path(0x0020, 0x0022);
    std::string prefix = "(0010,0011)";

    mitk::DICOMTagPath combined = prefix + path;

    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(2), combined.Size());

    CPPUNIT_ASSERT_EQUAL(0x0010u, combined.GetNode(0).tag.GetGroup());
    CPPUNIT_ASSERT_EQUAL(0x0011u, combined.GetNode(0).tag.GetElement());
    CPPUNIT_ASSERT_EQUAL(0x0020u, combined.GetNode(1).tag.GetGroup());
    CPPUNIT_ASSERT_EQUAL(0x0022u, combined.GetNode(1).tag.GetElement());
  }

  void TestOperatorPlusEquals()
  {
    mitk::DICOMTagPath path(0x0010, 0x0010); // Patient Name

    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(1), path.Size());

    // Use += with another path
    mitk::DICOMTagPath addition(0x0010, 0x0020);
    path += addition;

    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(2), path.Size());
    CPPUNIT_ASSERT_EQUAL(0x0020u, path.GetNode(1).tag.GetElement());

    // Use += with string
    path += "(0010,0030)"; // Patient Birth Date

    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(3), path.Size());
    CPPUNIT_ASSERT_EQUAL(0x0030u, path.GetNode(2).tag.GetElement());
  }

  void TestOperatorPlusChaining()
  {
    // Test multiple concatenations in one expression
    mitk::DICOMTagPath chained =
      mitk::DICOMTagPath(0x0008, 0x1140) +  // Referenced Image Sequence
      "(0008,1155)" +                       // Referenced SOP Instance UID
      mitk::DICOMTagPath(0x0020, 0x0013);   // Instance Number

    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(3), chained.Size());
    CPPUNIT_ASSERT_EQUAL(0x1140u, chained.GetNode(0).tag.GetElement());
    CPPUNIT_ASSERT_EQUAL(0x1155u, chained.GetNode(1).tag.GetElement());
    CPPUNIT_ASSERT_EQUAL(0x0013u, chained.GetNode(2).tag.GetElement());
  }

  void TestOperatorPlusWithWildcards()
  {
    mitk::DICOMTagPath path;
    path.AddAnySelection(0x0008, 0x1140); // Referenced Image Sequence[*]

    mitk::DICOMTagPath extended = path + "(0008,1155)";

    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(2), extended.Size());
    CPPUNIT_ASSERT_EQUAL(mitk::DICOMTagPath::NodeInfo::NodeType::AnySelection,
      extended.GetNode(0).type);
    CPPUNIT_ASSERT_EQUAL(mitk::DICOMTagPath::NodeInfo::NodeType::Element,
      extended.GetNode(1).type);

    // Test with any element wildcard
    mitk::DICOMTagPath wildcardPath;
    wildcardPath.AddElement(0x0010, 0x0010);
    wildcardPath.AddAnyElement();

    mitk::DICOMTagPath afterWildcard = wildcardPath + mitk::DICOMTagPath(0x0020, 0x0013);
    CPPUNIT_ASSERT_EQUAL(static_cast<mitk::DICOMTagPath::PathIndexType>(3), afterWildcard.Size());
  }


};

MITK_TEST_SUITE_REGISTRATION(mitkDICOMTagPath)
