// Copyright (C)  2002  Dominique Devriese <devriese@kde.org>

// 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 2
// 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
// 02110-1301, USA.

#include "calcpaths.h"

#include "../objects/object_calcer.h"
#include "../objects/object_imp.h"

#include <algorithm>

// mp:
// The previous algorithm by Dominique had an exponential complexity
// for some constructions (e.g. a sequence of "n" triangles each inscribed
// into the previous).
// The new version is directly taken from a book of Alan Bertossi
// "Algoritmi e strutture dati"

// temporarily disabling the new algorithm due to the freeze:
// I previously misunderstood the semantics of this function
// and thought that the os vector had to be completed with all
// the subtree generated by it.  On the contrary, the os vector
// contains *all* the objects that we want, we only have to
// reorder them.  Now it *should* work, however we postpone 
// activating this to a more proper moment

// to deactivate the new algorithm change "define" into "undef"

#define NEWCALCPATH
#ifdef NEWCALCPATH
void localdfs( ObjectCalcer* obj,
               std::vector<ObjectCalcer*>& visited,
               std::vector<ObjectCalcer*>& all);

std::vector<ObjectCalcer*> calcPath( const std::vector<ObjectCalcer*>& os )
{
  // "all" is the Objects var we're building, in reverse ordering
  std::vector<ObjectCalcer*> visited;
  std::vector<ObjectCalcer*> all;

  for ( std::vector<ObjectCalcer*>::const_iterator i = os.begin(); i != os.end(); ++i )
  {
    if ( std::find( visited.begin(), visited.end(), *i ) == visited.end() )
    {
      localdfs( *i, visited, all );
    }
  }

  // now, we need to remove all objects that are not in os
  // (forgot to do this in previous fix :-( )
  std::vector<ObjectCalcer*> ret;
  for ( std::vector<ObjectCalcer*>::reverse_iterator i = all.rbegin(); i != all.rend(); ++i )
  {
    // we only add objects that appear in os
    if ( std::find( os.begin(), os.end(), *i ) != os.end() ) ret.push_back( *i );
  };
  return ret;
}

void localdfs( ObjectCalcer* obj,
               std::vector<ObjectCalcer*>& visited,
               std::vector<ObjectCalcer*>& all)
{
  visited.push_back( obj );
  const std::vector<ObjectCalcer*> o = obj->children();
  for ( std::vector<ObjectCalcer*>::const_iterator i = o.begin(); i != o.end(); ++i )
  {
    if ( std::find( visited.begin(), visited.end(), *i ) == visited.end() )
      localdfs( *i, visited, all );
  }
  all.push_back( obj );
}

// old calcPath commented out...

#else
// these first two functions were written before i read stuff about
// graph theory and algorithms, so I'm sure they're far from optimal.
// However, they seem to work fine, and i don't think there's a real
// need for optimization here..
std::vector<ObjectCalcer*> calcPath( const std::vector<ObjectCalcer*>& os )
{
  // this is a little experiment of mine, i don't know if it is the
  // fastest way to do it, but it seems logical to me...

  // the general idea here:
  // first we build a new Objects variable.  For every object in os,
  // we put all of its children at the end of it, and we do the same
  // for the ones we add..

  // "all" is the Objects var we're building...
  std::vector<ObjectCalcer*> all = os;
  // tmp is the var containing the objects we're iterating over.  The
  // first time around this is the os variable, the next time, this
  // contains the variables we added in the first round...
  std::vector<ObjectCalcer*> tmp = os;
  // tmp2 is a temporary var.  During a round, it receives all the
  // variables we add ( to "all" ) in that round, and at the end of
  // the round, it is assigned to tmp.
  std::vector<ObjectCalcer*> tmp2;
  while ( ! tmp.empty() )
  {
    for ( std::vector<ObjectCalcer*>::const_iterator i = tmp.begin(); i != tmp.end(); ++i )
    {
      const std::vector<ObjectCalcer*> o = (*i)->children();
      std::copy( o.begin(), o.end(), std::back_inserter( all ) );
      std::copy( o.begin(), o.end(), std::back_inserter( tmp2 ) );
    };
    tmp = tmp2;
    tmp2.clear();
  };

  // now we know that if all objects appear at least once after all of
  // their parents.  So, we take all, and of every object, we remove
  // every reference except the last one...
  std::vector<ObjectCalcer*> ret;
  ret.reserve( os.size() );
  for ( std::vector<ObjectCalcer*>::reverse_iterator i = all.rbegin(); i != all.rend(); ++i )
  {
    // we only add objects that appear in os and only if they are not
    // already in ret..
    if ( std::find( ret.begin(), ret.end(), *i ) == ret.end() &&
         std::find( os.begin(), os.end(), *i ) != os.end() ) ret.push_back( *i );
  };
  std::reverse( ret.begin(), ret.end() );
  return ret;
}
#endif

bool addBranch( const std::vector<ObjectCalcer*>& o, const ObjectCalcer* to, std::vector<ObjectCalcer*>& ret )
{
  bool rb = false;
  for ( std::vector<ObjectCalcer*>::const_iterator i = o.begin(); i != o.end(); ++i )
  {
    if ( *i == to )
      rb = true;
    else
      if ( addBranch( (*i)->children(), to, ret ) )
      {
        rb = true;
        ret.push_back( *i );
      };
  };
  return rb;
}

std::vector<ObjectCalcer*> calcPath( const std::vector<ObjectCalcer*>& from, const ObjectCalcer* to )
{
  std::vector<ObjectCalcer*> all;

  for ( std::vector<ObjectCalcer*>::const_iterator i = from.begin(); i != from.end(); ++i )
  {
    (void) addBranch( (*i)->children(), to, all );
  };

  std::vector<ObjectCalcer*> ret;
  for ( std::vector<ObjectCalcer*>::iterator i = all.begin(); i != all.end(); ++i )
  {
    if ( std::find( ret.begin(), ret.end(), *i ) == ret.end() )
      ret.push_back( *i );
  };
  return std::vector<ObjectCalcer*>( ret.rbegin(), ret.rend() );
}

static void addNonCache( ObjectCalcer* o, std::vector<ObjectCalcer*>& ret )
{
  if ( ! o->imp()->isCache() )
  {
    if ( std::find( ret.begin(), ret.end(), o ) == ret.end() )
      ret.push_back( o );
    else
    {
      std::vector<ObjectCalcer*> parents = o->parents();
      for ( uint i = 0; i < parents.size(); ++i )
        addNonCache( parents[i], ret );
    };
  }
}

static bool visit( const ObjectCalcer* o, const std::vector<ObjectCalcer*>& from, std::vector<ObjectCalcer*>& ret )
{
  // this function returns true if the visited object depends on one
  // of the objects in from.  If we encounter objects that are on the
  // side of the tree path ( they do not depend on from themselves,
  // but their direct children do ), then we add them to ret.
  if ( std::find( from.begin(), from.end(), o ) != from.end() ) return true;

  std::vector<bool> deps( o->parents().size(), false );
  bool somedepend = false;
  bool alldepend = true;
  std::vector<ObjectCalcer*> parents = o->parents();
  for ( uint i = 0; i < parents.size(); ++i )
  {
    bool v = visit( parents[i], from, ret );
    somedepend |= v;
    alldepend &= v;
    deps[i] = v;
  };
  if ( somedepend && ! alldepend )
  {
    for ( uint i = 0; i < deps.size(); ++i )
      if ( ! deps[i] )
        addNonCache( parents[i], ret );
  };

  return somedepend;
}

std::vector<ObjectCalcer*> sideOfTreePath( const std::vector<ObjectCalcer*>& from, const ObjectCalcer* to )
{
  std::vector<ObjectCalcer*> ret;
  visit( to, from, ret );
  return ret;
}

std::vector<ObjectCalcer*> getAllParents( const std::vector<ObjectCalcer*>& objs )
{
  using namespace std;
  std::set<ObjectCalcer*> ret( objs.begin(),objs.end() );
  std::set<ObjectCalcer*> cur = ret;
  while ( ! cur.empty() )
  {
    std::set<ObjectCalcer*> next;
    for ( std::set<ObjectCalcer*>::const_iterator i = cur.begin(); i != cur.end(); ++i )
    {
      std::vector<ObjectCalcer*> parents = (*i)->parents();
      next.insert( parents.begin(), parents.end() );
    };

    ret.insert( next.begin(), next.end() );
    cur = next;
  };
  return std::vector<ObjectCalcer*>( ret.begin(), ret.end() );
}

std::vector<ObjectCalcer*> getAllParents( ObjectCalcer* obj )
{
  std::vector<ObjectCalcer*> objs;
  objs.push_back( obj );
  return getAllParents( objs );
}

bool isChild( const ObjectCalcer* o, ObjectCalcer* op )
{
  std::vector<ObjectCalcer*> os;
  os.push_back( op );
  return isChild( o, os );
}

bool isChild( const ObjectCalcer* o, const std::vector<ObjectCalcer*>& os )
{
  std::vector<ObjectCalcer*> parents = o->parents();
  std::set<ObjectCalcer*> cur( parents.begin(), parents.end() );
  while ( ! cur.empty() )
  {
    std::set<ObjectCalcer*> next;
    for ( std::set<ObjectCalcer*>::const_iterator i = cur.begin(); i != cur.end(); ++i )
    {
      if ( std::find( os.begin(), os.end(), *i ) != os.end() ) return true;
      std::vector<ObjectCalcer*> parents = (*i)->parents();
      next.insert( parents.begin(), parents.end() );
    };
    cur = next;
  };
  return false;
}

std::set<ObjectCalcer*> getAllChildren( ObjectCalcer* obj )
{
  std::vector<ObjectCalcer*> objs;
  objs.push_back( obj );
  return getAllChildren( objs );
}

std::set<ObjectCalcer*> getAllChildren( const std::vector<ObjectCalcer*> objs )
{
  std::set<ObjectCalcer*> ret;
  // objects to iterate over...
  std::set<ObjectCalcer*> cur( objs.begin(), objs.end() );
  while( !cur.empty() )
  {
    // contains the objects to iterate over the next time around...
    std::set<ObjectCalcer*> next;
    for( std::set<ObjectCalcer*>::iterator i = cur.begin();
         i != cur.end(); ++i )
    {
      ret.insert( *i );
      std::vector<ObjectCalcer*> children = (*i)->children();
      next.insert( children.begin(), children.end() );
    };
    cur = next;
  };
  return ret;
}

bool isPointOnCurve( const ObjectCalcer* point, const ObjectCalcer* curve )
{
  return point->isDefinedOnOrThrough( curve ) || curve->isDefinedOnOrThrough( point );
}
