/*
 * Copyright (C) 2015 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

#include "gazebo/test/ServerFixture.hh"
#include "test/util.hh"
#include "gazebo/physics/PhysicsTypes.hh"
#include "gazebo/physics/World.hh"
#include "gazebo/physics/WorldState.hh"

using namespace gazebo;

class WorldStateTest : public ServerFixture { };

//////////////////////////////////////////////////
TEST_F(WorldStateTest, SDFConstructor)
{
  // Create the state sdf
  std::ostringstream sdfStr;
  sdfStr << "<sdf version ='" << SDF_VERSION << "'>"
    << "<world name='default'>"
    << "<state world_name='world_state_name'>"
    << "<model name='ground_plane'>"
    << "  <pose>1 1 1 0 0 0</pose>"
    << "  <link name='link'>"
    << "    <pose>0.1 0.2 0.3 0.4 0.5 0.6</pose>"
    << "  </link>"
    << "</model>"
    << "<model name='model_1'>"
    << "  <pose>2 1 1 0 0 0</pose>"
    << "  <link name='link_1'>"
    << "  </link>"
    << "</model>"
    << "<light name='sun'>"
    << "  <pose>10 20 30 0 0 0</pose>"
    << "</light>"
    << "<light name='light_1'>"
    << "  <pose>1 2 3 0 0 0</pose>"
    << "</light>"
    << "</state>"
    << "</world>"
    << "</sdf>";

  sdf::SDFPtr worldSDF(new sdf::SDF);

  worldSDF->SetFromString(sdfStr.str());
  EXPECT_TRUE(worldSDF->Root()->HasElement("world"));

  auto worldElem = worldSDF->Root()->GetElement("world");
  EXPECT_TRUE(worldElem->HasElement("state"));

  // Create the world state
  physics::WorldState worldState(worldElem->GetElement("state"));

  // Check world state against values from the sdf string
  EXPECT_EQ(worldState.GetModelStateCount(), 2u);
  EXPECT_EQ(worldState.LightStateCount(), 2u);

  auto modelStates = worldState.GetModelStates();
  EXPECT_EQ(modelStates.size(), 2u);
  EXPECT_EQ(modelStates["ground_plane"].Pose(),
      ignition::math::Pose3d(1, 1, 1, 0, 0, 0));
  EXPECT_EQ(modelStates["model_1"].Pose(),
      ignition::math::Pose3d(2, 1, 1, 0, 0, 0));
  EXPECT_EQ(modelStates["fake_model"].Pose(),
      ignition::math::Pose3d(0, 0, 0, 0, 0, 0));

  auto lightStates = worldState.LightStates();
  EXPECT_EQ(lightStates.size(), 2u);
  EXPECT_EQ(lightStates["sun"].Pose(),
      ignition::math::Pose3d(10, 20, 30, 0, 0, 0));
  EXPECT_EQ(lightStates["light_1"].Pose(),
      ignition::math::Pose3d(1, 2, 3, 0, 0, 0));
  EXPECT_EQ(lightStates["fake_light"].Pose(),
      ignition::math::Pose3d(0, 0, 0, 0, 0, 0));
}

//////////////////////////////////////////////////
TEST_F(WorldStateTest, WorldConstructor)
{
  // Load a world
  this->Load("worlds/empty.world", true);
  physics::WorldPtr world = physics::get_world("default");

  // Create the world state
  physics::WorldState worldState(world);

  // Check world state
  EXPECT_EQ(worldState.GetModelStateCount(), 1u);
  EXPECT_EQ(worldState.LightStateCount(), 1u);

  auto modelStates = worldState.GetModelStates();
  EXPECT_EQ(modelStates.size(), 1u);
  EXPECT_EQ(modelStates["ground_plane"].Pose(),
      ignition::math::Pose3d(0, 0, 0, 0, 0, 0));

  auto lightStates = worldState.LightStates();
  EXPECT_EQ(lightStates.size(), 1u);
  EXPECT_EQ(lightStates["sun"].Pose(),
      ignition::math::Pose3d(0, 0, 10, 0, 0, 0));
}

//////////////////////////////////////////////////
TEST_F(WorldStateTest, FillSDF)
{
  // Load a world
  this->Load("worlds/empty.world", true);
  physics::WorldPtr world = physics::get_world("default");

  // Create the world state
  physics::WorldState worldState(world);

  // Fill SDF
  sdf::ElementPtr filledSDF(new sdf::Element);
  sdf::initFile("state.sdf", filledSDF);
  worldState.FillSDF(filledSDF);

  EXPECT_TRUE(filledSDF->HasAttribute("world_name"));
  EXPECT_EQ(filledSDF->Get<std::string>("world_name"),
      "default");

  EXPECT_TRUE(filledSDF->HasElement("model"));
  EXPECT_EQ(filledSDF->GetElement("model")->Get<std::string>("name"),
      "ground_plane");

  EXPECT_TRUE(filledSDF->HasElement("light"));
  EXPECT_EQ(filledSDF->GetElement("light")->Get<std::string>("name"),
      "sun");
}

//////////////////////////////////////////////////
TEST_F(WorldStateTest, OperatorsNoInsertionsDeletions)
{
  // Load a world
  this->Load("worlds/shapes.world", true);
  physics::WorldPtr world = physics::get_world("default");

  // Create a world state
  physics::WorldState worldState0(world);

  // Create the "next state"
  // Only checking a case without insertions or deletions
  std::ostringstream sdfStr;
  sdfStr << "<sdf version ='" << SDF_VERSION << "'>"
    << "<world name='default'>"
    << "<state world_name='default'>"
    << "<model name='ground_plane'>"
    << "  <pose>0 0 0 0 0 0</pose>"
    << "</model>"
    << "<model name='box'>"
    << "  <pose>0 0 0.5 0 0 0</pose>"
    << "</model>"
    << "<model name='sphere'>"
    << "  <pose>2 4 6 0 0 0</pose>"
    << "</model>"
    << "<model name='cylinder'>"
    << "  <pose>3 6 9 0 1.5707 0</pose>"
    << "</model>"
    << "<light name='sun'>"
    << "  <pose>10 20 30 0 0 0</pose>"
    << "</light>"
    << "</state>"
    << "</world>"
    << "</sdf>";

  sdf::SDFPtr worldSDF(new sdf::SDF);

  worldSDF->SetFromString(sdfStr.str());
  EXPECT_TRUE(worldSDF->Root()->HasElement("world"));

  auto worldElem = worldSDF->Root()->GetElement("world");
  EXPECT_TRUE(worldElem->HasElement("state"));

  physics::WorldState worldState1(worldElem->GetElement("state"));

  // Check that the 2 states are of the same world, with same entities
  EXPECT_EQ(worldState0.GetName(), worldState1.GetName());
  EXPECT_EQ(worldState0.GetModelStateCount(), worldState1.GetModelStateCount());
  EXPECT_EQ(worldState0.LightStateCount(), worldState1.LightStateCount());

  // Check subtraction
  auto worldState2 = worldState1 - worldState0;
  EXPECT_FALSE(worldState2.IsZero());

  // Check there were no deletions or insertions
  auto deletions = worldState2.Deletions();
  EXPECT_EQ(deletions.size(), 0u);

  auto insertions = worldState2.Insertions();
  EXPECT_EQ(insertions.size(), 0u);

  // Check that entities which are the same in both states were removed
  EXPECT_EQ(worldState2.GetModelStateCount(), 2u);
  EXPECT_EQ(worldState2.LightStateCount(), 1u);

  auto modelStates = worldState2.GetModelStates();
  EXPECT_EQ(modelStates.size(), 2u);
  EXPECT_EQ(modelStates["sphere"].Pose(),
      ignition::math::Pose3d(2, 2.5, 5.5, 0, 0, 0));
  EXPECT_EQ(modelStates["cylinder"].Pose(),
      ignition::math::Pose3d(3, 7.5, 8.5, 0, 0, 0));
  EXPECT_EQ(modelStates["fake_model"].Pose(),
      ignition::math::Pose3d(0, 0, 0, 0, 0, 0));

  auto lightStates = worldState2.LightStates();
  EXPECT_EQ(lightStates.size(), 1u);
  EXPECT_EQ(lightStates["sun"].Pose(),
      ignition::math::Pose3d(10, 20, 20, 0, 0, 0));
  EXPECT_EQ(lightStates["fake_light"].Pose(),
      ignition::math::Pose3d(0, 0, 0, 0, 0, 0));

  // Check addition
  worldState2 = worldState1 + worldState0;
  EXPECT_FALSE(worldState2.IsZero());

  // Check there were no deletions or insertions
  deletions = worldState2.Deletions();
  EXPECT_EQ(deletions.size(), 0u);

  insertions = worldState2.Insertions();
  EXPECT_EQ(insertions.size(), 0u);

  // Check that all entities are present
  EXPECT_EQ(worldState2.GetModelStateCount(), 4u);
  EXPECT_EQ(worldState2.LightStateCount(), 1u);

  modelStates = worldState2.GetModelStates();
  EXPECT_EQ(modelStates.size(), 4u);
  EXPECT_EQ(modelStates["ground_plane"].Pose(),
      ignition::math::Pose3d(0, 0, 0, 0, 0, 0));
  EXPECT_EQ(modelStates["box"].Pose(),
      ignition::math::Pose3d(0, 0, 1, 0, 0, 0));
  EXPECT_EQ(modelStates["sphere"].Pose(),
      ignition::math::Pose3d(2, 5.5, 6.5, 0, 0, 0));
  EXPECT_EQ(modelStates["cylinder"].Pose().Pos(),
      ignition::math::Vector3d(3, 4.5, 9.5));

  lightStates = worldState2.LightStates();
  EXPECT_EQ(lightStates.size(), 1u);
  EXPECT_EQ(lightStates["sun"].Pose(),
      ignition::math::Pose3d(10, 20, 40, 0, 0, 0));

  // Copy by assignment
  worldState1 = worldState0;

  // Check states are equal
  EXPECT_EQ(worldState0.GetName(), worldState1.GetName());
  EXPECT_TRUE((worldState0 - worldState1).IsZero());
}

//////////////////////////////////////////////////
TEST_F(WorldStateTest, InsertionOfMeshModel)
{
  // Load a world
  this->Load("worlds/empty.world", true);
  physics::WorldPtr world = physics::get_world("default");

  // Create a world state
  physics::WorldState worldState0(world);

  // Insert a model with a scaled mesh
  msgs::Factory msg;
  std::ostringstream newModelStr;

  newModelStr << "<sdf version='" << SDF_VERSION << "'>"
    << "<model name ='test_model'>"
    << "<pose>2 2 2 0.1 0.1 0.1</pose>"
    << "<link name ='link'>"
    << "  <collision name ='collision'>"
    << "    <geometry>"
    << "      <mesh>"
    << "        <uri>" << std::string(PROJECT_SOURCE_PATH)
                       << "/test/data/box_offset.dae</uri>"
    << "        <scale>0.2 0.3 0.4</scale>"
    << "      </mesh>"
    << "    </geometry>"
    << "  </collision>"
    << "  <visual name ='visual'>"
    << "    <geometry>"
    << "      <mesh>"
    << "        <uri>" << std::string(PROJECT_SOURCE_PATH)
                       << "/test/data/box_offset.dae</uri>"
    << "        <scale>0.5 0.6 0.7</scale>"
    << "      </mesh>"
    << "    </geometry>"
    << "  </visual>"
    << "</link>"
    << "</model>"
    << "</sdf>";

  msg.set_sdf(newModelStr.str());
  this->factoryPub->Publish(msg);

  this->WaitUntilEntitySpawn("test_model", 100, 100);
  ASSERT_TRUE(world->ModelByName("test_model") != nullptr);

  // Create a new world state
  physics::WorldState worldState1(world);

  // Check that the 2nd state has more entities
  EXPECT_EQ(worldState0.GetModelStateCount(), 1u);
  EXPECT_EQ(worldState1.GetModelStateCount(), 2u);

  // Difference between states to generate insertion
  auto worldState2 = worldState1 - worldState0;
  EXPECT_FALSE(worldState2.IsZero());

  // Check there were no deletions
  auto deletions = worldState2.Deletions();
  EXPECT_EQ(deletions.size(), 0u);

  // Check the old entity disappears and the new entity appears as insertion
  EXPECT_EQ(worldState2.GetModelStateCount(), 0u);
  auto insertions = worldState2.Insertions();
  EXPECT_EQ(insertions.size(), 1u);

  // Check that inserted entity's scale wasn't changed
  auto inserted = insertions[0];
  EXPECT_TRUE(inserted.find("<scale>0.2 0.3 0.4</scale>") != std::string::npos);
  EXPECT_TRUE(inserted.find("<scale>0.5 0.6 0.7</scale>") != std::string::npos);
}

//////////////////////////////////////////////////
TEST_F(WorldStateTest, Times)
{
  // Load a world
  this->Load("worlds/empty.world", true);
  physics::WorldPtr world = physics::get_world("default");

  // Create the world state
  physics::WorldState worldState(world);

  // Check default times
  EXPECT_EQ(worldState.GetSimTime(), common::Time(0));
  EXPECT_GT(worldState.GetWallTime(), common::Time(0));
  EXPECT_EQ(worldState.GetRealTime(), common::Time(0));

  // Set times
  worldState.SetSimTime(common::Time(1));
  worldState.SetWallTime(common::Time(2));
  worldState.SetRealTime(common::Time(3));

  // Check times
  EXPECT_EQ(worldState.GetSimTime(), common::Time(1));
  EXPECT_EQ(worldState.GetWallTime(), common::Time(2));
  EXPECT_EQ(worldState.GetRealTime(), common::Time(3));
}
