#!/usr/bin/perl
# script to take several doxygen dot files and combine them into a single dot file
# currently just merges 2 dot files
#
# usage: dotcat in1.dot in2.dot out.dot
#
# the dot command I like:
# dot -Tpng out.dot -o out.png -Gconcentrate="true" -Grankdir="LR" -Esametail=1 -Esamehead=2 -Granksep=2
#
# Copyright (c) 2004 Mark Ivey.  All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.

use warnings; # can't use -w because of Graph::Reader::Dot
use strict;
use English;

my $VERSION = 0.1;
my $DEBUG = 1;

################################################################################
package Node;
use warnings;
use strict;
use English;

use Class::MethodMaker
    new_with_init => 'new',
    new_hash_init => '_hash_init',
    get_set=> 'name',
    get_set=> 'label',
    get_set=> 'attributes',
    ;

use constant DEFAULTS => (  name => 'Unnamed',
                            label => 'Unlabeled'
                        );

sub init
{
    my $self = shift;
    my %args = ( DEFAULTS, @ARG );      # @ARG overrides DEFAULTS
    
    $self->_hash_init ( %args );
}

################################################################################
package Edge;
use warnings;
use strict;
use English;

use Class::MethodMaker
    new_with_init => 'new',
    new_hash_init => '_hash_init',
    get_set=> 'node1',
    get_set=> 'node2',
    get_set=> 'label',
    get_set=> 'attributes',
    ;

use constant DEFAULTS => (  node1 => 'Unnamed_Node1',
                            node2 => 'Unnamed_Node2',
                            label => 'Unlabeled'
                        );

sub init
{
    my $self = shift;
    my %args = ( DEFAULTS, @ARG );      # @ARG overrides DEFAULTS
    
    $self->_hash_init ( %args );
}

################################################################################
package MyGraph;
use warnings;
use strict;
use English;

use Class::MethodMaker
    new_with_init => 'new',
    new_hash_init => '_hash_init',
    get_set=> '_vertices',
    get_set=> '_vertex_aliases',
    get_set=> '_edges',
    ;

use constant DEFAULTS => (  _vertices => {},
                            _vertex_aliases => {},
                            _edges => {}
                        );

sub init
{
    my $self = shift;
    my %args = ( DEFAULTS, @ARG );      # @ARG overrides DEFAULTS
    
    $self->_hash_init ( %args );
}

sub add_dot_graph()
{
    my $self = shift;
    my $graph = shift;
    
    # copy verticies
    foreach my $name ($graph->vertices())
    {
        my $label = $graph->get_attribute("label", $name);
        
        print "New node: Label is $label.  name is $name" if ($DEBUG >= 1);
        
        if ( defined $self->_vertices->{$label} )    
        {
            # just add alias if we've seen this node before
            my $alias = $self->_vertices->{$label}->name;
            print "  Duplicate of $alias\n" if ($DEBUG >= 1);
            $self->_vertex_aliases->{$name} = $alias;
        }
        else
        {
            print "\n" if ($DEBUG >= 1);
            $self->_vertices->{$label} = Node->new(  name=>$name, 
                                            label=>$label, 
                                            attributes=>{$graph->get_attributes($name)},
                                        );
        }
    }
    
    # copy edges
    my @E = $graph->edges();
    print "Edges: @E\n" if ($DEBUG >= 1);
    for (my $i=0; $i < scalar @E; $i+=2)
    {
        my ($u, $v) = ($E[$i], $E[$i+1]);
        my $label = $graph->get_attribute("label", $u, $v);
        print "edge: $u $v\n" if ($DEBUG >= 1);
        $self->_edges->{$u.":".$v} = Edge->new( node1=>$u,
                                    node2=>$v,
                                    label=>$label,
                                    attributes=>{$graph->get_attributes($u, $v)},
                                );
    }
}    

sub get_contents()
{
    my $self = shift;
    my $graph_out = Graph::Directed->new();
    print "Vertices we have seen:\n" if ($DEBUG >= 1);
    
    foreach my $v (values %{$self->_vertices()})
    {
        next if (exists $self->_vertex_aliases->{$v->name}); # skip duplicates
        
        print $v->name . ": " if ($DEBUG >= 1);
        $graph_out->add_vertex($v->name);
        
        # copy this node's attributes
        my %attributes = %{ $v->attributes() };
        my ($attr, $value);
        while (($attr, $value) = each %attributes)
        {
            print "$attr=$value " if ($DEBUG >= 1);
            $graph_out->set_attribute($attr, $v->name, $value);
        }
        print "\n" if ($DEBUG >= 1);
    }
    
    print "Edges we have seen:\n" if ($DEBUG >= 1);
    foreach my $e (values %{$self->_edges})
    {
        next if (exists $self->_vertex_aliases->{$e->node1} and exists $self->_vertex_aliases->{$e->node2}); # skip duplicates
        
        print $e->node1 . ":" . $e->node2 if ($DEBUG >= 1);
        
        # re-connect edges from duplicate nodes to their alias nodes
        if (exists $self->_vertex_aliases->{$e->node1}) { $e->node1( $self->_vertex_aliases->{$e->node1} ) } 
        if (exists $self->_vertex_aliases->{$e->node2}) { $e->node2( $self->_vertex_aliases->{$e->node2} ) }
        print " becomes " . $e->node1 . ":" . $e->node2 . ": " if ($DEBUG >= 1);
        
        $graph_out->add_edge($e->node1, $e->node2);
        
        # copy this edges's attributes
        my %attributes = %{ $e->attributes() };
        my ($attr, $value);
        while (($attr, $value) = each %attributes)
        {
            print "$attr=$value " if ($DEBUG >= 1);
            $graph_out->set_attribute($attr, $e->node1, $e->node2, $value);
        }
        print "\n" if ($DEBUG >= 1);
    }    
    
    return $graph_out;
}

################################################################################
package main;

use Graph; 
use Graph::Reader::Dot; 
use Graph::Writer::Dot; 

my $mygraph = MyGraph->new();

my $reader = Graph::Reader::Dot->new(); 
my $writer = Graph::Writer::Dot->new(); 

# read input graphs
my $graph_in1 = $reader->read_graph($ARGV[0]); 
my $graph_in2 = $reader->read_graph($ARGV[1]); 

# add input graphs to mygraph
foreach my $graph ($graph_in1, $graph_in2)
{
    $mygraph->add_dot_graph($graph);
}

# get contents of mygraph
my $graph_out = $mygraph->get_contents();

# write output graph to file
$writer->write_graph($graph_out, $ARGV[2]);
