+#include <msp/strings/utils.h>
+#include "layout.h"
+#include "track.h"
+#include "trackchain.h"
+
+namespace R2C2 {
+
+TrackChain::TrackChain(Layout &l):
+ layout(l)
+{
+ layout.signal_object_removed.connect(sigc::mem_fun(this, &TrackChain::object_removed));
+}
+
+void TrackChain::add_track(Track &track)
+{
+ if(tracks.count(&track))
+ return;
+
+ Validity valid = check_validity(track);
+ if(valid!=VALID)
+ throw_bad_chain(valid);
+
+ tracks.insert(&track);
+ update_ends(track);
+ on_track_added(track);
+}
+
+void TrackChain::add_tracks(const TrackSet &trks)
+{
+ TrackSet pending;
+ for(TrackSet::const_iterator i=trks.begin(); i!=trks.end(); ++i)
+ if(!tracks.count(*i))
+ pending.insert(*i);
+
+ while(!pending.empty())
+ {
+ Validity valid = UNLINKED;
+ for(TrackSet::iterator i=pending.begin(); i!=pending.end(); ++i)
+ if((valid=check_validity(**i))==VALID)
+ {
+ tracks.insert(*i);
+ update_ends(**i);
+ on_track_added(**i);
+ pending.erase(i);
+ break;
+ }
+
+ if(valid!=VALID)
+ throw_bad_chain(valid);
+ }
+}
+
+TrackChain::Validity TrackChain::check_validity(Track &track) const
+{
+ if(tracks.empty())
+ return VALID;
+
+ if(!ends[1])
+ {
+ if(!ends[0])
+ return UNLINKED;
+ return ends[0]->get_link_slot(track)>=0 ? VALID : UNLINKED;
+ }
+
+ Validity result = UNLINKED;
+ for(unsigned i=0; i<2; ++i)
+ {
+ int j = ends[i]->get_link_slot(track);
+ if(j>=0)
+ {
+ const TrackType::Endpoint &ep1 = ends[i].endpoint();
+ const TrackType::Endpoint &ep2 = ends[i]->get_type().get_endpoint(j);
+ if(!ep1.has_common_paths(ep2))
+ return BAD_PATH;
+
+ result = VALID;
+ }
+ }
+
+ return result;
+}
+
+void TrackChain::throw_bad_chain(Validity v)
+{
+ if(v==UNLINKED)
+ throw bad_chain("unlinked");
+ else if(v==BAD_PATH)
+ throw bad_chain("bad path");
+ else if(v==INCOMPATIBLE)
+ throw bad_chain("incompatible");
+}
+
+void TrackChain::update_ends(Track &track)
+{
+ if(!ends[0])
+ // We don't really know the proper endpoint yet; just pick something
+ ends[0] = TrackIter(&track, 0);
+ else if(!ends[1])
+ {
+ // We're adding the second track; determine endpoints for both
+ ends[0] = TrackIter(&*ends[0], ends[0]->get_link_slot(track));
+ ends[1] = TrackIter(&track, track.get_link_slot(*ends[0]));
+ }
+ else
+ {
+ unsigned linked = 0;
+ for(unsigned i=0; i<2; ++i)
+ if(ends[i]->get_link_slot(track)>=0)
+ linked |= 1<<i;
+
+ if(linked==3)
+ {
+ // Closed loop; clear the ends
+ ends[0] = TrackIter();
+ ends[1] = TrackIter();
+ }
+ else
+ ends[linked-1] = TrackIter(&track, track.get_link_slot(*ends[linked-1]));
+ }
+}
+
+bool TrackChain::has_track(Track &t) const
+{
+ return tracks.count(&t);
+}
+
+void TrackChain::object_removed(Object &obj)
+{
+ if(Track *track = dynamic_cast<Track *>(&obj))
+ tracks.erase(track);
+ /* TODO If the track was in the middle of the chain, keep only the
+ longest fragment */
+}
+
+} // namespace R2C2