# Fixture.pm
#
# Copyright (c) 2002 Cunningham & Cunningham, Inc.
# Released under the terms of the GNU General Public License version 2 or later.
#
# Perl translation by Dave W. Smith <dws@postcognitive.com>

package Fixture;

use strict;

use vars qw(%summary);

%summary = ();

sub new {
    my $pkg = shift;
    return bless { counts => new Counts(),
                   @_ }, $pkg;
}

sub counts {
    my $self = shift;
    $self->{'counts'} = $_[0] if @_;
    return $self->{'counts'};
}

sub doTables {
    my $self = shift;
    my($tables) = @_;

    $summary{'run date'} = scalar localtime(time());

    while ( $tables ) {
	my $heading = $tables->at(0,0,0);
	if ( $heading ) {
	    eval {
		my $pkg = $heading->text();
		$pkg =~ s/^(\w+\.)*//;			#TBD refactor?
		require "$pkg.pm";
		my $fixture = $pkg->new();
		$fixture->counts($self->counts());
		$fixture->doTable($tables);
	    };
	    if ( $@ ) {
		$self->exception($heading, $@);
	    }
	}
        $tables = $tables->more();
    }
}

sub doTable {
    my $self = shift;
    my($table) = @_;

    $self->doRows($table->parts()->more());
}

sub doRows {
    my $self = shift;
    my($rows) = @_;
    while ( $rows ) {
	$self->doRow($rows);
	$rows = $rows->more();
    }
}

sub doRow {
    my $self = shift;
    my($row) = @_;

    $self->doCells($row->parts());
}

sub doCells {
    my $self = shift;
    my($cells) = @_;
    my $columnNumber = 0;

    while ( $cells ) {
	eval {
	    $self->doCell($cells, $columnNumber);
	};
	if ( $@ ) {
	    $self->exception($cells, $@);
	}
	$cells = $cells->more();
	++$columnNumber;
    }
}

sub doCell {
    my $self = shift;
    my($cell, $columnNumber) = @_;

    $self->ignore($cell);
}

# Annotations

sub right {
    my $self = shift;
    my($cell) = @_;
    $cell->addToTag(' bgcolor="#cfffcf"');
    $self->counts()->{'right'} += 1;
}

sub wrong {
    my $self = shift;
    my($cell, $actual) = @_;

    $cell->addToTag(' bgcolor="#ffcfcf"');
    $cell->addToBody(label("expected") .
                     "<hr>" .
                     escape($actual) .
                     label("actual"))
	if defined($actual);
    $self->counts()->{'wrong'} += 1;
}

sub ignore {
    my $self = shift;
    my($cell) = @_;

    $cell->addToTag(' bgcolor="#efefef"');
    $self->counts()->{'ignores'} += 1;
}


sub exception {
    my $self = shift;
    my($cell, $exception) = @_;

    #TBD how to include a stack trace is an open issue

    $cell->addToTag(' bgcolor="ffffcf"');
    $cell->addToBody('<hr><font size=-2><pre>' .
                     $exception .
                     "</pre></font>");
    $self->counts()->{'exceptions'} += 1;
}

# Utilities

sub label {
    my($string) = @_;

    return ' <font size=-1 color="#c08080"><i>' . $string . '</i></font>';
}

sub gray {
    my $self = shift;
    my($string) = @_;

    return '<font color="#808080">' . $string . '</font>';
}

sub escape {
    my($string) = @_;

    $string =~ s/\&/&amp;/g;
    $string =~ s/</&lt;/g;
    return $string;
}

sub camel {
    my($string) = @_;

    $string =~ s/\s(\S)/uc($1)/eg;
    return $string;
}

sub parse {
    my $self = shift;
    my($string, $type) = @_;
    die "can't yet parse $type\n" if $type ne "generic";
    return $string;
}

sub check {
    my $self = shift;
    my($cell, $adapter) = @_;

    my $text = $cell->text();
    return if $text eq "";

    if ( not defined($adapter) ) {
	$self->ignore($cell);
    }
    elsif ( $text eq "error" ) {
	eval {
	    my $result = $adapter->invoke();
	    $self->wrong($cell, $adapter->toString($result));
	};
	if ( $@ ) {
	    #TBD The Java source distinguishes between illegal access
            # and "normal" exceptions. 
	    $self->right($cell);
	}
    }
    else {
	eval {
	    my $result = $adapter->get();
	    if ( $adapter->equals($adapter->parse($text), $result) ) {
	        $self->right($cell);
	    } else {
	        $self->wrong($cell, $adapter->toString($result));
	    }
	};
	if ( $@ ) {
	    $self->exception($cell, $@);
	}
    }
}


package Counts;

sub new {
    my $pkg = shift;
    bless {
	right => 0,
	wrong => 0,
	ignores => 0,
        exceptions => 0
    }, $pkg;
}

sub toString {
   my $self = shift;

   join(", ", map { $self->{$_} . " " . $_ } qw(right wrong ignores exceptions))
}

sub tally {
    my $self = shift;
    my($counts) = @_;

    $self->{'right'} += $counts->{'right'};
    $self->{'wrong'} += $counts->{'wrong'};
    $self->{'ignores'} += $counts->{'ignores'};
    $self->{'exceptions'} += $counts->{'exceptions'};
}

1;

__END__

package fit;

// Copyright (c) 2002 Cunningham & Cunningham, Inc.
// Released under the terms of the GNU General Public License version 2 or later.

import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import java.text.DateFormat;

public class Fixture {

    public static Map summary = new HashMap();
    public Counts counts = new Counts();

    public class Counts {
        public int right = 0;
        public int wrong = 0;
        public int ignores = 0;
        public int exceptions = 0;

        public String toString() {
            return
                right + " right, " +
                wrong + " wrong, " +
                ignores + " ignored, " +
                exceptions + " exceptions";
        }

        public void tally(Counts source) {
            right += source.right;
            wrong += source.wrong;
            ignores += source.ignores;
            exceptions += source.exceptions;
        }
    }


    // Traversal //////////////////////////

    public void doTables(Parse tables) {
        summary.put("run date", new Date());
        while (tables != null) {
            Parse heading = tables.at(0,0,0);
            if (heading != null) {
                try {
                    Fixture fixture = (Fixture)(Class.forName(heading.text()).newInstance());
                    fixture.counts = counts;
                    fixture.doTable(tables);
                } catch (Exception e) {
                    exception (heading, e);
                }
            }
            tables = tables.more;
        }
    }

    public void doTable(Parse table) {
        doRows(table.parts.more);
    }

    public void doRows(Parse rows) {
        while (rows != null) {
            doRow(rows);
            rows = rows.more;
        }
    }

    public void doRow(Parse row) {
        doCells(row.parts);
    }

    public void doCells(Parse cells) {
        for (int i=0; cells != null; i++) {
            try {
                doCell(cells, i);
            } catch (Exception e) {
                exception(cells, e);
            }
            cells=cells.more;
        }
    }

    public void doCell(Parse cell, int columnNumber) {
        ignore(cell);
    }


    // Annotation ///////////////////////////////

    public  void right (Parse cell) {
        cell.addToTag(" bgcolor=\"#cfffcf\"");
        counts.right++;
    }

    public void wrong (Parse cell) {
        cell.addToTag(" bgcolor=\"#ffcfcf\"");
        counts.wrong++;
    }

    public void wrong (Parse cell, String actual) {
        wrong(cell);
        cell.addToBody(label("expected") + "<hr>" + escape(actual) + label("actual"));
    }

    public void ignore (Parse cell) {
        cell.addToTag(" bgcolor=\"#efefef\"");
        counts.ignores++;
    }

    public void exception (Parse cell, Throwable exception) {
        while(exception.getClass().equals(InvocationTargetException.class)) {
            exception = ((InvocationTargetException)exception).getTargetException();
        }
        final StringWriter buf = new StringWriter();
        exception.printStackTrace(new PrintWriter(buf));
        cell.addToBody("<hr><font size=-2><pre>" + (buf.toString()) + "</pre></font>");
        cell.addToTag(" bgcolor=\"#ffffcf\"");
        counts.exceptions++;
    }

    // Utility //////////////////////////////////

    public String counts() {
        return counts.toString();
    }

    public static String label (String string) {
        return " <font size=-1 color=#c08080><i>" + string + "</i></font>";
    }

    public static String gray (String string) {
        return " <font color=#808080>" + string + "</font>";
    }

    public static String escape (String string) {
        return escape(escape(string, '&', "&amp;"), '<', "&lt;");
    }

    public static String escape (String string, char from, String to) {
        int i=-1;
        while ((i = string.indexOf(from, i+1)) >= 0) {
            if (i == 0) {
                string = to + string.substring(1);
            } else if (i == string.length()) {
                string = string.substring(0, i) + to;
            } else {
                string = string.substring(0, i) + to + string.substring(i+1);
            }
        }
        return string;
    }

    public static String camel (String name) {
        StringBuffer b = new StringBuffer(name.length());
        StringTokenizer t = new StringTokenizer(name);
        b.append(t.nextToken());
        while (t.hasMoreTokens()) {
            String token = t.nextToken();
            b.append(token.substring(0, 1).toUpperCase());      // replace spaces with camelCase
            b.append(token.substring(1));
        }
        return b.toString();
    }

    public Object parse (String s, Class type) throws Exception {
        if (type.equals(String.class))              {return s;}
        if (type.equals(Date.class))                {return DateFormat.getDateInstance().parse(s);}
        if (type.equals(ScientificDouble.class))    {return ScientificDouble.valueOf(s);}
        throw new Exception("can't yet parse "+type);
    }

    public void check(Parse cell, TypeAdapter a) {
        String text = cell.text();
        if (text.equals("")) {
            // skip cell
        } else if (a == null) {
            ignore(cell);
        } else  if (text.equals("error")) {
            try {
                Object result = a.invoke();
                wrong(cell, a.toString(result));
            } catch (IllegalAccessException e) {
                exception (cell, e);
            } catch (Exception e) {
                right(cell);
            }
        } else {
            try {
                Object result = a.get();
                if (a.equals(a.parse(text), result)) {
                    right(cell);
                } else {
                    wrong(cell, a.toString(result));
                }
            } catch (Exception e) {
                exception(cell, e);
            }
        }
    }

}
