#! /usr/bin/perl -T

# Read a database table, present its records in a HTML table.

my $dir;
BEGIN {
    use Cwd qw(abs_path);
    $dir = abs_path(__FILE__);
    $dir =~ /(.*)(\/.*\/.*\/.*\..*)$/;
    $dir = $1;
    unshift(@INC, $dir . "/lib");
}

use strict;
use warnings;
use CGI qw(:standard -nosticky -utf8 start_div end_div);
use Encode qw(decode);
use File::Basename qw(basename dirname);
use HTML::Entities;
use JSON qw(encode_json);
use URI::Encode qw( uri_encode uri_decode );

use CGIParameters qw(read_cgi_parameters);
use Database;
use HTMLGenerator qw(
    error_page
    start_html5
    start_root
    end_root
    header_tables_pl
    start_doc_wrapper
    end_doc_wrapper
    start_main_content
    end_main_content
    footer_tables_pl
    start_card_fluid
    end_card
    two_column_row_html
    warnings2html
);
use RestfulDB::CGI qw( save_cgi_parameters );
use RestfulDB::Defaults qw( get_css_styles get_default_cgi_parameters );
use RestfulDB::DBSettings qw( get_database_settings );
use RestfulDB::JSON;
use RestfulDB::Spreadsheet qw( sprintf_csv_line );

# STDIN must NOT be set to binmode "UTF-8", since CGI with the '-utf8'
# flag handles this.
binmode( STDOUT, "utf8" );
binmode( STDERR, "utf8" );

sub get_list_column_counts
{
    my ( $name_count, $fl_count ) = @_;

    # Set number of data columns to divide into.
    my $divisor = $name_count > 33 ? 4 : ( $name_count > 13 ? 2 : 1 ) ;

    my $total = $name_count + $fl_count;
    my $whole_part = int( $total/$divisor );
    my $remainder = $total % $divisor;

    my @counts; # Array of minimum entry lines per data column.
    # Set minimums evenly.
    $counts[$_] = $whole_part foreach ( 0..($divisor-1) );
    # Add remaining entries to columns from left to right.
    while ( $remainder > 0 ) {
        foreach my $column ( @counts ) {
            $column++;
            $remainder--;
            last if $remainder == 0;
        }
    }
    return \@counts;
}

my $page_level = -1;

$ENV{PATH} = ""; # Untaint and clean PATH

# Path to database directory for SQlite3 databases:
my $db_dir = $dir . "/db";

my @warnings;
local $SIG{__WARN__} = sub { print STDERR $_[0]; push @warnings, $_[0] };

my $cgi = new CGI;
my $format = 'html';

eval {

    my %local_CGI_parameters = (
        format => { re => 'html|csv|xml|cif|json' },
    );

    my( $base_uri, $query_string ) = split /\?/, $ENV{REQUEST_URI};

    my( $params, $changed );
    eval {
        ( $params, $changed ) =
            read_cgi_parameters( $cgi,
                                 { %RestfulDB::Defaults::CGI_parameters,
                                   %local_CGI_parameters },
                                 { query_string => $query_string } );
    };
    InputException->throw( $@ ) if $@;

    my %params = %$params;

    if( $params{debug} && $params{debug} eq 'save' ) {
        save_cgi_parameters( $db_dir );
    }

    $format = $params{format} if $changed->{format};

    my %db_settings = get_database_settings( \%params, \%ENV,
                                             { db_dir => $db_dir, level => 0 });

    my $db_user = $db_settings{db_user};
    my $db_name = $db_settings{db_name};
    my $db_path = $db_settings{db_path};
    my $db_table = $db_settings{db_table};
    my $db_engine = $db_settings{db_engine};

    my $remote_user = $db_settings{remote_user_for_print};

    my $db_settings = {
        content_db => { DB => $db_path,
                        engine => $db_engine },
    };

    my $db = Database->new( $db_settings );
    $db->connect();

    my @tables = $db->get_table_list( { skip_history_tables => 1 } );
    my @tables_copy = @tables;

    if( defined $format && $format eq 'csv' ) {
        print $cgi->header( -type => 'text/csv',
                            -expires => 'now',
                            -charset => 'UTF-8',
                            -attachment => "tables.csv" );

        my $csv = Text::CSV->new();
        print sprintf_csv_line( [ 'table' ], $csv ), "\n";
        for (@tables) {
            print sprintf_csv_line( [ $_ ], $csv ), "\n";
        }

    } elsif( defined $format && $format eq 'json' ) {
        print $cgi->header( -type => $RestfulDB::JSON::MEDIA_TYPE,
                            -expires => 'now',
                            -charset => 'UTF-8',
                            -attachment => "tables.json" );

        print encode_json( \@tables ), "\n";

    } elsif( !defined $format || $format eq 'html' ) {
        my $html;

        $html = $cgi->header(-type=>"text/html", -expires=>'now', -charset=>'UTF-8') .
                start_html5( $cgi,
                    {
                        -style => get_css_styles( $db_name, $page_level ),
                        -head => Link({
                                       -rel  => 'icon',
                                       -type => 'image/x-icon',
                                       -href => './images/favicon.ico'
                        }),
                        -title => "$db_name db: " . $RestfulDB::Defaults::website_title,
                        -meta => {
                          'viewport' => 'width=device-width, initial-scale=1',
                          'description' => 'Short restful db description',
                          'keywords' => join( ', ', @RestfulDB::Defaults::keywords ),
                          'author' => 'Restful Authors'
                        }
                    }
                );

        # Open Main containers.
        $html .= start_root();
        $html .= header_tables_pl( $db_name );
        $html .= start_doc_wrapper();
        $html .= start_main_content();

        # Hidden 'top' paragraph on the data page for the 'To top' link.
        $html .= p( {-id => "top"}, 'Top' ) . "\n";

        # Warnings
        $html .= warnings2html( @warnings );

        # Acknowledge the user name:
        if( defined $remote_user ) {
            $html .= start_card_fluid();
            # 'doc' class for paragraphs ensures that their content
            # can be center-aligned.
            $html .= p( {-class => "doc"}, "User: $remote_user" ) . "\n";
            $html .= end_card();
        }

        $html .= start_card_fluid(); # Main data card.

        $html .= start_div( {-class => "section"} ) . "\n";

        $html .= two_column_row_html({
                        column_major => "Database: $db_name",
                        row_class => "page-title"
                    });

        #----------------------------------------------------------------------
        # Create nav panel.
        my $fl; # First letter for the table name.
        my @first_letters; # Collect to array.
        my $fl_weight = 2; # Set the weight in data lines per FL paragraph.

        foreach my $i ( 0..$#tables ) {
            my $table_name = $tables[$i];
            my $current_letter = substr($table_name, 0, 1);
            if ( !$fl || $fl ne uc($current_letter) ) {
                my $cl = uc($current_letter);
                push @first_letters, $cl;
                $fl = $cl;
            }
        }

        my $dbtables_letters_nav_html;
        $dbtables_letters_nav_html .= "<p class='dbtable-letter-nav'>";
        $dbtables_letters_nav_html .= span([
            map { a( { -href => "#dbtable-letter-$_" }, "$_" ) } @first_letters
            ]) . "\n";
        $dbtables_letters_nav_html .= "</p>";

        #----------------------------------------------------------------------
        # 'scale' parameter meaning : signifies the column dimension class for the
        #                             MINOR column.
        #                             So the major column fills up all of the remaining
        #                             row space.
        $html .=
            two_column_row_html({ column_major => $dbtables_letters_nav_html,
                                  column_minor => p( "Go to:" ),
                                  scale_lg => 2,
                                  scale_md => 2,
                                  scale_sm => 2,
                                  row_id => 'database-nav-panel',
                                  row_class => 'navigate-table-letters',
                                  invert_columns => 1
                              });

        $html .= end_div() . "\n"; # end section 1.
        #----------------------------------------------------------------------

        # The main data part : list of table names.
        $html .= start_div( {-class => "section"} ) . "\n";

        $html .= start_div( {-class => "row",
                             -id => "tables-list",
                             -style => "padding: inherit"} ) . "\n";

        #######################################################################
        # Create alphabetically delineated list of tables: each new first
        # letter starts new unordered list structure of table names.

        # @columns : array of counts of elements in each displayable column.
        my @columns =
            @{get_list_column_counts( scalar(@tables),
                                      scalar(@first_letters)*$fl_weight )};

        $html .= start_div( {
            -class => "col-sm-12".
                      ( @columns == 4 ? " col-md-6" : " col-md-12" ).
                      ( @columns == 4 ? " col-lg-6" : " col-lg-12" ),
            -id => "tables-list-halfcolumn-1"} ) . "\n";

        # Start new row for 2 children subcolumns.
        $html .= start_div( {-class => "row",
                             -style => "padding: inherit"} ) . "\n";

        my $first_letter;
        # Column height := number of 'entry lines' in the current data column.
        my $current_column_height = 0;
        # Runner index for table names.
        my $i = 0;

        while ( @tables_copy ) {
            my $table_name = shift @tables_copy;
            my $current_letter = substr($table_name, 0, 1);
            # If the limit of entries for the current column has been reached
            # and still having data columns to go through, then:
            # - end the current unordered list;
            # - start new data column;
            # - start new unordered list for data to fill.
            if ( $current_column_height <= 0 && @columns ) {
                if ( $i != 0 && $i != $#tables ) {
                    $html .= "</ul>" . end_div();
                    if (@columns == 2) {
                        # Is active only in case if there are more than 2
                        # columns in the initial columns array
                        # (bc of the $i NOT 0 condition).
                        # I.e. in data display on large screens we see 4
                        # distinct columns that consist of two subcolumns
                        # actually.
                        $html .= end_div(); # Close childrens subrow.
                        $html .= end_div(); # Close column container.
                        $html .= start_div( {
                            -class => "col-sm-12 col-md-6 col-lg-6",
                            -id => "tables-list-halfcolumn-2"} ) . "\n";
                        # Start new row for 2 children subcolumns.
                        $html .= start_div( {-class => "row",
                                             -style => "padding: inherit"} ) . "\n";
                    }
                }
                if ( $i != $#tables || $#tables == 0 ) {
                    $html .= start_div({-class => "col-sm-12 col-md-12 col-lg-6"});
                }
                ### Debug prints.
                ##$html .= p("Current index: $i, letter: $current_letter");
                ###
                if ( $i != 0 && $i != $#tables ) {
                    $html .= "<ul>\n";
                }
                $current_column_height = shift @columns;
            }
            # If it's the first letter entry in the table list or seen the
            # change in first letters in data base table names, then:
            # - close the previous unordered list (if not the first entry in db);
            # - update the first letter paragraph entry;
            # - create linkable id for the letter paragraph (see database-nav-panel
            #   spans);
            # - start new list.
            if ( !$first_letter || $first_letter ne uc($current_letter) ) {
                my $cl = uc($current_letter);
                if ( !$first_letter ) {
                    ##$html .= "<li>\n";
                    $html .= h4( {-id => "dbtable-letter-$cl" }, "$cl" ) . "\n";
                    $html .= "<ul>\n";
                } else {
                    $html .= "</ul>\n";
                    $html .= h4( {-id => "dbtable-letter-$cl" }, "$cl" ) . "\n";
                    $html .= "<ul>\n";
                }
                $first_letter = $cl;
                $current_column_height -= $fl_weight;
            }
            # Grow the current unordered list of table entries.
            $html .= li( a({ -href => "$db_name/$table_name" }, $table_name));
            $current_column_height--;
            # If it's the last table entry in the database, just close the
            # last list tag.
            if ( $i == $#tables ) {
                $html .= "</ul>" . end_div();
                $html .= end_div(); # Close childrens subrow.
                $html .= end_div(); # Close column container.
            }
            $i++;
        }
        #######################################################################

        $html .= end_div() . "\n"; # Close the tables-list data row.
        $html .= end_div() . "\n"; # Close the section.

        # This ends an HTML document by printing the </body></html> tags.
        $html .= end_card();
        $html .= footer_tables_pl( $db_name );
        $html .= end_main_content();
        $html .= end_doc_wrapper();
        $html .= end_root();
        $html .= end_html . "\n";

        print $html;
    } else {
        InputException->throw( "Unsupported format '$format'" );
    }

    $db->disconnect();
};

if( $@ ) {
    error_page( $cgi, $@, $page_level );
}
