#!/usr/bin/perl -CA

#BUGBUG -CAS should allow removing utf8::encode, utf8::decode from Translate.pm
# See: http://perldoc.perl.org/perlrun.html#*-C-[_number/list_]*
# or binmode STDIN, ":encoding(UTF-8)";
# http://en.wikibooks.org/wiki/Perl_Programming/Unicode_UTF-8

use warnings;
use strict;
use 5.008;
use Carp;
use Getopt::Long qw(:config no_auto_abbrev);
use Pod::Usage;
use Publican;
use Publican::CreateBook;
use Publican::CreateBrand;
use Publican::Builder;
use Publican::XmlClean;
use Publican::TreeView;
use Publican::WebSite;
use File::Path;
use File::Find::Rule;
use File::pushd;
use Archive::Tar;
use Term::ANSIColor qw(:constants);
use Cwd qw(abs_path cwd);
use File::Basename;
use File::HomeDir;
use Publican::ConfigData;

my $VERSION           = "$Publican::VERSION";
my $LANG_PATTERN      = q|__LANG__|;
my $WEB_TEMPLATE_PATH = Publican::ConfigData->config('web');
my $DATA_DIR          = Publican::ConfigData->config('datadir');

# Try to catch SRPMS created using outdated versions of publican in brew/koji/mock
if (   ( $ENV{RPM_BUILD_DIR} )
    && ( $ENV{RPM_BUILD_DIR}    ne "" )
    && ( $ENV{RPM_PACKAGE_NAME} ne "publican" )
    && (-f "/builddir/build/SRPMS/$ENV{RPM_PACKAGE_NAME}-$ENV{RPM_PACKAGE_VERSION}-$ENV{RPM_PACKAGE_RELEASE}.src.rpm"
    )
    )
{

    my $pkg
        = qq|/builddir/build/SRPMS/$ENV{RPM_PACKAGE_NAME}-$ENV{RPM_PACKAGE_VERSION}-$ENV{RPM_PACKAGE_RELEASE}.src.rpm|;
    my $req_version
        = qx|rpm --requires -qp $pkg \| grep 'publican ' \| sed -e 's/^[^0-9]*//'|;

    system("rpm -Uvh $pkg");
    my $spec = qq|/builddir/build/SPECS/$ENV{RPM_PACKAGE_NAME}.spec|;

    my $db_count = qx|grep -c 'publican update_db' $spec|;
    my $pm_count = qx|grep -c 'use Publican' $spec|;

    chomp($req_version);
    if (( $db_count > 0 or $pm_count > 0 )
        and ( !$req_version
            || $req_version eq ""
            || $req_version < $Publican::SPEC_VERSION )
        )
    {
        croak(
            "\n\n\nERROR: The version of publican used to create the SRPM is too old to use with this version of publican. Upgrade publican on your local machine to at least version $Publican::SPEC_VERSION and try again.\n\n\n"
        );
    }
}

### Code goes here
my $man          = 0;
my $help         = 0;
my $action       = "";
my $help_actions = 0;
my $show_version = 0;
my $bash_comp    = undef;

#Getopt::Long::Configure("debug");

## BUGBUG how do we get this after install?
my @pod_paths = (
    './pod1', Publican::ConfigData->config('docdir') . "/publican-$VERSION"
);

# Global options
my %opts = (
    help         => \$help,
    man          => \$man,
    v            => \$show_version,
    help_actions => \$help_actions,
    bash         => \$bash_comp,
);

#Action options
my %options = (
    help              => maketext('Display help message'),
    man               => maketext('Display full man page'),
    v                 => maketext('Display the version of Publican'),
    'config=s'        => maketext('Use a nonstandard config file'),
    binary            => maketext('Build binary rpm when running package'),
    brew              => maketext('Push SRPM to brew'),
    scratch           => maketext('Use scratch instead of tag build'),
    wait              => maketext('Wait for brew to finish building'),
    'common_config=s' => maketext('Override path to Common_Config directory'),
    'common_content=s' =>
        maketext('Override path to Common_Content directory'),
    'formats=s' => maketext(
        'Comma-separated list of formats, for example: html,pdf,html-single,html-desktop,txt,epub'
    ),
    'langs=s' => maketext(
        'Comma-separated list of languages, for example: en-US,de-DE,all'),
    'embedtoc' =>
        maketext('Embed the web site TOC object in the generated HTML'),
    'name=s'    => maketext('The name of the book, article, set, or brand'),
    'version=s' => maketext('The version of the product'),
    'edition=s' => maketext('The edition of the book, article, or set'),
    'product=s' => maketext('The name of the product'),
    'brand=s'   => maketext('The brand to use'),
    'lang=s'    => maketext('The language the XML will be written in'),
    'type=s'    => maketext('The type (book, article, or set)'),
    publish     => maketext('Set up built content for publishing'),
    desktop     => maketext('Create desktop instead of web package'),
    short_sighted =>
        maketext('Create package without using version in package name'),
    'path=s'        => maketext('/path/to/install/to'),
    distributed_set => maketext(
        qq|This flag tells publican the data being processed is a distributed set. Note: do not use distributed_set on the command line. Publican uses this flag when calling itself to process distributed sets. This is the only safe way this flag can be used.|
    ),
    nocolours => maketext('Disable ANSI colourisation of logging.'),
    quiet     => maketext('Disable all logging.'),
## WebSite options
    'db_file=s'   => maketext('Override default database file.'),
    'toc_path=s'  => maketext('Override the default TOC path.'),
    'tmpl_path=s' => maketext('Override the default template path.'),
    'site_config=s' =>
        maketext('WebSite configuration file to use or create.'),
    novalid           => maketext('Do not run the DTD validation'),
    add               => maketext('Add a database entry'),
    del               => maketext('Delete a database entry'),
    'subtitle=s'      => maketext('Sub title for a book'),
    'abstract=s'      => maketext('Abstract for a book'),
    'product_label=s' => maketext('product label for a book'),
    'version_label=s' => maketext('version label for a book'),
    'name_label=s'    => maketext('name label for a book'),
## Revisions
    'revnumber=s' => maketext('Revision number to use for a revision.'),
    'date=s'      => maketext('Date to use for a revision.'),
    'member=s@'   => maketext(
        'An entry to be added to the revision. Can be specified multiple times.'
    ),
    'firstname=s'  => maketext('firstname to use for a revision.'),
    'surname=s'    => maketext('surname to use for a revision.'),
    'email=s'      => maketext('email to use for a revision.'),
    msgmerge       => maketext(q|Use gettext's msgmerge for POT/PO merging.|),
    web            => maketext(q|Install the web content for a brand.|),
    'src_dir=s'    => maketext(q|Directory to source publican files from.|),
    'brand_dir=s'  => maketext(q|Directory to source brand files from.|),
    'sort_order=s' => maketext('Order to sort a book'),
    'pdftool=s'    => maketext(
        'Override the tool to use when creating PDFs. Valid options are wkhtmltopdf and fop.'
    ),
    'pub_dir=s' =>
        maketext(q|Directory to publish files to. Defaults to publish.|)
);

# Options all actions use
my @utility_opts = (
    'help',            'config=s',
    'common_config=s', 'common_content=s',
    'nocolours',       'quiet',
    'brand_dir=s',
);

# Actions
my %actions = (
    'build' => {
        'brief' => maketext(
            'Transform XML to other formats (pdf, html, html-single, drupal-book, etc)'
        ),
        'options' => [
            'formats',         'langs',   'publish', 'embedtoc',
            'distributed_set', 'novalid', 'src_dir', 'pdftool',
            'pub_dir'
        ],
    },
    'clean' => {
        'brief'   => maketext('Remove all temporary files and directories'),
        'options' => ['pub_dir'],
    },

    'clean_set' => {
        'brief'   => maketext('Remove local copies of remote set books'),
        'options' => [],
    },

    'clean_ids' => {
        'brief'   => maketext('Run clean ids for source XML'),
        'options' => [],
    },
    'print_tree' => {
        'brief'   => maketext('Print a tree of the xi:includes'),
        'options' => [],
    },
    'print_banned' => {
        'brief'   => maketext('Print a list of banned DocBook tags'),
        'options' => [],
    },
    'print_known' => {
        'brief'   => maketext("Print a list of QA'd DocBook tags"),
        'options' => [],
    },
    'print_unused' => {
        'brief'   => maketext('Print a list of unused XML files'),
        'options' => [],
    },
    'print_unused_images' => {
        'brief'   => maketext('Print a list of unused Image files'),
        'options' => [],
    },
    'create' => {
        'brief'   => maketext('Create a new book, set, or article'),
        'options' => [
            'name',  'version', 'edition', 'product',
            'brand', 'lang',    'type'
        ],
    },
    'create_brand' => {
        'brief'   => maketext('Create a new brand'),
        'options' => [ 'name', 'lang' ],
    },
    'package' => {
        'brief'   => maketext('Package a language for shipping'),
        'options' => [
            'lang',          'desktop', 'brew', 'scratch',
            'short_sighted', 'binary',  'wait', 'pub_dir'
        ],
    },
    'update_pot' => {
        'brief'   => maketext('Update the POT files'),
        'options' => [],
    },
    'update_po' => {
        'brief'   => maketext('Update the PO files'),
        'options' => [ 'langs', 'msgmerge', 'firstname', 'surname', 'email' ],
    },
    'install_brand' => {
        'brief'   => maketext('Install a brand to the supplied location'),
        'options' => [ 'path', 'web', 'pub_dir' ],
    },
    'help_config' => {
        'brief'   => maketext('Display help text for the configuration file'),
        'options' => [],
    },
    'lang_stats' => {
        'brief'   => maketext('report PO statistics'),
        'options' => ['lang'],
    },
## WebSite actions
    'create_site' => {
        'brief' => maketext('Create a new WebSite in the supplied location.'),
        'options' =>
            [ 'site_config', 'db_file', 'toc_path', 'tmpl_path', 'lang' ],
    },
    'update_site' => {
        'brief'   => maketext('Update an existing sites templates.'),
        'options' => ['site_config'],
    },
    'install_book' => {
        'brief'   => maketext('Install a book in to a WebSite.'),
        'options' => [ 'site_config', 'lang' ],
    },
    'remove_book' => {
        'brief'   => maketext('Remove a book from a WebSite.'),
        'options' => [ 'site_config', 'lang' ],
    },
    'site_stats' => {
        'brief'   => maketext('Report on the contents of a WebSite'),
        'options' => ['site_config'],
    },
    'update_db' => {
        'brief' => maketext(
            'Add or remove database entries. Used for processing pre-build books, such as when building packages.'
        ),
        'options' => [
            'site_config',   'add',
            'del',           'lang',
            'product',       'version',
            'name',          'formats',
            'subtitle',      'abstract',
            'product_label', 'version_label',
            'name_label',    'sort_order',
        ],
    },
    'add_revision' => {
        'brief'   => maketext('Add an entry to the revision history'),
        'options' => [
            'lang',      'revnumber', 'date', 'member',
            'firstname', 'surname',   'email'
        ],
    },
    'rename' => {
        'brief'   => maketext('Rename a publican book'),
        'options' => [ 'name', 'product', 'version' ],
    },
    'migrate_site' => {
        'brief' => maketext(
            'Migrate a website DataBase from Publican < 3 to Publican 3.'),
        'options' => ['site_config'],
    },
    'trans_drop' => {
        'brief' =>
            maketext('Snapshot the source language for use in translation.'),
        'options' => [],
    },
);

# Grab to limit options to the valid ones for this action
# TODO should we croak on more than one action? Handle more than one?
$action = ( $ARGV[0] || "" );

# Getopt: standard + the options for the supplied action
my @optns = ( keys(%opts), @utility_opts );

if ( defined $actions{$action} ) {
    foreach my $opt ( @{ $actions{$action}->{options} } ) {
        if ( $options{$opt} ) {
            push( @optns, $opt );
        }

        # match options that take a String
        # this will need to be exapnded if other types are used
        elsif ( $options{"$opt=s"} ) {
            push( @optns, "$opt=s" );
        }
        elsif ( $options{"$opt=s@"} ) {
            push( @optns, "$opt=s@" );
        }
        else {

            # This should never happen
            croak(
                maketext(
                    "Invalid option '[_1]' in \$actions{\$action}->{options}",
                    $opt
                )
            );
        }
    }
}

GetOptions( \%opts, @optns, )
    or pod2usage(
    -verbose  => 1,
    -exit     => 1,
    -input    => 'publican',
    -pathlist => \@pod_paths
    );

# catch multiple actions
if ( scalar @ARGV > 1 ) {
    croak(
        maketext(
            "publican only accepts one action you supplied '[_1]': [_2]\n",
            scalar @ARGV, join( ' & ', @ARGV )
        )
    );
}

# Getopt will remove the options leaving only the action
$action = ( $ARGV[0] || "" );

if ($show_version) {
    print("version=$VERSION\n");
    exit(0) if ( $action eq "" );
}

# Undocumented way of getting help for all actions
if ( $help and ( $action eq "all" ) ) {
    foreach my $cmd ( sort( keys(%actions) ) ) {
        _help_action($cmd);
        print("\n");
    }
    exit(0);
}

sub _help_actions {
    print( "\n", maketext("Valid actions are:"), "\n\n" );
    foreach my $cmd ( sort( keys(%actions) ) ) {
        printf( "    %-12s  %s\n", $cmd, $actions{$cmd}->{brief} );
    }
    print("\n\n");
    print(
        maketext(
            "Run: '[_1] <action> --help' for details on action usage", $0
        ),
        "\n\n"
    );

    return;
}

if ($bash_comp) {
    _bash_completion();
    exit(0);
}

# catch no action set
if ( $action eq "" ) {
    pod2usage(
        -verbose  => 1,
        -exit     => 0,
        -input    => 'publican',
        -pathlist => \@pod_paths
    ) if $help;
    pod2usage(
        -verbose  => 2,
        -exit     => 0,
        -input    => 'publican',
        -pathlist => \@pod_paths
    ) if $man;

    if ($help_actions) {
        _help_actions();
        exit(0);
    }

    pod2usage(
        -msg      => "\n" . maketext("Action required!") . "\n",
        -verbose  => 1,
        -exit     => 1,
        -input    => 'publican',
        -pathlist => \@pod_paths
    );

    exit(1);
}

# Catch bogus action
if ( not defined( $actions{$action} ) ) {
    print( maketext( "'[_1]' is an unknown action!", $action ), "\n\n" );
    _help_actions();

    exit(1);

}

sub _help_action {
    my $cmd = shift || croak maketext( "[_1] is a required argument", 'cmd' );

    print( "$cmd\n    " . $actions{$cmd}->{brief} );
    print( "\n\n\t", maketext("Options:"), "\n" );
    foreach my $option ( @utility_opts, @{ $actions{$cmd}->{options} } ) {
        debug_msg("TODO: does printf work for right to left languages?\n");
        if ( $options{$option} ) {
            printf( "        --%-20s    %s\n", $option, $options{$option} );
        }
        else {
            printf(
                "        --%-20s    %s\n",
                "$option=<" . uc($option) . ">",
                ( $options{"$option=s"} || $options{"$option=s@"} )
            );
        }
    }
    print("\n");

    return;
}

# $action must  be set to get here
if ($help) {
    _help_action($action);
    exit(0);
}

#######################################################################
#
# Start processing actions
#
#######################################################################

#pod2usage(1) if ( !$name || $type !~ /[Book|Set|Article]/);
if ( $action eq 'create' ) {

    my $docname = $opts{name}
        || croak( maketext("name is a required parameter") );
    $docname =~ s/\s/_/g;

    my $creator = Publican::CreateBook->new(
        {   name    => $docname,
            version => $opts{version},
            edition => $opts{edition},
            product => $opts{product},
            brand   => $opts{brand},
            lang    => $opts{lang},
            type    => $opts{type}
        }
    );
    $creator->create();

    my $dir = pushd($docname);

    my $publican = Publican->new(
        {   configfile     => $opts{config},
            common_config  => $opts{common_config},
            common_content => $opts{common_content},
            QUIET          => $opts{quiet},
            NOCOLOURS      => $opts{nocolours},
        }
    );

    my $builder = Publican::Builder->new();
    $builder->clean_ids();

    $dir = undef;

    exit(0);
}

if ( $action eq 'create_brand' ) {
    my $creator = Publican::CreateBrand->new(
        {   name => $opts{name},
            lang => $opts{lang},
        }
    );
    $creator->create();

    exit(0);
}

if ( $action eq 'create_site' ) {
    my $site_config = $opts{site_config}
        || croak( maketext( "[_1] is a required argument", 'site_config' ) );
    my $db_file = $opts{db_file}
        || croak( maketext( "[_1] is a required argument", 'db_file' ) );
    my $toc_path = $opts{toc_path}
        || croak( maketext( "[_1] is a required argument", 'toc_path' ) );
    my $tmpl_path = $opts{tmpl_path} || undef;
    my $def_lang  = $opts{lang}      || undef;

    if ( -f $site_config ) {
        croak(
            maketext(
                "Config file exists, you must supply a non-existent filename."
            )
        );
    }

    my $config = new Config::Simple();
    $config->syntax('http');

    my ( $filename, $directories, $suffix ) = fileparse($db_file);
    mkpath($directories) unless -d $directories;
    $config->param( 'db_file', abs_path($db_file) ) if ($db_file);

    if ($toc_path) {
        mkpath($toc_path) unless -d $toc_path;
        $config->param( 'toc_path', abs_path($toc_path) );
        rcopy( "$WEB_TEMPLATE_PATH/*", "$toc_path/." );
    }

    $config->param( 'tmpl_path', abs_path($tmpl_path) ) if ($tmpl_path);
    $config->param( 'def_lang',  $def_lang )            if ($def_lang);
    $config->write($site_config);

    my $ws = Publican::WebSite->new(
        { create => 1, site_config => $site_config } );
    $ws->regen_all_toc();

    exit(0);
}

if ( $action eq 'update_site' ) {
    my $site_config = $opts{site_config} || undef;
    local $File::Copy::Recursive::RMTrgFil = 1;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    rcopy( "$WEB_TEMPLATE_PATH/*", $ws->toc_path() . "/." );
    $ws->regen_all_toc( { force => 1 } );

    exit(0);
}

if ( $action eq 'migrate_site' ) {
    my $site_config = $opts{site_config} || undef;
    local $File::Copy::Recursive::RMTrgFil = 1;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    $ws->MigrateDB();
    $ws->regen_all_toc( { force => 1 } );

    print(
        maketext(
            "Now that your site is migrated you will need to rebuild your brands and install the brands web payload on your site.\n\nIt is also recommended that you rebuild and reinstall all books to ensure the layout is properly migrated."
        )
    );
    print( maketext('$ cd path/to/brand'), "\n" );
    print( maketext('$ publican build --formats=xml --langs=all --publish'),
        "\n" );
## BUGBUG should this use siteconfig?
    print(
        maketext(
            '$ publican install_brand --web --path=/path/to/site/%{brand}'),
        "\n"
    );
    print(
        maketext('$ publican update_site --site_config /path/to/site/config'),
        "\n"
    );

    exit(0);
}

if ( $action eq 'site_stats' ) {
    my $site_config = $opts{site_config} || undef;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );
    print( $ws->report() );

    exit(0);
}

# install/remove book for pre-build content (RPM, DEB, etc)
# BUGBUG formats isn't required for del
if ( $action eq 'update_db' ) {
    my $site_config = $opts{site_config} || undef;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    my $add = $opts{add} || undef;
    my $del = $opts{del} || undef;

    croak(
        maketext("One of add or del is required when updating the database") )
        unless ( $add || $del );

    my $lang = $opts{lang}
        || croak( maketext( "[_1] is a required argument", 'lang' ) );
    my $product = $opts{product}
        || croak( maketext( "[_1] is a required argument", 'product' ) );

    my $version
        = defined $opts{version}
        ? $opts{version}
        : croak( maketext( "[_1] is a required argument", 'version' ) );

    my $name = $opts{name}
        || croak( maketext( "[_1] is a required argument", 'name' ) );
    my $formats = $opts{formats}
        || croak( maketext( "[_1] is a required argument", 'formats' ) );

    # required for add
    my $subtitle = $opts{subtitle} || undef;
    my $abstract = $opts{abstract} || undef;

    if ($add) {
        croak( maketext( "[_1] is a required argument", 'subtitle' ) )
            unless ($subtitle);
        croak( maketext( "[_1] is a required argument", 'abstract' ) )
            unless ($abstract);
    }

    # optional for add
    my $product_label = $opts{product_label} || undef;
    my $version_label = $opts{version_label} || undef;
    my $name_label    = $opts{name_label}    || undef;
    my $sort_order    = $opts{sort_order}    || undef;

    if ($add) {
##        foreach my $format ( split( /,/, $formats ) ) {
        $ws->update_or_add_entry(
            {   language      => $lang,
                product       => $product,
                version       => $version,
                name          => $name,
                formats       => $formats,
                product_label => $product_label,
                version_label => $version_label,
                name_label    => $name_label,
                subtitle      => $subtitle,
                abstract      => $abstract,
                sort_order    => $sort_order,
            }
        );
##        }
    }
    else {
##        foreach my $format ( split( /,/, $formats ) ) {
        $ws->del_entry(
            {   language => $lang,
                product  => $product,
                version  => $version,
                name     => $name,
##                    format   => "$format"
            }
        );
##        }

    }

## BUGBUG TODO only update langauges that have been updated
    # make sure OPDS is correct...
    $ws->regen_all_toc();

    exit(0);
}

## NOTE: All targets here MUST have a valid publican.cfg file
if ( $action eq 'print_known' ) {
    Publican::XmlClean::print_known_tags();
    exit(0);
}
my %args = ( 'configfile' => $opts{config} );
my $src_dir;
my $cwd = cwd();

if ( defined( $opts{src_dir} ) ) {
    $src_dir = pushd( $opts{src_dir} );
}

if ( !defined( $opts{pub_dir} ) ) {
    $opts{pub_dir} = 'publish';
}

my $publican = Publican->new(
    {   'configfile'   => $opts{config},
        common_config  => $opts{common_config},
        common_content => $opts{common_content},
        QUIET          => $opts{quiet},
        NOCOLOURS      => $opts{nocolours},
        brand_dir      => $opts{brand_dir},
    }
);

my $configfile = File::HomeDir->home() . "/.publican.cfg";
if ( -f $configfile ) {
    my $user_cfg = new Config::Simple();
    $user_cfg->syntax('http');
    $user_cfg->read($configfile)
        || croak(
        maketext( "Failed to load user config file: [_1]", $configfile ) );

    $opts{firstname} = $user_cfg->param('firstname')
        unless ( $opts{firstname} );
    $opts{surname} = $user_cfg->param('surname') unless ( $opts{surname} );
    $opts{email}   = $user_cfg->param('email')   unless ( $opts{email} );
    $opts{lang}    = $user_cfg->param('lang')    unless ( $opts{lang} );
    $opts{formats} = $user_cfg->param('formats') unless ( $opts{formats} );
    $opts{langs}   = $user_cfg->param('langs')   unless ( $opts{langs} );
}

if ( defined( $opts{src_dir} ) ) {
    $publican->{config}->param( 'tmp_dir',
        abs_path( "$cwd/" . $publican->param('tmp_dir') ) );
}

if ( $action eq 'print_banned' ) {
    $publican->print_banned_tags();
    exit(0);
}

if ( $action eq 'install_brand' ) {
    my $brand = $publican->param('brand')
        || croak("Can't find brand name");

    my $xml_lang = $publican->param('xml_lang');

    croak( maketext( "[_1] is a required argument", 'path' ) )
        unless ( $opts{path} );
    croak( maketext("you need to publish the brand first") )
        unless ( -d $opts{pub_dir} );
    croak( maketext("destination must exist") ) unless ( -d $opts{path} );

    if ( $opts{web} ) {
        foreach my $ln ( split( ',', get_all_langs() ) ) {
            mkpath("$opts{path}/$ln/css") unless ( -f "$opts{path}/$ln/css" );

            dircopy( "$xml_lang/css", "$opts{path}/$ln/css" )
                if ( -d "$xml_lang/css" );
            dircopy( "$ln/css", "$opts{path}/$ln/css" )
                if ( -d "$ln/css" );

            unless ( -f "$opts{path}/$ln/css/menu.css" ) {
                my $DUMMY_FILE;
                open( $DUMMY_FILE, '>', "$opts{path}/$ln/css/menu.css" )
                    || croak(
                    "can't open file for output: $opts{path}/$ln/css/menu.css"
                    );
                close($DUMMY_FILE);
            }

            my $images = $publican->param('img_dir');
            mkpath("$opts{path}/$ln/$images")
                unless ( -f "$opts{path}/$ln/$images" );
            rcopy( "$xml_lang/$images/*", "$opts{path}/$ln/$images/." )
                if ( -d "$xml_lang/$images" );
            rcopy( "$ln/$images/*", "$opts{path}/$ln/$images/." )
                if ( -d "$ln/$images" );

            if ( -d "$xml_lang/scripts" ) {
                mkpath("$opts{path}/$ln/scripts")
                    unless ( -f "$opts{path}/$ln/scripts" );
                rcopy( "$xml_lang/scripts/*", "$opts{path}/$ln/scripts/." );
            }

            if ( -d "$ln/scripts" ) {
                mkpath("$opts{path}/$ln/scripts")
                    unless ( -f "$opts{path}/$ln/scripts" );
                rcopy( "$ln/scripts/*", "$opts{path}/$ln/scripts/." );
            }
        }
    }
    else {
        rcopy( "$opts{pub_dir}/*", "$opts{path}/." );
        rcopy( "publican.cfg",     "$opts{path}/$brand/." );
        rcopy( "defaults.cfg",     "$opts{path}/$brand/." )
            if ( -f "defaults.cfg" );
        rcopy( "overrides.cfg", "$opts{path}/$brand/." )
            if ( -f "overrides.cfg" );
    }

    exit(0);
}

if ( $action eq 'help_config' ) {
    $publican->help_config();

    exit(0);
}

if ( $action eq 'print_tree' ) {
    my $treeview = Publican::TreeView->new();
    $treeview->print_tree();
    exit(0);
}

if ( $action eq 'print_unused' ) {
    my $treeview = Publican::TreeView->new();
    $treeview->print_unused();
    exit(0);
}

if ( $action eq 'print_unused_images' ) {
    my $treeview = Publican::TreeView->new();
    $treeview->print_unused_images();
    exit(0);
}

if ( $action eq 'clean' || $action eq 'package' ) {
    if ( $action eq 'package' ) {
        logger(
            maketext(
                "Running clean process to ensure stale content is not bundled in packages."
                )
                . "\n",
            RED
        );
    }
    logger(
        maketext( "Clean: Removing [_1] and publish directories.",
            $publican->param('tmp_dir') )
            . "\n",
    );

    my $error;

    rmtree( $publican->param('tmp_dir') );
    rmtree( $opts{pub_dir} );

    my $books = $publican->param('books') || "";
    foreach my $book ( split( " ", $books ) ) {
        rmtree("$book/tmp");
        rmtree("$book/$opts{pub_dir}");
    }
}

if ( $action eq 'clean_set' ) {
    my $books = $publican->param('books') || "";
    foreach my $book ( split( " ", $books ) ) {
        rmtree("$book");
    }
}

if ( $action eq 'clean_ids' ) {
    my $builder = Publican::Builder->new();
    $builder->clean_ids();
}

if ( $action eq 'trans_drop' ) {
    my $translater = Publican::Translate->new();
    $translater->trans_drop();
}

if ( $action eq 'update_pot' ) {
    my $translater = Publican::Translate->new();
    $translater->update_pot();
}

if ( $action eq 'update_po' ) {
    my $translater = Publican::Translate->new();
    if ( !defined $opts{langs} || $opts{langs} eq 'all' ) {
        $translater->update_po_all(
            {   msgmerge  => $opts{msgmerge},
                email     => $opts{email},
                firstname => $opts{firstname},
                surname   => $opts{surname}
            }
        );
    }
    else {
        $translater->update_po(
            {   langs     => $opts{langs},
                msgmerge  => $opts{msgmerge},
                email     => $opts{email},
                firstname => $opts{firstname},
                surname   => $opts{surname}
            }
        );
    }
}

if ( $action eq 'lang_stats' ) {
    my $translater = Publican::Translate->new();
    if ( $opts{lang} eq 'all' ) {
        foreach my $lang ( split( /,/, get_all_langs() ) ) {
            $translater->po_report( { lang => $lang } );
        }
    }
    else {
        $translater->po_report( { lang => $opts{lang} } );
    }
}

if ( $action eq 'build' ) {
    my $builder = Publican::Builder->new( { novalid => $opts{novalid} } );
    $builder->build(
        {   formats         => $opts{formats},
            langs           => $opts{langs},
            publish         => $opts{publish},
            embedtoc        => $opts{embedtoc},
            distributed_set => $opts{distributed_set},
            pdftool         => $opts{pdftool},
            pub_dir         => $opts{pub_dir},
        }
    );
}

if ( $action eq 'add_revision' ) {

    if ( $publican->param('type') eq 'brand' ) {
        croak( maketext("add_revision does not work for brands.") );
    }

    $publican->add_revision(
        {   lang      => $opts{lang},
            revnumber => $opts{revnumber},
            date      => $opts{date},
            members   => $opts{member},
            email     => $opts{email},
            firstname => $opts{firstname},
            surname   => $opts{surname},
        }
    );
}

if ( $action eq 'package' ) {
    my $builder = Publican::Builder->new();
    if ( $publican->param('type') eq 'brand' ) {
        $builder->package_brand( { binary => $opts{binary} } );
    }
    elsif ( $publican->param('web_home') || $publican->param('web_type') ) {
        $builder->package_home( { binary => $opts{binary} } );
    }
    else {
        $builder->package(
            {   lang          => $opts{lang},
                desktop       => $opts{desktop},
                short_sighted => $opts{short_sighted},
                binary        => $opts{binary}
            }
        );
    }

    if ( $opts{brew} ) {

        my @filelist = File::Find::Rule->file->name('*.src.rpm')
            ->in( $publican->param('tmp_dir') );

        my $srpm = pop(@filelist);

        my @brew_args = ( "brew", "build" );
        push( @brew_args, "--scratch" ) if ( $opts{scratch} );
        if ( $opts{'wait'} ) {
            push( @brew_args, "--wait" );
        }
        else {
            push( @brew_args, "--nowait" );
        }

        push( @brew_args, $publican->param('brew_dist') );

        if ( -f "$srpm" ) {
            my $result = system( @brew_args, "$srpm" );
            if ($result) {
                croak( maketext("Brew died, error $result: '$!'") );
            }
        }
        else {
            croak( maketext("Can't locate srpm, packaging aborted") );
        }
    }
}

if ( $action eq 'install_book' ) {
    my $site_config = $opts{site_config} || undef;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    if ( $publican->param('web_home') || $publican->param('web_type') ) {
        croak( maketext("can't find 'publish' directory") )
            unless ( -d "$opts{pub_dir}/home" );
        rcopy( "$opts{pub_dir}/home/*", $ws->toc_path() . "/." );
    }
    else {
        my $lang = $opts{lang}
            || croak( maketext( "[_1] is a required argument", 'lang' ) );
        my $product       = $publican->param('product');
        my $version       = $publican->param('version');
        my $name          = $publican->param('docname');
        my $product_label = $publican->param('web_product_label');
        my $version_label = $publican->param('web_version_label');
        my $name_label    = $publican->param('web_name_label');
        my $sort_order    = $publican->param('sort_order');

        my $subtitle = $publican->get_subtitle( { lang => $lang } );
        my $abstract = $publican->get_abstract( { lang => $lang } );

        croak(
            maketext(
                "can't find publish directory '[_1]/[_2]'", $opts{pub_dir},
                $lang
            )
        ) unless ( -d "$opts{pub_dir}/$lang" );

        my $type     = $publican->param('type');
        my $xml_lang = $publican->param('xml_lang');
        my $tmp_dir  = $publican->param('tmp_dir');

        # get translated labels
## BUGBUG this is copied from Publican.pm, maybe a sub for these?
        if ( $lang ne $xml_lang ) {
            my $xml_file = "$tmp_dir/$lang/xml/$type" . '_Info.xml';
            $xml_file = "$tmp_dir/$lang/xml/" . $publican->param('info_file')
                if ( $publican->param('info_file') );

            croak( maketext( "Can't locate required file: [_1]", $xml_file ) )
                if ( !-f $xml_file );

            my $xml_doc = XML::TreeBuilder->new();
            $xml_doc->parse_file($xml_file);

            # BUGBUG can't translate overridden labels :(
            unless ($product_label) {
                $product_label = eval {
                    $xml_doc->root()->look_down( "_tag", "productname" )
                        ->as_text();
                };
                if ($@) {

    #                    croak maketext("productname not found in Info file");
                }
                else {
                    $product_label =~ s/\s/_/g;

                    $product_label = undef if ( $product_label eq $product );
                }
            }

            unless ($name_label) {
                $name_label = eval {
                    $xml_doc->root()->look_down( "_tag", "title" )->as_text();
                };
                if ($@) {

          #                    croak maketext("title not found in Info file");
                }
                else {
                    $name_label =~ s/\s/_/g;
                    $name_label = undef if ( $name_label eq $name );
                }
            }
        }

        mkpath( $ws->toc_path() . "/$lang" )
            unless ( -d $ws->toc_path() . "/$lang" );
        rcopy( "$opts{pub_dir}/$lang/*", $ws->toc_path() . "/$lang/." );

##        my @formats = split( /,/, $publican->param('web_formats') );
##        foreach my $format (@formats) {
##            next unless ( -d "publish/$lang/$product/$version/$format" );

        $ws->update_or_add_entry(
            {   language      => $lang,
                product       => $product,
                version       => $version,
                name          => $name,
                formats       => $publican->param('web_formats'),
                product_label => $product_label,
                version_label => $version_label,
                name_label    => $name_label,
                subtitle      => $subtitle,
                abstract      => $abstract,
                sort_order    => $sort_order,
            }
        );
##        }
    }

    $ws->regen_all_toc();
}

if ( $action eq 'remove_book' ) {
    my $site_config = $opts{site_config} || undef;
    my $ws = Publican::WebSite->new( { site_config => $site_config } );

    my $lang = $opts{lang}
        || croak( maketext( "[_1] is a required argument", 'lang' ) );
    my $product = $publican->param('product');
    my $version = $publican->param('version');
    my $name    = $publican->param('docname');

    $ws->del_entry(
        {   language => $lang,
            product  => $product,
            version  => $version,
            name     => $name,
        }
    );

## BUGBUG should we fetch formats from DB?
    my @formats = split( /,/, $publican->param('web_formats') );
    foreach my $format (@formats) {
        my $dir = "$ws->{toc_path}/$lang/$product/$version/$format/$name";
        rmtree($dir);

        # delete empty directories
        $dir = "$ws->{toc_path}/$lang/$product/$version/$format";
        unless ( File::Find::Rule->not_name(qr/(\.|\.\.)/)->in($dir) ) {
            rmtree($dir);
            $dir = "$ws->{toc_path}/$lang/$product/$version";
            unless ( File::Find::Rule->not_name(qr/(\.|\.\.)/)->in($dir) ) {
                rmtree($dir);
                $dir = "$ws->{toc_path}/$lang/$product";
                unless ( File::Find::Rule->not_name(qr/(\.|\.\.)/)->in($dir) )
                {
                    rmtree($dir);
                }
            }
        }
    }
    $ws->regen_all_toc();
}

if ( $action eq 'rename' ) {

    my $conf_file = ( $opts{config} || 'publican.cfg' );
    my $config = new Config::Simple($conf_file);

    $config->syntax('http');

    # load in $type_Info.xml
    my $file
        = $config->param('xml_lang') . '/'
        . ( $config->param('type') || 'Book' )
        . '_Info.xml';
    $file = $config->param('xml_lang') . "/" . $config->param('info_file')
        if ( $config->param('info_file') );

    my $cleaner = Publican::XmlClean->new( { clean_id => 1 } );

    my $xml_doc = XML::TreeBuilder->new(
        { 'NoExpand' => "1", 'ErrorContext' => "2" } );
    $xml_doc->store_comments(1);
    $xml_doc->parse_file($file)
        || croak( maketext( "Can't open file '[_1]' [_2]", $file, $@ ) );

    if ( $opts{name} ) {

        # set mainfile in publican.cfg if it's not set
        unless ( $config->param('mainfile') ) {
            $config->param( 'mainfile',
                $publican->{config}->param('docname') );
        }

        # delete docname
        $config->delete('docname');

        # Change Title
        my $title = $xml_doc->root()->look_down( "_tag", "title" )
            || croak( maketext('Cannot locate title tag in Info file!') );
        $title->delete_content();
        $title->push_content( $opts{name} );
    }

    # Change Product Name
    if ( $opts{product} ) {
        my $prod = $xml_doc->root()->look_down( "_tag", "productname" )
            || croak(
            maketext('Cannot locate productname tag in Info file!') );
        $prod->delete_content();
        $prod->push_content( $opts{product} );
    }

    # Change Product Number
    if ( $opts{version} ) {
        my $prod_num = $xml_doc->root()->look_down( "_tag", "productnumber" )
            || croak(
            maketext('Cannot locate productnumber tag in Info file!') );
        $prod_num->delete_content();
        $prod_num->push_content( $opts{version} );
    }

    # write out new files
    $config->write($conf_file);
    $cleaner->print_xml( { xml_doc => $xml_doc, out_file => $file } );

    # clean up memory
    $xml_doc->root()->delete();
}

exit(0);

# Secret action to create bash completion file.
# Nothing special just don't want to confuse people by discussing
# it in the general help

sub _bash_completion {

    my $actions = join( " ", sort( keys(%actions) ) );
    my $util_opts = "";
    foreach my $opto ( sort(@utility_opts) ) {
        $opto =~ s/=.*$//g;
        $util_opts .= "--$opto ";
    }

    my $top = <<TOP;
# publican completion

_publican()
{
	local cur prev commands options command

	COMPREPLY=()
	cur=`_get_cword`
        brands=`ls $DATA_DIR/Common_Content`

	commands='$actions'

	if [[ \$COMP_CWORD -eq 1 ]] ; then
		if [[ "\$cur" == -* ]]; then
			COMPREPLY=( \$( compgen -W '--help' -- \$cur ) )
		else
			COMPREPLY=( \$( compgen -W "\$commands" -- \$cur ) )
		fi
	else
		prev=\${COMP_WORDS[COMP_CWORD-1]}
		case \$prev in
			@(--site_config|--config))
				_filedir 'cfg'
				return 0;
				;;
			@(--lang|--langs))
				COMPREPLY=( \$( compgen -d -- "\$cur" ) )
				return 0;
				;;
			--formats)
				COMPREPLY=( \$( compgen -W "eclipse epub drupal-book html html-single html-desktop man pdf txt xml" -- \$cur ) )
				return 0;
				;;
			--type)
				COMPREPLY=( \$( compgen -W "Article Book Set" -- \$cur ) )
				return 0;
				;;
			--brand)
				COMPREPLY=( \$( compgen -W "\$brands" -- \$cur ) )
				return 0;
				;;
		esac

		command=\${COMP_WORDS[1]}

		if [[ "\$cur" == -* ]]; then
			# possible options for the command
			case \$command in
TOP

    my $options = "";

    foreach my $action ( sort( keys(%actions) ) ) {
        my $opt = "";
        foreach my $option ( sort( @{ $actions{$action}{'options'} } ) ) {
            $opt .= "--$option ";
        }

        $options .= <<EOO;
				$action)
					options='$opt'
					;;
EOO

    }

    my $bottom = <<BOTTOM;
			esac
			options="\$options $util_opts"
			COMPREPLY=( \$( compgen -W "\$options" -- \$cur ) )
		else
			if [[ "\$command" == @(--help) ]]; then
				COMPREPLY=( \$( compgen -W "\$commands" -- \$cur ) )
			else
				_filedir
			fi
		fi
	fi

	return 0
}
complete -F _publican publican
BOTTOM

    my $COMP_FILE;
    open( $COMP_FILE, ">", "completion/_publican" )
        || croak("file open fail: @_");
    print( $COMP_FILE $top );
    print( $COMP_FILE $options );
    print( $COMP_FILE $bottom );
    close($COMP_FILE);
    return;
}

