//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2018 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "MiscProcessAnimate.hpp"

using qglviewer::Vec;

namespace mgx 
{
  struct Animate
  {
    Point3d pos[7];
    Quaternion orient[7];
    VisFlags flags[7];
    double zoom;
    Point3d center;
    int steps;
  };

  std::vector<Animate> animVec;

  bool AnimationAddKeyFrame::run(int steps)
  {
    if(steps <= 0)
      throw QString("%1::run Steps must be > 0k").arg(name());

    qglviewer::Frame *frames[7] = {camera()->frame(), &stack(0)->frame(), &stack(1)->frame(), 
                                 &clip1()->frame(), &clip2()->frame(), &clip3()->frame(), &cuttingSurface()->frame()};

    QStringList names = QStringList() << "Camera" << "Stack1" << "Stack2" << "Clip1" << "Clip2" << "Clip3" << "CutSurf";

    Animate anim;
    for(int i = 0; i < 7; i++) {
      // All have position and orientation
      anim.pos[i] = Point3d(frames[i]->position());
      anim.orient[i] = Point4d(frames[i]->orientation());
      // All have flags except camera
      if(names[i] == "Stack1") {
        anim.flags[i].stackFlags.main = stack(0)->main()->isVisible();
        anim.flags[i].stackFlags.work = stack(0)->work()->isVisible();
      } else if(names[i] == "Stack2") {
        anim.flags[i].stackFlags.main = stack(1)->main()->isVisible();
        anim.flags[i].stackFlags.work = stack(1)->work()->isVisible();
      } else if(names[i] == "Clip1") {
        anim.flags[i].clipFlags.enabled = clip1()->enabled();
        anim.flags[i].clipFlags.grid = clip1()->grid();
      } else if(names[i] == "Clip2") {
        anim.flags[i].clipFlags.enabled = clip2()->enabled();
        anim.flags[i].clipFlags.grid = clip2()->grid();
      } else if(names[i] == "Clip3") {
        anim.flags[i].clipFlags.enabled = clip3()->enabled();
        anim.flags[i].clipFlags.grid = clip3()->grid();
      } else if(names[i] == "CutSurf") {
        anim.flags[i].cutSurfFlags.enabled = cuttingSurface()->isVisible();
        anim.flags[i].cutSurfFlags.grid = cuttingSurface()->drawGrid();
      }
    }
    // Extra fields for camera
    anim.zoom = camera()->zoom();
    anim.center = Point3d(camera()->sceneCenter());
    anim.steps = steps;

    animVec.push_back(anim);

    return true;

  }
  REGISTER_PROCESS(AnimationAddKeyFrame);

  bool AnimationPlay::run(bool loop)
  {
    int keyFrames = animVec.size();
    if(keyFrames < 2)
      throw QString("%1::run There must be at least 2 key frames").arg(name());

    QStringList names = QStringList() << "Camera" << "Stack1" << "Stack2" << "Clip1" << "Clip2" << "Clip3" << "CutSurf";

    Animate prevAnimate;

    Point3d prevPos[7];
    Quaternion prevOrient[7];
    ulong prevFlags[7];
    double prevZoom = 0;
    Point3d prevCenter;
    int i = 0;
    do {
      auto &anim1 = animVec[i];
      auto &anim2 = i >= keyFrames - 1 ? animVec[0] : animVec[i+1];
      
      // Separate translation from position due to rotation
      const Point3d zAxis = Point3d(0, 0, 1.0);
      Point3d viewDir1 = normalized(anim1.orient[0].rotate(zAxis));
      Point3d viewDir2 = normalized(anim2.orient[0].rotate(zAxis));
      Point3d viewPos1 = viewDir1 * (anim1.pos[0] * viewDir1); 
      Point3d viewPos2 = viewDir2 * (anim2.pos[0] * viewDir2); 
      Point3d translate1 = anim1.orient[0].inverseRotate(anim1.pos[0] - viewPos1);
      Point3d translate2 = anim2.orient[0].inverseRotate(anim2.pos[0] - viewPos2);

      for(int j = 0; j <= anim2.steps; j++) {
        if(!progressAdvance())
          userCancel();

        // If not on first step, skip first frame (same as previous last)
        bool firstStep = i == 0 and j == 0;
        if(!firstStep and j == 0)
          j++;

        // Find interpolation amount
        double s = double(j)/double(anim2.steps);

        // Loop over manipulated frames 
        for(int k = 0; k < 7; k++) { 
          Point3d pos = anim1.pos[k] * (1.0 - s) + anim2.pos[k] * s;
          Quaternion orient = slerp(anim1.orient[k], anim2.orient[k], s);
          VisFlags flags = (j == 0) ? anim1.flags[k] : anim2.flags[k];

          // Camera
          if(k == 0) {
            double zoom = anim1.zoom * (1.0 - s) + anim2.zoom * s;
            Point3d center = anim1.center * (1.0 - s) + anim2.center * s;

            // Interpolate distance
            double distance = norm(viewPos1) * (1.0 - s) + norm(viewPos2) * s;

            // Sum position from rotation and the translation
            Point3d viewDir = normalized(orient.rotate(zAxis));
            pos = center + viewDir * distance + orient.rotate(translate1) * (1.0 - s) + orient.rotate(translate2) * s;

            // If something has changed, update it
            if(firstStep or prevPos[k] != pos or prevOrient[k] != orient or prevCenter != center or prevZoom != zoom) 
              setCameraVis(pos, orient, center, zoom);

            // Save previous values
            prevCenter = center;
            prevZoom = zoom;
          } else if(firstStep or prevFlags[k] != flags.iFlags or prevPos[k] != pos or prevOrient[k] != orient) 
            setFrameVis(names[k], flags.iFlags, pos, orient); // Other frames

          // Save previous values
          prevPos[k] = pos;
          prevOrient[k] = orient;
          prevFlags[k] = flags.iFlags;
        }

        updateViewer();
      }
      i++;
      if(i >= keyFrames and loop)
        i = 0;
    } while(loop or i < keyFrames - 1);

    return true;
  }
  REGISTER_PROCESS(AnimationPlay);

  bool AnimationClear::run()
  {
    animVec.clear();

    return true;
  }
  REGISTER_PROCESS(AnimationClear);
  
  bool AnimationSave::run(const QString &fileName)
  {
    if(animVec.size() == 0)
      throw QString("%1::run Animation is empty").arg(name());
    if(fileName.isEmpty())
      throw QString("%1::run File name is empty").arg(name());

    QFile file(fileName);
    if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
      throw QString("%1::run Cannot open file: %2").arg(name()).arg(fileName);

    QTextStream out(&file);
    out << animVec.size() << endl;
    for(uint i = 0; i < animVec.size(); i++) {
      auto &anim = animVec[i];
      for(uint j = 0; j < 7; j++)
        out << anim.pos[j] << endl;
      for(uint j = 0; j < 7; j++)
        out << anim.orient[j] << endl;
      for(uint j = 0; j < 7; j++)
        out << anim.flags[j].iFlags << endl;
      out << anim.zoom << endl;
      out << anim.center << endl;
      out << anim.steps << endl;
    }
    Information::out << QString("Saved %1 frames to file: %2").arg(animVec.size()).arg(fileName) << endl;

    return true;
  }
  REGISTER_PROCESS(AnimationSave);

  bool AnimationLoad::run(const QString &fileName)
  {
    if(fileName.isEmpty())
      throw QString("%1::run File name is empty").arg(name());

    QFile file(fileName);
    if(!file.open(QIODevice::ReadWrite | QIODevice::Text))
      throw QString("%1::run Cannot open file: %2").arg(name()).arg(fileName);

    QTextStream in(&file);
    uint size;
    in >> size >> endl;

    if(size == 0)
      throw QString("%1::run Input file is empty: %2").arg(name()).arg(fileName);
    Information::out << QString("Reading %1 frames from file: %2").arg(size).arg(fileName) << endl;
    animVec.resize(size);
    for(uint i = 0; i < size; i++) {
      auto &anim = animVec[i];
      for(uint j = 0; j < 7; j++)
        in >> anim.pos[j] >> endl;
      for(uint j = 0; j < 7; j++) 
        in >> anim.orient[j] >> endl;
      for(uint j = 0; j < 7; j++) 
        in >> anim.flags[j].iFlags >> endl;
      in >> anim.zoom >> endl;
      in >> anim.center >> endl;
      in >> anim.steps >> endl;
    }

    return true;
  }
  REGISTER_PROCESS(AnimationLoad);
}
