/**
 * Orthanc - A Lightweight, RESTful DICOM Store
 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
 * Department, University Hospital of Liege, Belgium
 * Copyright (C) 2017-2023 Osimis S.A., Belgium
 * Copyright (C) 2024-2025 Orthanc Team SRL, Belgium
 * Copyright (C) 2021-2025 Sebastien Jodogne, ICTEAM UCLouvain, Belgium
 *
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 **/


#include "PrecompiledHeadersUnitTests.h"
#include <gtest/gtest.h>

#include "../../OrthancFramework/Sources/OrthancException.h"

#include "../Sources/Search/DatabaseLookup.h"

using namespace Orthanc;


TEST(DatabaseLookup, SingleConstraint)
{
  {
    ASSERT_THROW(DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal, 
                                        "HEL*LO", true, true), OrthancException);
    ASSERT_THROW(DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal,
                                        "HEL?LO", true, true), OrthancException);
    ASSERT_THROW(DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal,
                                        true, true), OrthancException);

    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal, "HELLO", true, true);
    ASSERT_TRUE(tag.IsMatch("HELLO"));
    ASSERT_FALSE(tag.IsMatch("hello"));

    ASSERT_TRUE(tag.IsCaseSensitive());
    ASSERT_EQ(ConstraintType_Equal, tag.GetConstraintType());

    DicomMap m;
    ASSERT_FALSE(tag.IsMatch(m));
    m.SetNullValue(DICOM_TAG_PATIENT_NAME);
    ASSERT_FALSE(tag.IsMatch(m));
    m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", true /* binary */);
    ASSERT_FALSE(tag.IsMatch(m));
    m.SetValue(DICOM_TAG_PATIENT_NAME, "HELLO", false /* string */);
    ASSERT_TRUE(tag.IsMatch(m));
  }

  {
    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Equal, "HELlo", false, true);
    ASSERT_TRUE(tag.IsMatch("HELLO"));
    ASSERT_TRUE(tag.IsMatch("hello"));

    ASSERT_EQ("HELlo", tag.GetValue());
  }

  {
    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Wildcard, "HE*L?O", true, true);
    ASSERT_TRUE(tag.IsMatch("HELLO"));
    ASSERT_TRUE(tag.IsMatch("HELLLLLO"));
    ASSERT_TRUE(tag.IsMatch("HELxO"));
    ASSERT_FALSE(tag.IsMatch("hello"));
  }

  {
    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_Wildcard, "HE*l?o", false, true);
    ASSERT_TRUE(tag.IsMatch("HELLO"));
    ASSERT_TRUE(tag.IsMatch("HELLLLLO"));
    ASSERT_TRUE(tag.IsMatch("HELxO"));
    ASSERT_TRUE(tag.IsMatch("hello"));

    ASSERT_FALSE(tag.IsCaseSensitive());
    ASSERT_EQ(ConstraintType_Wildcard, tag.GetConstraintType());
  }

  {
    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_SmallerOrEqual, "123", true, true);
    ASSERT_TRUE(tag.IsMatch("120"));
    ASSERT_TRUE(tag.IsMatch("123"));
    ASSERT_FALSE(tag.IsMatch("124"));
    ASSERT_TRUE(tag.IsMandatory());
  }

  {
    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_GreaterOrEqual, "123", true, false);
    ASSERT_FALSE(tag.IsMatch("122"));
    ASSERT_TRUE(tag.IsMatch("123"));
    ASSERT_TRUE(tag.IsMatch("124"));
    ASSERT_FALSE(tag.IsMandatory());
  }

  {
    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_List, true, true);
    ASSERT_FALSE(tag.IsMatch("CT"));
    ASSERT_FALSE(tag.IsMatch("MR"));

    tag.AddValue("CT");
    ASSERT_TRUE(tag.IsMatch("CT"));
    ASSERT_FALSE(tag.IsMatch("MR"));

    tag.AddValue("MR");
    ASSERT_TRUE(tag.IsMatch("CT"));
    ASSERT_TRUE(tag.IsMatch("MR"));
    ASSERT_FALSE(tag.IsMatch("ct"));
    ASSERT_FALSE(tag.IsMatch("mr"));

    ASSERT_THROW(tag.GetValue(), OrthancException);
    ASSERT_EQ(2u, tag.GetValues().size());
  }

  {
    DicomTagConstraint tag(DICOM_TAG_PATIENT_NAME, ConstraintType_List, false, true);

    tag.AddValue("ct");
    tag.AddValue("mr");

    ASSERT_TRUE(tag.IsMatch("CT"));
    ASSERT_TRUE(tag.IsMatch("MR"));
    ASSERT_TRUE(tag.IsMatch("ct"));
    ASSERT_TRUE(tag.IsMatch("mr"));
  }
}



TEST(DatabaseLookup, FromDicom)
{
  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_ID, "HELLO", true, true);
    ASSERT_EQ(1u, lookup.GetConstraintsCount());
    ASSERT_EQ(ConstraintType_Equal, lookup.GetConstraint(0).GetConstraintType());
    ASSERT_EQ("HELLO", lookup.GetConstraint(0).GetValue());
    ASSERT_TRUE(lookup.GetConstraint(0).IsCaseSensitive());

    ASSERT_TRUE(lookup.HasTag(DICOM_TAG_PATIENT_ID));
    ASSERT_FALSE(lookup.HasTag(DICOM_TAG_PATIENT_NAME));
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_ID, "HELLO", false, true);
    ASSERT_EQ(1u, lookup.GetConstraintsCount());

    // This is *not* a PN VR => "false" above is *not* used
    ASSERT_TRUE(lookup.GetConstraint(0).IsCaseSensitive());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_NAME, "HELLO", true, true);
    ASSERT_EQ(1u, lookup.GetConstraintsCount());
    ASSERT_TRUE(lookup.GetConstraint(0).IsCaseSensitive());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_NAME, "HELLO", false, true);
    ASSERT_EQ(1u, lookup.GetConstraintsCount());

    // This is a PN VR => "false" above is used
    ASSERT_FALSE(lookup.GetConstraint(0).IsCaseSensitive());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_SERIES_DESCRIPTION, "2012-2016", false, true);

    // This is not a data VR
    ASSERT_EQ(ConstraintType_Equal, lookup.GetConstraint(0).GetConstraintType());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "2012-2016", false, true);

    // This is a data VR => range is effective
    ASSERT_EQ(2u, lookup.GetConstraintsCount());

    ASSERT_TRUE(lookup.GetConstraint(0).GetConstraintType() != lookup.GetConstraint(1).GetConstraintType());

    for (size_t i = 0; i < 2; i++)
    {
      ASSERT_TRUE(lookup.GetConstraint(i).GetConstraintType() == ConstraintType_SmallerOrEqual ||
                  lookup.GetConstraint(i).GetConstraintType() == ConstraintType_GreaterOrEqual);
    }
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "2012-", false, true);

    ASSERT_EQ(1u, lookup.GetConstraintsCount());
    ASSERT_EQ(ConstraintType_GreaterOrEqual, lookup.GetConstraint(0).GetConstraintType());
    ASSERT_EQ("2012", lookup.GetConstraint(0).GetValue());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_BIRTH_DATE, "-2016", false, true);

    ASSERT_EQ(1u, lookup.GetConstraintsCount());
    ASSERT_EQ(DICOM_TAG_PATIENT_BIRTH_DATE,  lookup.GetConstraint(0).GetTag());
    ASSERT_EQ(ConstraintType_SmallerOrEqual, lookup.GetConstraint(0).GetConstraintType());
    ASSERT_EQ("2016", lookup.GetConstraint(0).GetValue());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_MODALITIES_IN_STUDY, "CT\\MR", false, true);

    ASSERT_EQ(1u, lookup.GetConstraintsCount());
    ASSERT_EQ(DICOM_TAG_MODALITIES_IN_STUDY,  lookup.GetConstraint(0).GetTag());
    ASSERT_EQ(ConstraintType_List, lookup.GetConstraint(0).GetConstraintType());

    const std::set<std::string>& values = lookup.GetConstraint(0).GetValues();
    ASSERT_EQ(2u, values.size());
    ASSERT_TRUE(values.find("CT") != values.end());
    ASSERT_TRUE(values.find("MR") != values.end());
    ASSERT_TRUE(values.find("nope") == values.end());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "CT\\MR", false, true);

    ASSERT_EQ(1u, lookup.GetConstraintsCount());
    ASSERT_EQ(DICOM_TAG_STUDY_DESCRIPTION, lookup.GetConstraint(0).GetTag());
    ASSERT_EQ(ConstraintType_List, lookup.GetConstraint(0).GetConstraintType());

    const std::set<std::string>& values = lookup.GetConstraint(0).GetValues();
    ASSERT_EQ(2u, values.size());
    ASSERT_TRUE(values.find("CT") != values.end());
    ASSERT_TRUE(values.find("MR") != values.end());
    ASSERT_TRUE(values.find("nope") == values.end());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "HE*O", false, true);

    ASSERT_EQ(1u, lookup.GetConstraintsCount());
    ASSERT_EQ(ConstraintType_Wildcard, lookup.GetConstraint(0).GetConstraintType());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_STUDY_DESCRIPTION, "HE?O", false, true);

    ASSERT_EQ(1u, lookup.GetConstraintsCount());
    ASSERT_EQ(ConstraintType_Wildcard, lookup.GetConstraint(0).GetConstraintType());
  }

  {
    DatabaseLookup lookup;
    lookup.AddDicomConstraint(DICOM_TAG_RELATED_FRAME_OF_REFERENCE_UID, "TEST", false, true);
    lookup.AddDicomConstraint(DICOM_TAG_PATIENT_NAME, "TEST2", false, false);
    ASSERT_TRUE(lookup.GetConstraint(0).IsMandatory());
    ASSERT_FALSE(lookup.GetConstraint(1).IsMandatory());
  }
}
