/*
* This file is part of gitg
*
* Copyright (C) 2012 - Jesse van den Kieboom
*
* gitg 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.
*
* gitg 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 gitg. If not, see .
*/
namespace Gitg
{
public class Lanes : Object
{
public int inactive_max { get; set; }
public int inactive_collapse { get; set; }
public int inactive_gap { get; set; }
public bool inactive_enabled { get; set; }
private SList d_previous;
private SList d_lanes;
private HashTable d_collapsed;
[Compact]
class LaneContainer
{
public Lane lane;
public uint inactive;
public Ggit.OId? from;
public Ggit.OId? to;
public LaneContainer.with_color(Ggit.OId? from,
Ggit.OId? to,
Color? color)
{
this.from = from;
this.to = to;
this.lane = new Lane.with_color(color);
this.inactive = 0;
}
public LaneContainer(Ggit.OId? from,
Ggit.OId? to)
{
this.with_color(from, to, null);
}
public void next(int index)
{
lane = lane.copy();
lane.tag = LaneTag.NONE;
lane.from = new SList();
lane.from.prepend(index);
if (to != null)
{
++inactive;
}
}
}
[Compact]
class CollapsedLane
{
public Color color;
public uint index;
public Ggit.OId? from;
public Ggit.OId? to;
public CollapsedLane(LaneContainer container)
{
color = container.lane.color;
from = container.from;
to = container.to;
}
}
public Lanes()
{
d_collapsed = new HashTable(Ggit.OId.hash,
Ggit.OId.equal);
reset();
}
public void reset()
{
d_previous = new SList();
d_lanes = new SList();
Color.reset();
d_collapsed.remove_all();
}
public SList next(Commit next,
out int nextpos)
{
var myoid = next.get_id();
if (inactive_enabled)
{
collapse_lanes();
expand_lanes(next);
}
unowned LaneContainer? mylane = find_lane_by_oid(myoid, out nextpos);
if (mylane == null)
{
// there is no lane reserver for this comit, add a new lane
d_lanes.append(new LaneContainer(myoid, null));
nextpos = (int)d_lanes.length() - 1;
}
else
{
// copy the color here because the commit is a new stop
mylane.lane.color = mylane.lane.color.copy();
mylane.to = null;
mylane.from = next.get_id();
mylane.inactive = 0;
}
var res = lanes_list();
prepare_lanes(next, nextpos);
return res;
}
private void prepare_lanes(Commit next, int pos)
{
var parents = next.get_parents();
var myoid = next.get_id();
init_next_layer();
unowned LaneContainer mylane = d_lanes.nth_data(pos);
for (uint i = 0; i < parents.size(); ++i)
{
int lnpos;
var poid = parents.get_id(i);
unowned LaneContainer? container = find_lane_by_oid(poid, out lnpos);
if (container != null)
{
// there is already a lane for this parent. This means that
// we add pos as a merge for the lane, also this means the
// color of this lane incluis the merge should change to
// one color
container.lane.from.append(pos);
container.lane.color.next_index();
container.inactive = 0;
container.from = myoid;
continue;
}
else if (mylane != null && mylane.to == null)
{
// there is no parent yet which can proceed on the current
// commit lane, so set it now
mylane.to = poid;
if (parents.size() > 1)
{
mylane.lane.color = Color.next();
}
else
{
mylane.lane.color = mylane.lane.color.copy();
}
}
else
{
// generate a new lane for this parent
LaneContainer newlane = new LaneContainer(myoid, poid);
newlane.lane.from.prepend(pos);
d_lanes.append((owned)newlane);
}
}
if (mylane != null && mylane.to == null)
{
// remove current lane if no longer needed (i.e. merged)
d_lanes.remove(mylane);
}
// store new commit in track list
if (d_previous.length() == inactive_collapse + inactive_gap + 1)
{
d_previous.delete_link(d_previous.last());
}
d_previous.prepend(next);
}
private void add_collapsed(LaneContainer container,
int index)
{
var collapsed = new CollapsedLane(container);
collapsed.index = index;
d_collapsed.insert(container.to, (owned)collapsed);
}
private void collapse_lane(LaneContainer container,
int index)
{
add_collapsed(container, index);
unowned SList item = d_previous;
while (item != null)
{
var commit = item.data;
unowned SList lns = commit.get_lanes();
unowned Lane lane = lns.nth_data(index);
if (item.next != null)
{
var newindex = lane.from.data;
lns = commit.remove_lane(lane);
if (item.next.next != null)
{
update_merge_indices(lns, newindex, -1);
}
var mylane = commit.mylane;
if (mylane > index)
{
--commit.mylane;
}
index = newindex;
}
else
{
lane.tag |= LaneTag.END;
lane.boundary_id = container.to;
}
item = item.next;
}
}
private void collapse_lanes()
{
int index = 0;
unowned SList item = d_lanes;
while (item != null)
{
unowned LaneContainer container = item.data;
if (container.inactive != inactive_max + inactive_gap)
{
item = item.next;
++index;
continue;
}
collapse_lane(container, container.lane.from.data);
update_current_lane_merge_indices(index, -1);
unowned SList next = item.next;
d_lanes.remove_link(item);
item = next;
}
}
private int ensure_correct_index(Commit commit,
int index)
{
var len = commit.get_lanes().length();
if (index > len)
{
return (int)len;
}
else
{
return index;
}
}
private void update_lane_merge_indices(SList from,
int index,
int direction)
{
while (from != null)
{
int idx = from.data;
if (idx > index || (direction > 0 && idx == index))
{
from.data = idx + direction;
}
from = from.next;
}
}
private void update_merge_indices(SList lanes,
int index,
int direction)
{
foreach (unowned Lane lane in lanes)
{
update_lane_merge_indices(lane.from, index, direction);
}
}
private void update_current_lane_merge_indices(int index,
int direction)
{
foreach (unowned LaneContainer container in d_lanes)
{
update_lane_merge_indices(container.lane.from,
index,
direction);
}
}
private void expand_lane(CollapsedLane lane)
{
var index = lane.index;
var ln = new Lane.with_color(lane.color);
var len = d_lanes.length();
if (index > len)
{
index = len;
}
var next = ensure_correct_index(d_previous.data, (int)index);
var container = new LaneContainer.with_color(lane.from,
lane.to,
lane.color);
update_current_lane_merge_indices((int)index, 1);
container.lane.from.prepend(next);
d_lanes.insert((owned)container, (int)index);
index = next;
uint cnt = 0;
unowned SList ptr = d_previous;
while (ptr != null)
{
var commit = ptr.data;
if (cnt == inactive_collapse)
{
break;
}
// Insert new lane at the index
Lane copy = ln.copy();
unowned SList lns = commit.get_lanes();
if (ptr.next == null || cnt + 1 == inactive_collapse)
{
copy.boundary_id = lane.from;
copy.tag |= LaneTag.START;
}
else
{
next = ensure_correct_index(ptr.next.data, (int)index);
copy.from.prepend(next);
update_merge_indices(lns, (int)index, 1);
}
commit.insert_lane(copy, (int)index);
var mylane = commit.mylane;
if (mylane >= index)
{
++commit.mylane;
}
index = next;
++cnt;
ptr = ptr.next;
}
}
private void expand_lane_from_oid(Ggit.OId id)
{
unowned CollapsedLane? collapsed = d_collapsed.lookup(id);
if (collapsed != null)
{
expand_lane(collapsed);
d_collapsed.remove(id);
}
}
private void expand_lanes(Commit commit)
{
expand_lane_from_oid(commit.get_id());
var parents = commit.get_parents();
for (uint i = 0; i < parents.size(); ++i)
{
expand_lane_from_oid(parents.get_id(i));
}
}
private void init_next_layer()
{
int index = 0;
foreach (unowned LaneContainer container in d_lanes)
{
container.next(index++);
}
}
private unowned LaneContainer? find_lane_by_oid(Ggit.OId id,
out int pos)
{
int p = 0;
unowned SList ptr = d_lanes;
while (ptr != null)
{
unowned LaneContainer? container = ptr.data;
if (container != null &&
id.equal(container.to))
{
pos = p;
return container;
}
++p;
ptr = ptr.next;
}
pos = -1;
return null;
}
private SList lanes_list()
{
var ret = new SList();
foreach (unowned LaneContainer container in d_lanes)
{
ret.prepend(container.lane.copy());
}
ret.reverse();
return ret;
}
}
}
// ex:set ts=4 noet