//=========================================================================
//  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/common/UUID.h"
#include "smtk/model/Vertex.h"
#include "smtk/model/Edge.h"
#include "smtk/model/Face.h"
#include "smtk/model/Volume.h"
#include "smtk/model/VertexUse.h"
#include "smtk/model/EdgeUse.h"
#include "smtk/model/FaceUse.h"
#include "smtk/model/Chain.h"
#include "smtk/model/Loop.h"
#include "smtk/model/Shell.h"
#include "smtk/model/Manager.h"

#include "smtk/model/DescriptivePhrase.h"

#include "smtk/model/testing/cxx/helpers.h"

#include <iomanip>

#if defined(_WIN32) && !defined(__CYGWIN__)
#  include <windows.h> // for GetTickCount()
#else
#  include <sys/time.h> // for gettimeofday()
#  define SMTK_HAVE_GETTIMEOFDAY
#endif

using namespace smtk::common;
using namespace smtk::model;
using smtk::common::UUID;
using smtk::common::UUIDArray;

namespace smtk {
  namespace model {
    namespace testing {

UUIDArray createTet(smtk::model::ManagerPtr sm)
{
  static const double x[][3] = {
      { 0., 0., 0. },
      { 4., 0., 0. },
      { 2., 4., 0. },
      { 1., 1., 0. },
      { 2., 3., 0. },
      { 3., 1., 0. },
      { 2., 0.,-4. },
  };

  UUID uc00 = sm->insertCellOfDimension(0)->first; // keep just the UUID around.
  UUID uc01 = sm->insertCellOfDimension(0)->first;
  UUID uc02 = sm->insertCellOfDimension(0)->first;
  UUID uc03 = sm->insertCellOfDimension(0)->first;
  UUID uc04 = sm->insertCellOfDimension(0)->first;
  UUID uc05 = sm->insertCellOfDimension(0)->first;
  UUID uc06 = sm->insertCellOfDimension(0)->first;

  UUID uc07 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc00).pushRelation(uc01))->first;
  UUID uc08 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc01).pushRelation(uc02))->first;
  UUID uc09 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc02).pushRelation(uc00))->first;
  UUID uc10 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc03).pushRelation(uc04))->first;
  UUID uc11 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc04).pushRelation(uc05))->first;
  UUID uc12 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc05).pushRelation(uc03))->first;
  UUID uc13 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc00).pushRelation(uc06))->first;
  UUID uc14 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc01).pushRelation(uc06))->first;
  UUID uc15 = sm->insertEntity(Entity(CELL_ENTITY, 1).pushRelation(uc02).pushRelation(uc06))->first;

  UUID uc16 = sm->insertEntity(
    Entity(CELL_ENTITY, 2)
    .pushRelation(uc07)
    .pushRelation(uc08)
    .pushRelation(uc09)
    .pushRelation(uc10)
    .pushRelation(uc11)
    .pushRelation(uc12)
    )->first;
  UUID uc17 = sm->insertEntity(
    Entity(CELL_ENTITY, 2)
    .pushRelation(uc10)
    .pushRelation(uc12)
    .pushRelation(uc11)
    )->first;
  UUID uc18 = sm->insertEntity(
    Entity(CELL_ENTITY, 2)
    .pushRelation(uc07)
    .pushRelation(uc13)
    .pushRelation(uc14)
    )->first;
  UUID uc19 = sm->insertEntity(
    Entity(CELL_ENTITY, 2)
    .pushRelation(uc08)
    .pushRelation(uc14)
    .pushRelation(uc15)
    )->first;
  UUID uc20 = sm->insertEntity(
    Entity(CELL_ENTITY, 2)
    .pushRelation(uc09)
    .pushRelation(uc15)
    .pushRelation(uc13)
    )->first;

  UUID uc21 = sm->insertEntity(
    Entity(CELL_ENTITY, 3)
    .pushRelation(uc16)
    .pushRelation(uc17)
    .pushRelation(uc18)
    .pushRelation(uc19)
    .pushRelation(uc20))->first;

  sm->setTessellation(uc21, Tessellation()
    .addCoords(x[0][0], x[0][1], x[0][2])
    .addCoords(x[1][0], x[1][1], x[1][2])
    .addCoords(x[2][0], x[2][1], x[2][2])
    .addCoords(x[3][0], x[3][1], x[3][2])
    .addCoords(x[4][0], x[4][1], x[4][2])
    .addCoords(x[5][0], x[5][1], x[5][2])
    .addCoords(x[6][0], x[6][1], x[6][2])
    .addTriangle(0, 3, 5)
    .addTriangle(0, 5, 1)
    .addTriangle(1, 5, 4)
    .addTriangle(1, 4, 2)
    .addTriangle(2, 4, 3)
    .addTriangle(2, 3, 0)
    .addTriangle(3, 5, 4)
    .addTriangle(0, 6, 1)
    .addTriangle(1, 6, 2)
    .addTriangle(2, 6, 0));

  UUIDArray uids;
  uids.push_back(uc00);
  uids.push_back(uc01);
  uids.push_back(uc02);
  uids.push_back(uc03);
  uids.push_back(uc04);
  uids.push_back(uc05);
  uids.push_back(uc06);
  uids.push_back(uc07);
  uids.push_back(uc08);
  uids.push_back(uc09);
  uids.push_back(uc10);
  uids.push_back(uc11);
  uids.push_back(uc12);
  uids.push_back(uc13);
  uids.push_back(uc14);
  uids.push_back(uc15);
  uids.push_back(uc16);
  uids.push_back(uc17);
  uids.push_back(uc18);
  uids.push_back(uc19);
  uids.push_back(uc20);
  uids.push_back(uc21);

  // Add point coordinates
  for (int i = 0; i < 7; ++i)
    {
    sm->setTessellation(uids[i],Tessellation().addCoords(x[i][0], x[i][1], x[i][2]));
    }

  // Create vertex-uses
  // Because we have a single volume, each vertex should have a single use.
  VertexUses vu(7);
  for (int i = 0; i < 7; ++i)
    {
    vu[i] = sm->addVertexUse(
      /*vert*/ Vertex(sm, uids[i]),
      /*sense*/ 0);
    uids.push_back(vu[i].entity());
    }

  // Create 5 face-uses and shell for 1 volume.
  FaceUses su;
  for (int i = 0; i < 5; ++i)
    {
    su.push_back(
      sm->addFaceUse(
        /*face*/ Face(sm, uids[16 + i]),
        /*sense*/ 0,
        /*orientation*/ NEGATIVE));
    uids.push_back(su.back().entity());
    }
  Shell sh = sm->addShell(Volume(sm, uc21)).addUses(su);
  uids.push_back(sh.entity());

  // Create edge-uses and loops (6 of them) for 5 faces.
  EdgeUses lu[6];
  Loop lp[6];
  Chains chains;

  int ee[9][2] = { // Edge endpoints
      {0, 1},
      {1, 2},
      {2, 0},
      {3, 4},
      {4, 5},
      {5, 3},
      {0, 6},
      {1, 6},
      {2, 6},
  };
  int eul[6][3] = { // edge-use-to-loop uid offsets
      { 9,  8,  7},
      {10, 11, 12},
      {12, 11, 10},
      { 7, 14, 13},
      { 8, 15, 14},
      { 9, 13, 15}
  };
  int eus[6][3] = { // edge-use sense
      {0, 0, 0},
      {0, 0, 0},
      {1, 1, 1},
      {1, 1, 0},
      {1, 1, 0},
      {1, 1, 0}
  };
  Orientation euo[6][3] = { // edge-use orientations
      {NEGATIVE, NEGATIVE, NEGATIVE},
      {NEGATIVE, NEGATIVE, NEGATIVE},
      {POSITIVE, POSITIVE, POSITIVE},
      {POSITIVE, POSITIVE, NEGATIVE},
      {POSITIVE, POSITIVE, NEGATIVE},
      {POSITIVE, POSITIVE, NEGATIVE}
  };
  for (int i = 0; i < 6; ++i)
    {
    for (int j = 0; j < 3; ++j)
      {
      lu[i].push_back(
        sm->addEdgeUse(
          /*edge*/ Edge(sm, uids[eul[i][j]]),
          /*sense*/ eus[i][j],
          /*orientation*/ euo[i][j]));
      chains.push_back(
        sm->addChain(lu[i].back())
          .addUse(vu[ee[eul[i][j] - 7][euo[i][j] == POSITIVE ? 0 : 1]])
          .addUse(vu[ee[eul[i][j] - 7][euo[i][j] == POSITIVE ? 1 : 0]]));
      uids.push_back(lu[i][j].entity());
      uids.push_back(chains.back().entity());
      }
    // Add the loops, remembering that the second
    // loop is a hole and is thus a child of another loop:
    if (i == 0)
      {
      lp[i] = sm->addLoop(su[0]).addUses(lu[i]).as<Loop>();
      }
    else if (i == 1)
      {
      lp[i] = sm->addLoop(lp[0]).addUses(lu[i]).as<Loop>();
      }
    else
      { // note special index uids[15+i] b/c the hole-loop is repeated:
      lp[i] = sm->addLoop(su[i - 1]).addUses(lu[i]).as<Loop>();
      }
    uids.push_back(lp[i].entity());
    }

  return uids;
}

/** Report an integer as a hexadecimal value.
  *
  * The constant will be zero-padded to a width of 8.
  */
std::ostream& operator << (std::ostream& os, const hexconst& x)
{
  os << std::setbase(16) << std::fixed << std::setw(8) << std::setfill('0') << x.m_val;
  return os;
}

/// Platform-specific data for measuring elapsed time.
#ifdef SMTK_HAVE_GETTIMEOFDAY
class Timer::Internal
{
public:
  struct timeval m_mark;
  Internal()
    {
    this->fetch();
    }
  void fetch()
    {
    gettimeofday(&this->m_mark, NULL);
    }
};
#else
class Timer::Internal
{
public:
  unsigned long long m_mark;
  Internal()
    {
    this->fetch();
    }
  void fetch()
    {
    m_mark = GetTickCount64();
    }
};
#endif

Timer::Timer()
{
  this->P = new Internal;
}

Timer::~Timer()
{
  delete this->P;
}

/// Mark a time as the start time for reporting elapsed time.
void Timer::mark()
{
  this->P->fetch();
}

/** Report the number of seconds since Timer::mark() was last called.
  *
  * On sane platforms, this will have microsecond precision.
  * On Windows, this will have millisecond precision at best.
  */
double Timer::elapsed()
{
  Internal other;
#ifdef SMTK_HAVE_GETTIMEOFDAY
  double result = other.m_mark.tv_sec - this->P->m_mark.tv_sec;
  result += 1e-6 * (other.m_mark.tv_usec - this->P->m_mark.tv_usec);
#else
  double result = 1e-3 * (other.m_mark - this->P->m_mark);
#endif
  return result;
}

static int maxIndent = 20;

void printPhrase(std::ostream& os, int indent, DescriptivePhrase::Ptr p)
{
  // Do not descend too far, as infinite recursion is possible,
  // even with the SimpleSubphraseGenerator
  if (indent > maxIndent)
    return;

  os << std::string(indent, ' ') << p->title() << "  (" << p->subtitle() << ")";
  FloatList rgba = p->relatedColor();
  if (rgba[3] >= 0.)
    os << " rgba(" << rgba[0] << "," << rgba[1] << "," << rgba[2] << "," << rgba[3] << ")";
  os << "\n";
  DescriptivePhrases sub = p->subphrases();
  indent += 2;
  for (DescriptivePhrases::iterator it = sub.begin(); it != sub.end(); ++it)
    {
    printPhrase(os, indent, *it);
    }
}

    } // namespace testing
  } // namespace model
} // namespace smtk
