//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================

#include "smtk/session/aeva/Predicates.h"
#include "smtk/session/aeva/Registrar.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.h"

#include "smtk/session/aeva/operators/EditFreeformAttributes.h"
#include "smtk/session/aeva/operators/Read.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/VoidItem.h"

#include "smtk/model/EntityRef.h"
#include "smtk/model/Face.h"
#include "smtk/model/Model.h"

#include "smtk/resource/Manager.h"

#include "smtk/operation/Manager.h"

#include "smtk/common/TypeName.h"

#include "vtkDataObject.h"
#include "vtkDataSetAttributes.h"
#include "vtkIntArray.h"

namespace
{
std::string dataRoot = AEVA_DATA_DIR;

smtk::resource::Manager::Ptr resourceManager;
smtk::operation::Manager::Ptr operationManager;

std::shared_ptr<smtk::session::aeva::Resource> LoadTestModel()
{
  std::shared_ptr<smtk::session::aeva::Resource> resource;
  // Create a read operation
  smtk::session::aeva::Read::Ptr readOp = operationManager->create<smtk::session::aeva::Read>();
  if (!readOp)
  {
    std::cerr << "  No read operation\n";
    return resource;
  }

  // Set the file path
  std::string testFilePath(dataRoot);
  testFilePath += "/smtk/simple.smtk";
  readOp->parameters()->findFile("filename")->setValue(testFilePath);

  // Test the ability to operate
  if (!readOp->ableToOperate())
  {
    std::cerr << "  Import operation unable to operate\n";
    return resource;
  }

  // Execute the operation
  smtk::operation::Operation::Result readOpResult = readOp->operate();
  if (readOpResult->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Read operation failed\n";
    return resource;
  }

  auto resourceItem = readOpResult->findResource("resource");
  resource = std::dynamic_pointer_cast<smtk::session::aeva::Resource>(resourceItem->value());
  if (!resource || !resource->session())
  {
    std::cerr << "  Resulting resource is invalid\n";
    return resource;
  }

  return resource;
}

bool GetTestEntities(const smtk::session::aeva::Resource::Ptr& resource,
  std::shared_ptr<smtk::model::Entity>& primary,
  std::shared_ptr<smtk::model::Entity>& all,
  std::shared_ptr<smtk::model::Entity>& lf,
  std::shared_ptr<smtk::model::Entity>& md,
  std::shared_ptr<smtk::model::Entity>& rt)
{
  smtk::resource::Component::Visitor visitor =
    [&](const smtk::resource::Component::Ptr& comp) -> void {
    std::string name;
    auto ent = std::dynamic_pointer_cast<smtk::model::Entity>(comp);
    if (ent)
    {
      name = ent->name();
      if (name == "primary")
      {
        primary = ent;
      }
      else if (name == "all")
      {
        all = ent;
      }
      else if (name == "lf")
      {
        lf = ent;
      }
      else if (name == "md")
      {
        md = ent;
      }
      else if (name == "rt")
      {
        rt = ent;
      }
    }
  };
  resource->visit(visitor);
  return primary && all && lf && md && rt;
}

template<typename T>
bool SetItemValue(smtk::session::aeva::EditFreeformAttributes::Ptr& /*op*/, const T& /*value*/)
{
  std::cerr << "Error: unknown item type " << smtk::common::typeName<T>() << "\n";
  return false;
}

template<>
bool SetItemValue<std::string>(smtk::session::aeva::EditFreeformAttributes::Ptr& op,
  const std::string& value)
{
  smtk::attribute::ItemPtr removeItem = op->parameters()->find("remove");
  removeItem->setIsEnabled(false);
  auto typeItem = op->parameters()->findInt("type");
  typeItem->setValue(0); // Indicates we are dealing with a string property
  auto stringItem =
    std::dynamic_pointer_cast<smtk::attribute::StringItem>(typeItem->activeChildItem(0));
  stringItem->setValue(value);
  return true;
}

template<>
bool SetItemValue<double>(smtk::session::aeva::EditFreeformAttributes::Ptr& op, const double& value)
{
  smtk::attribute::ItemPtr removeItem = op->parameters()->find("remove");
  removeItem->setIsEnabled(false);
  auto typeItem = op->parameters()->findInt("type");
  typeItem->setValue(1); // Indicates we are dealing with a double property
  auto doubleItem =
    std::dynamic_pointer_cast<smtk::attribute::DoubleItem>(typeItem->activeChildItem(0));
  doubleItem->setValue(value);
  return true;
}

template<>
bool SetItemValue<long>(smtk::session::aeva::EditFreeformAttributes::Ptr& op, const long& value)
{
  smtk::attribute::ItemPtr removeItem = op->parameters()->find("remove");
  removeItem->setIsEnabled(false);
  auto typeItem = op->parameters()->findInt("type");
  typeItem->setValue(2); // Indicates we are dealing with a integer property
  auto intItem = std::dynamic_pointer_cast<smtk::attribute::IntItem>(typeItem->activeChildItem(0));
  intItem->setValue(value);
  return true;
}

template<typename T>
bool UnsetItemValue(smtk::session::aeva::EditFreeformAttributes::Ptr& /*op*/)
{
  std::cerr << "Error: unknown item type " << smtk::common::typeName<T>() << "\n";
  return false;
}

template<>
bool UnsetItemValue<std::string>(smtk::session::aeva::EditFreeformAttributes::Ptr& op)
{
  smtk::attribute::ItemPtr removeItem = op->parameters()->find("remove");
  removeItem->setIsEnabled(true);
  auto typeItem = op->parameters()->findInt("type");
  typeItem->setValue(0); // Indicates we are dealing with a string property
  return true;
}

template<>
bool UnsetItemValue<double>(smtk::session::aeva::EditFreeformAttributes::Ptr& op)
{
  smtk::attribute::ItemPtr removeItem = op->parameters()->find("remove");
  removeItem->setIsEnabled(true);
  auto typeItem = op->parameters()->findInt("type");
  typeItem->setValue(1); // Indicates we are dealing with a double property
  return true;
}

template<>
bool UnsetItemValue<long>(smtk::session::aeva::EditFreeformAttributes::Ptr& op)
{
  smtk::attribute::ItemPtr removeItem = op->parameters()->find("remove");
  removeItem->setIsEnabled(true);
  auto typeItem = op->parameters()->findInt("type");
  typeItem->setValue(2); // Indicates we are dealing with a int property
  return true;
}

int EmptyName()
{
  std::cout << "Testing empty property name\n";
  auto resource = LoadTestModel();
  std::shared_ptr<smtk::model::Entity> primary;
  std::shared_ptr<smtk::model::Entity> all;
  std::shared_ptr<smtk::model::Entity> lf;
  std::shared_ptr<smtk::model::Entity> md;
  std::shared_ptr<smtk::model::Entity> rt;
  if (!GetTestEntities(resource, primary, all, lf, md, rt))
  {
    return 1;
  }

  auto editOp = operationManager->create<smtk::session::aeva::EditFreeformAttributes>();
  if (!editOp)
  {
    std::cerr << "  No edit operation.\n";
    return 1;
  }

  auto assoc = editOp->parameters()->associations();
  if (!assoc->appendValue(lf))
  {
    std::cerr << "  Could not edit 'lf'\n";
    return 1;
  }

  auto result = editOp->operate();
  if (result->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::UNABLE_TO_OPERATE))
  {
    std::cerr << "  Edit operation should have been unable to proceed without a name\n";
    return 1;
  }

  return 0;
}

template<typename T>
int CreateModifyRemoveAttributes(const std::string& name, const T& value, const T& modifiedValue)
{
  std::cout << "Testing attributes of type <" << smtk::common::typeName<T>() << ">\n";
  auto resource = LoadTestModel();
  std::shared_ptr<smtk::model::Entity> primary;
  std::shared_ptr<smtk::model::Entity> all;
  std::shared_ptr<smtk::model::Entity> lf;
  std::shared_ptr<smtk::model::Entity> md;
  std::shared_ptr<smtk::model::Entity> rt;
  if (!GetTestEntities(resource, primary, all, lf, md, rt))
  {
    return 1;
  }

  auto editOp = operationManager->create<smtk::session::aeva::EditFreeformAttributes>();
  if (!editOp)
  {
    std::cerr << "  No edit operation.\n";
    return 1;
  }

  auto assoc = editOp->parameters()->associations();
  if (!assoc->appendValue(lf))
  {
    std::cerr << "  Could not edit 'lf'\n";
    return 1;
  }

  editOp->parameters()->findString("name")->setValue(name);
  if (!SetItemValue(editOp, value))
  {
    return 1;
  }
  auto result = editOp->operate();
  if (result->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Edit operation should have succeeded\n";
    return 1;
  }

  if (result->findComponent("modified")->numberOfValues() != 1 ||
    result->findComponent("modified")->value(0) != lf)
  {
    std::cerr << "  Modified entity not reported.\n";
    return 1;
  }

  if (!lf->properties().get<T>().contains(name) || lf->properties().at<T>(name) != value)
  {
    std::cerr << "  Edit operation should have created the attribute\n";
    return 1;
  }

  // Test modification
  if (!SetItemValue(editOp, modifiedValue))
  {
    return 1;
  }
  result = editOp->operate();
  if (result->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Edit operation should have succeeded\n";
    return 1;
  }

  if (result->findComponent("modified")->numberOfValues() != 1 ||
    result->findComponent("modified")->value(0) != lf)
  {
    std::cerr << "  Modified entity not reported.\n";
    return 1;
  }

  if (!lf->properties().get<T>().contains(name) || lf->properties().at<T>(name) != modifiedValue)
  {
    std::cerr << "  Edit operation should have modified the attribute\n";
    return 1;
  }

  // Test erasure
  if (!UnsetItemValue<T>(editOp))
  {
    return 1;
  }
  result = editOp->operate();
  if (result->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "  Edit operation should have succeeded\n";
    return 1;
  }

  if (result->findComponent("modified")->numberOfValues() != 1 ||
    result->findComponent("modified")->value(0) != lf)
  {
    std::cerr << "  Modified entity not reported.\n";
    return 1;
  }

  if (lf->properties().get<T>().contains(name))
  {
    std::cerr << "  Edit operation should have removed the attribute\n";
    return 1;
  }

  return 0;
}

}

int TestEditFreeformAttributes(int argc, char* argv[])
{
  (void)argc;
  (void)argv;
  int result = 0;

  resourceManager = smtk::resource::Manager::create();
  {
    smtk::session::aeva::Registrar::registerTo(resourceManager);
  }
  operationManager = smtk::operation::Manager::create();
  {
    smtk::session::aeva::Registrar::registerTo(operationManager);
  }
  operationManager->registerResourceManager(resourceManager);

  // Create an unnamed property (should fail).
  result |= EmptyName();

  // Create new properties, modify them, remove them.
  result |= CreateModifyRemoveAttributes("foo", std::string("bar"), std::string("baz"));
  result |= CreateModifyRemoveAttributes("answer", static_cast<long>(42), static_cast<long>(39));
  result |= CreateModifyRemoveAttributes("favorites", 2.71828, 3.14159);

  return result;
}
