package BetterOutput;
use strict;
use HTML::Entities;
use URI::Escape;
1;

sub new {
    my $class = shift;
    my($results) = @_;
    return bless {'results' => $results, 'output' => undef}, $class;
}

sub capture {
    my $self = shift;
    my($target) = @_;
    $self->{output} = $target;
}

sub print {
    my $self = shift;
    if (defined $self->{output}) {
        local $" = '';
        ${$self->{output}} .= "@_";
    } else {
        CORE::print(@_);
    }
}

sub redirect {
    my $self = shift;
    my($command, $args) = @_;
    my $uri = './?command=' . encode_entities($command);
    foreach (keys %$args) {
        $uri .= '&' . encode_entities($_) . '=' . encode_entities($args->{$_});
    }
    $self->print(<<eof);
Status: 303 See Other
Location: $uri
Content-Type: text/plain;charset=utf-8

See $uri
eof
}

sub page {
    my $self = shift;
    my($title, $status) = @_;
    $title = encode_entities($title);
    $self->print("Status: $status\n") if defined $status;
    $self->print(<<eof);
Content-Type: text/html;charset=utf-8

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN">
<html lang="en">
 <head>
  <title>BETTER: $title</title>
  <style type="text/css">
eof
    $self->stylesheet();
    $self->print(<<eof);
  </style>
  <link rel="appendix" title="Administration" href="./?command=admin">
 </head>
 <body>
  <h1>$title</h1>
eof
    $self->{close} = sub {
        CORE::print(" </body>\n</html>\n");
    };
}

sub subhead {
    my $self = shift;
    my($title) = @_;
    $title = encode_entities($title);
    $self->print("  <h2>$title</h2>\n");
}

sub paragraph {
    my $self = shift;
    my $attributes = {};
    if (ref $_[0] eq 'HASH') {
        $attributes = shift;
    }
    $self->print('  <p');
    foreach my $attr (%$attributes) {
        my $value = encode_entities($attributes->{$attr});
        $self->print(" $attr=\"$value\"");
    }
    $self->print('>');
    foreach (@_) {
        my $a = '';
        my $text = '';
        my $z = '';
        if (ref $_ eq 'ARRAY') {
            $a = "<$_->[0]>";
            $text = $_->[1];
            $z = "</$_->[0]>";
        } else {
            $text = $_;
        }
        $text = encode_entities($text);
        $self->print("$a$text$z");
    }
    $self->print("</p>\n");
}

sub explicitLink {
    my $self = shift;
    my($text, $uri) = @_;
    $text = encode_entities($text);
    $uri = encode_entities($uri);
    $self->print("  <p>$text <code><a href=\"$uri\">$uri</a></code></p>\n");
}

sub link {
    my $self = shift;
    my($text, $uri) = @_;
    $text = encode_entities($text);
    $uri = encode_entities($uri);
    $self->print("  <p><a href=\"$uri\">$text</a></p>\n");
}

sub openList {
    my $self = shift;
    my($title) = @_;
    $self->print("  <ol>\n");
}

sub closeList {
    my $self = shift;
    my($title) = @_;
    $self->print("  </ol>\n");
}

sub getLinkItem {
    my $self = shift;
    my($data, $label, $comment) = @_;
    my $uri = './?';
    foreach (keys %$data) {
        $uri .= uri_escape($_);
        $uri .= '=';
        $uri .= uri_escape($data->{$_});
        $uri .= '&';
    }
    chop $uri; # remove trailing '&' (or '?')
    $uri = encode_entities($uri);
    $label = encode_entities($label);
    if (defined $comment) {
        $comment = encode_entities(" $comment");
    } else {
        $comment = '';
    }
    $self->print("   <li><a href=\"$uri\">$label</a>$comment</li>\n");
}

sub postLinkItem {
    my $self = shift;
    my($data, $label, $comment) = @_;
    $label = encode_entities($label);
    $comment = '' unless defined $comment;
    $comment = encode_entities($comment);
    $self->print(<<eof);
   <li>
    <form action="./" method="POST">
     <p>
      <input type="submit" value="$label">
eof
    foreach (keys %$data) {
        my $name = encode_entities($_);
        my $value = encode_entities($data->{$_});
        $self->print("      <input type=\"hidden\" name=\"$name\" value=\"$value\">\n");
    }
    $self->print(<<eof);
      $comment
     </p>
    </form>
   </li>
eof
}

sub form {
    my $self = shift;
    my($command, $method, $fields, $submit) = @_;
    $command = encode_entities($command);
    $method = encode_entities($method);
    $submit = encode_entities($submit);
    $self->print(<<eof);
  <form action="./" method="$method">
   <p>
eof
    my $count = 0;
    foreach (@$fields) {
        my $type = encode_entities($_->{type});
        my $name = encode_entities($_->{name});
        my $label = '';
        if (exists $_->{label}) {
            $label = '<span>' . encode_entities($_->{label}) . '</span> ';
        }
        if ($type ne 'hidden') {
            $count++;
            if ($count > 1) {
                $self->print("   </p>\n   <p>\n");
            }
        }
        if ($_->{type} eq 'textarea') {
            my $value = encode_entities($_->{value});
            $self->print("    <label>$label<textarea name=\"$name\" cols=\"80\" rows=\"10\">$value</textarea></label>\n");
        } elsif ($_->{type} eq 'select') {
            $self->print("    <label>$label\n     <select name=\"$name\">\n");
            foreach my $item (@{$_->{values}}) {
                my $selected = ($item->{id} eq $_->{value}) ? ' selected="selected"' : '';
                my $value = encode_entities($item->{id});
                my $caption = encode_entities($item->{name});
                $self->print("      <option value=\"$value\"$selected>$caption</option>\n");
            }
            $self->print("     </select>\n    </label>\n");
        } else {
            my $value = encode_entities($_->{value});
            $self->print("    <label>$label<input type=\"$type\" name=\"$name\" value=\"$value\"></label>\n");
        }
    }
    if ($count > 1) {
        $self->print("   </p>\n   <p>\n");
    }
    $self->print(<<eof);
    <input type="hidden" name="command" value="$command">
    <input type="submit" value="$submit">
   </p>
  </form>
eof
}

sub testFrameset {
    my $self = shift;
    my($title, $runID, $suiteID, $testID, $uri, $n, $maxN, $tester, $password) = @_;
    my $frameURI = './?command=submissionFrame&run=' . uri_escape($runID) .
                                           '&suite=' . uri_escape($suiteID) .
                                            '&test=' . uri_escape($testID) .
                                       '&remaining=' . uri_escape($n) .
                                           '&total=' . uri_escape($maxN) .
                                          '&tester=' . uri_escape($tester) .
                                        '&password=' . uri_escape($password);
    foreach ($title, $uri, $frameURI) {
        $_ = encode_entities($_);
    }
    $self->print(<<eof);
Content-Type: text/html;charset=utf-8

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
<html lang="en">
 <head>
  <title>BETTER: $title</title>
  <script type="text/javascript">
   var betterSystem = {
     report: function (resultCode) {
       frame = document.getElementById('results').contentDocument;
       frame.getElementById('result').value = resultCode;
       frame.getElementById('automation').submit();
     }
   };
  </script>
  <style type="text/css">
eof
    $self->stylesheet();
    $self->print(<<eof);
  </style>
 </head>
 <frameset rows="*,10%">
  <frame src="$uri" id="test">
  <frame src="$frameURI" id="results">
  <noframes>
   <object src="$uri"><p><a href="$uri">View test</a>.</p></object>   
eof
    $self->submissionBlock(@_[1..3], @_[5..8]); # $runID, $suiteID, $testID, $n, $maxN, $tester, $password (but not escaped)
    $self->print("  </noframes>\n </frameset>\n</html>");
}

sub testSubmissionFrame {
    my $self = shift;
    # my($runID, $suiteID, $testID, $n, $maxN, $tester, $password) = @_;
    $self->print(<<eof);
Content-Type: text/html;charset=utf-8

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN">
<html lang="en">
 <head>
  <title>BETTER: Test Result Submission Form</title>
  <script type="text/javascript">
   function bootstrap() {
     var test = parent.document.getElementById('test').contentWindow;
     if (test.betterResultHook) {
       test.betterResultHook(parent.betterSystem);
     }
   }

   /* simple sample hook:

     function betterResultHook(better) {
       if (passed) {
         better.report('P'); // Passed
       } else {
         better.report('B'); // Buggy
       }
     }

   */

  </script>
  <style type="text/css">
eof
    $self->stylesheet();
    $self->print(<<eof);
  </style>
 </head>
 <body onload="bootstrap()" class="frameset">
eof
    $self->submissionBlock(@_); # $runID, $suiteID, $testID, $n, $maxN, $tester, $password (not escaped)
    $self->print(" </body>\n</html>");
}

sub submissionBlock {
    my $self = shift;
    my($runID, $suiteID, $testID, $n, $maxN, $tester, $password) = @_;
    foreach ($runID, $suiteID, $testID, $n, $maxN, $tester, $password) {
        $_ = encode_entities($_);
    }
    my $resultCodes = $self->{results};
    foreach (@$resultCodes) {
        my $code = $_->{'code'};
        my $label = $_->{'label'};
        $self->print(<<eof);
  <form action="./" method="POST" target="_top" class="results">
   <p>
    <input type="hidden" name="command" value="submit">
    <input type="hidden" name="run" value="$runID">
    <input type="hidden" name="suite" value="$suiteID">
    <input type="hidden" name="test" value="$testID">
    <input type="hidden" name="tester" value="$tester">
    <input type="hidden" name="password" value="$password">
    <input type="hidden" name="result" value="$code">
    <input type="submit" value="$label" class="$code">
   </p>
  </form>
eof
    }
    # XXX support skipping tests
    $n = $maxN - $n + 1;
    $self->print(<<eof);
  <form action="./" method="post" target="_top" id="automation">
   <p> <!-- Form for automatic submission -->
    <input type="hidden" name="result" id="result" value="">
   </p>
  </form>
  <p class="remaining"> Test $n of $maxN. </p>
eof
}

sub stylesheet {
    my $self = shift;
    $self->print(<<eof);
   body.frameset { padding: 0; margin: 0; }
   form.results { margin: 0.5em; float: left; }
   form.results p { margin: 0; }
   p.remaining { margin: 0.5em; float: right; }
   input[type=submit] { min-width: 6em; }
   input[type=text], input[type=password] { min-width: 6em; width: 25%; max-width: 25em; }
   input[name=run] { width: 6em; max-width: 6em; }
   label span { display: inline-block; width: 8em; text-align: right; padding: 0 1em 0 0; }
   textarea { display: block; }
   .Y { background: green; color: white; font-weight: 900; }
   .P { background: green; color: white; font-weight: 100; }
   .N { font-weight: 900; }
   .B { background: red; color: yellow; font-weight: 900; }
   .D { background: maroon; color: yellow; font-weight: 900; text-decoration: blink; }
   .C { background: black; color: yellow; font-weight: 900; }
   td:first-child { padding: 0 0.5em 0 0; }
   td + td { text-align: center; padding: 0 0.5em; }
   .sort { background: yellow; color: black; }
eof
}

sub openTable {
    my $self = shift;
    my($columns, $otherColumns, $currentKeys, $arguments) = @_;
    my $links = $self->getSortLinks([@$columns, @$otherColumns], $currentKeys, $arguments);
    $self->print("  <table>\n   <thead>\n    <tr>\n");
    foreach my $col (@$columns) {
        my $link = shift @$links;
        $self->print("     <th>$link</th>\n");
    }
    $self->print("    </tr>\n   </thead>\n");
    return $links;
}

sub getSortLinks {
    my $self = shift;
    my($columns, $currentKeys, $arguments) = @_;
    my @links;
    my $prefix = './?';
    foreach (keys %$arguments) {
        $prefix .= uri_escape($_) . '=' . uri_escape($arguments->{$_}) . '&';
    }
    my $currentKey = '';
    if (@$currentKeys) {
        $currentKey = $currentKeys->[0];
    }
    foreach my $col (@$columns) {
        my $key = $col->[0];
        if (defined $key) {
            my $class = '';
            my $keyPrefix = '';
            if ($currentKey eq $key) {
                $class = 'class="ascending sort"';
                $keyPrefix = '-';
            } elsif ($currentKey eq "-$key") {
                $class = 'class="descending sort"';
            }
            my $link = $prefix . 'key0=' . encode_entities("$keyPrefix$key");
            my $keyIndex = 0;
            foreach (@$currentKeys) {
                if ($_ !~ /^-?\Q$key\E/s) {
                    ++$keyIndex;
                    $link .= "&key$keyIndex=" . encode_entities($_);
                }
            }
            $link = encode_entities($link);
            my $title = encode_entities($col->[1]); 
            push(@links, "<a href=\"$link\"$class>$title</a>");
        } else {
            push(@links, encode_entities($col->[1]));
        }
    }
    return \@links;
}

sub printIndexRow {
    my $self = shift;
    my($label, $arguments, @values) = @_;
    my $uri;
    if (ref $arguments eq 'HASH') {
        $uri = './?';
        foreach (keys %$arguments) {
            $uri .= uri_escape($_) . '=' . uri_escape($arguments->{$_}) . '&';
        }
        chop $uri;
    } else {
        $uri = $arguments;
    }
    $uri = encode_entities($uri);
    $label = encode_entities($label);
    $self->print("   <tr>\n    <td>\n     <a href=\"$uri\">$label</a>\n    </td>\n");
    foreach (@values) {
        if (ref $_ eq 'ARRAY') {
            my $data = encode_entities($_->[0]);
            my $class = encode_entities($_->[1]);
            $self->print("    <td class=\"$class\">$data</td>\n");
        } elsif (ref $_ eq 'HASH') {
            my $uri = encode_entities($_->{'uri'});
            my $label = encode_entities($_->{'label'});
            $self->print("    <td><a href=\"$uri\">$label</a></td>\n");
        } else {
            my $data = encode_entities($_);
            $self->print("    <td>$data</td>\n");
        }
    }
    $self->print("   </tr>\n");
}

sub closeTable {
    my $self = shift;
    my($otherLinks) = @_;
    $self->print("  </table>\n");
    if (@$otherLinks) {
        $self->print('  <p>Sort by:');
        my $prefix = ' ';
        foreach (@$otherLinks) {
            $self->print("$prefix$_");
            $prefix = ', ';
        }
        $self->print("</p>\n");
    }
}

sub DESTROY {
    my $self = shift;
    if (defined $self->{close}) {
        &{$self->{close}};
    }
}
