/*****
 * guide.h
 * Andy Hammerlindl 2005/02/23
 *
 *****/

#ifndef GUIDE_H
#define GUIDE_H

#include <iostream>
#include "knot.h"
#include "flatguide.h"
#include "settings.h"

namespace camp {

// Abstract base class for guides.
class guide : public gc {
protected:
public:
  virtual ~guide() {}
  
  // Returns the path that the guide represents.
  virtual path solve() {
    return path();
  }

  // Add the information in the guide to the flatguide, so that it can be
  // solved via the knotlist solving routines.
  // Returns true if guide has an interior cycle token. 
  virtual void flatten(flatguide&, bool allowsolve=true)=0;
  
  virtual bool cyclic() {return false;}
  
  virtual void print(ostream& out) const {
    out << "nullpath";
  }
  
  // Needed so that multiguide can know where to put in ".." symbols.
  virtual side printLocation() const {
    return END;
  }
  
};

inline ostream& operator<< (ostream& out, const guide& g)
{
  g.print(out);
  return out;
}

// Draws dots between two printings of guides, if their locations are such that
// the dots are necessary.
inline void adjustLocation(ostream& out, side l1, side l2)
{
  if (l1 == END)
    out << endl;
  if ((l1 == END || l1 == OUT) && (l2 == IN || l2 == END))
    out << "..";
}

// A guide representing a pair.
class pairguide : public guide {
  pair z;

public:
  void flatten(flatguide& g, bool=true) {
    g.add(z);
  }

  pairguide(pair z)
    : z(z) {}

  path solve() {
    return path(z);
  }

  void print(ostream& out) const {
    out << z;
  }
  
  side printLocation() const {
    return END;
  }
};


// A guide representing a path.
class pathguide : public guide {
  path p;

public:
  void flatten(flatguide& g, bool allowsolve=true) {
    g.add(p,allowsolve);
  }

  pathguide(path p)
    : p(p) {}

  path solve() {
    return p;
  }

  bool cyclic() {return p.cyclic();}

  void print(ostream& out) const {
    out << p;
  }
  
  side printLocation() const {
    return END;
  }
};

// Tension expressions are evaluated to this class before being cast to a guide,
// so that they can be cast to other types (such as guide3) instead.
class tensionSpecifier : public gc {
  double out,in;
  bool atleast;

public:
  tensionSpecifier(double val, bool atleast=false)
    : out(val), in(val), atleast(atleast) {}
  tensionSpecifier(double out, double in, bool atleast=false)
    : out(out), in(in), atleast(atleast) {}

  double getOut() const { return out; }
  double getIn() const { return in; }
  bool getAtleast() const { return atleast; }
};


// A guide giving tension information (as part of a join).
class tensionguide : public guide {
  tension tout,tin;

public:
  void flatten(flatguide& g, bool=true) {
    g.setTension(tin,IN);
    g.setTension(tout,OUT);
  }

  tensionguide(tensionSpecifier spec)
    : tout(spec.getOut(), spec.getAtleast()),
      tin(spec.getIn(), spec.getAtleast()) {}

  void print(ostream& out) const {
    out << (tout.atleast ? ".. tension atleast " : ".. tension ")
        << tout.val << " and " << tin.val << " ..";
  }
  
  side printLocation() const {
    return JOIN;
  }
};

// Similar to tensionSpecifier, curl expression are evaluated to this type
// before being cast to guides.
class curlSpecifier : public gc {
  double value;
  side s;

public:
  curlSpecifier(double value, side s)
    : value(value), s(s) {}

  double getValue() const { return value; }
  side getSide() const { return s; }
};

// A guide giving a specifier.
class specguide : public guide {
  spec *p;
  side s;

public:
  void flatten(flatguide& g, bool=true) {
    g.setSpec(p,s);
  }
  
  specguide(spec *p, side s)
    : p(p), s(s) {}

  specguide(curlSpecifier spec)
    : p(new curlSpec(spec.getValue())), s(spec.getSide()) {}

  void print(ostream& out) const {
    out << *p;
  }
  
  side printLocation() const {
    return s;
  }
};

// A guide for explicit control points between two knots.  This could be done
// with two specguides, instead, but this prints nicer, and is easier to encode.
class controlguide : public guide {
  pair zout, zin;

public:
  void flatten(flatguide& g, bool=true) {
    g.setSpec(new controlSpec(zout), OUT);
    g.setSpec(new controlSpec(zin), IN);
  }

  controlguide(pair zout,pair zin)
    : zout(zout),zin(zin) {}
  controlguide(pair z)
    : zout(z),zin(z) {}

  void print(ostream& out) const {
    out << ".. controls "
        << zout << " and " << zin << " ..";
  }
  
  side printLocation() const {
    return JOIN;
  }
};

// A guide that is a sequence of other guides.  This is used, for instance is
// joins, where we have the left and right guide, and possibly specifiers and
// tensions in between.
typedef mem::vector<guide *> guidevector;

class multiguide : public guide {
  guidevector v;

public:

  void flatten(flatguide&, bool=true);
  
  bool cyclic() {
    size_t n=v.size();
    if(n < 1) return false;
    return v[n-1]->cyclic();
  }
  
  multiguide(guidevector& v)
    : v(v) {}

  path solve() {
    if (settings::verbose>3) {
      cerr << "solving guide:\n";
      print(cerr); cerr << "\n\n";
    }
    
    flatguide g;
    this->flatten(g);
    path p=g.solve(false);

    if (settings::verbose>3)
      cerr << "solved as:\n" << p << "\n\n";

    return p;
  }

  void print(ostream& out) const;
  
  side printLocation() const {
    return v.back()->printLocation();
  }
};

struct cycleToken : public gc {};

// A guide representing the cycle token.
class cycletokguide : public guide {
public:
  void flatten(flatguide& g, bool allowsolve=true) {
    // If cycles occur in the midst of a guide, the guide up to that point
    // should be solved as a path.  Any subsequent guide will work with that
    // path locked in place.
    if(allowsolve)
      g.solve(true);
    else
      g.close();
  }

  bool cyclic() {return true;}
  
  path solve() {
    // Just a cycle on it's own makes an empty guide.
    return path();
  }

  void print(ostream& out) const {
    out << "cycle";
  }

  side printLocation() const {
    return END;
  }
};

} // namespace camp

GC_DECLARE_PTRFREE(camp::pairguide);
GC_DECLARE_PTRFREE(camp::tensionSpecifier);
GC_DECLARE_PTRFREE(camp::tensionguide);
GC_DECLARE_PTRFREE(camp::curlSpecifier);
GC_DECLARE_PTRFREE(camp::controlguide);
GC_DECLARE_PTRFREE(camp::cycleToken);
GC_DECLARE_PTRFREE(camp::cycletokguide);

#endif // GUIDE_H