
package Text::Wigwam;

# This class represents the front-end user interface.

# Latest Wigwam version and documentation are available at
# http://www.wigwamhq.org

# Config options handled by the common interface:
#        modules
#        plugins
#        engine
#        default_engine
#        directive_path
#        directive_root
#        config_open
#        config_term
#        escape_var

use 5.008;
use strict;
use warnings;
our $VERSION = '0.02';

use base qw(
    Text::Wigwam::Api
    Text::Wigwam::Expression
);

use Text::Wigwam::Globals;
use Text::Wigwam::Stash;
use Text::Wigwam::Dtree;
use Text::Wigwam::Beautify::Text;
use Text::Wigwam::Beautify::Html;
use Text::Wigwam::Loader;
use Text::Wigwam::Config;
use Text::Wigwam::Paths;
use Text::Wigwam::Debug;
use Text::Wigwam::Fetch;
use Text::Wigwam::Virtual;
use Text::Wigwam::Overload;

use Scalar::Util 1.08 ( qw/blessed reftype/ );
use File::Spec::Functions(qw/catfile/);

our $ERROR;    # Holds the most recent error message.

# The $plugins variable is used for caching the code
# references that are used to initialize engines and plug-ins.
my $plugins = {};

# The $beautifiers variable is used to cache loaded beautifiers.
our $beautifiers = {};

sub new {
    my ( $class, $type, $docu, %uopts ) = @_;
    local $_;
    $ERROR = '';
    unless ( defined $docu ) {
        $ERROR = "A required argument is missing";
        return;
    }
    my $opts = {};
    map { $opts->{ lc $_ } = $uopts{$_} } keys %uopts;

#        $opts->{lc $_} = $uopts{$_} for keys %uopts;
    my $wwroot;
    unless ( ref($class) ) {
        unless ( ($wwroot) = __FILE__ =~ /^(.*)\..*$/ ) {
            $ERROR = "Unable to locate Wigwam library";
            return;
        }

        my $paths = Text::Wigwam::Paths->new(
          qw/
            module
            plugin
            engine
            template
            beautifier
            perlmod
          /
        );
        $paths->set( module     => catfile( $wwroot, q/Modules/  ) );
        $paths->set( plugin     => catfile( $wwroot, q/Plugins/  ) );
        $paths->set( engine     => catfile( $wwroot, q/Engine/   ) );
        $paths->set( beautifier => catfile( $wwroot, q/Beautify/ ) );
        $paths->set( perlmod    => @INC );
        $paths->pre( template   => catfile( $wwroot, q/Templates/  ) );

        return $class->_new(
            $type => $docu,
            $class->_default_config($opts),
            $paths,
            $class->_init_globals($opts->{global}),
            $opts->{varspace}
        );
    }

     # Merge new global values with existing global values
    if( $opts->{global} ) {
        my $global_hash
            = $class->globals->get_global( q/global/ )
           || $class->globals->set_global( global => {} );
         # give presidence to existing values
        %$global_hash = (
            %{$opts->{global}||{}},
            %$global_hash,
        );
    }

    return $class->_new(
        $type => $docu,
        $class->_default_config($opts),
        $class->paths,
        $class->globals,
        $opts->{varspace} || $class->{varspace},
    );
}

sub spawn {
    # Spawn a child template
    my ( $self, @args ) = @_;
    return ( "Spawn must be called on a blessed reference" )
        unless ref $self;
    local ( $_ );
    return ( ${$self->globals->get_global( q/_DIE/ )} )
        unless $self->root_expr;
    my $obj;
    return $self->{engine}->spawn(@args);
}

sub error {
    my ($class) = @_;
    return ref($class)
           ? $class->{ERROR}
           : $ERROR
    ;
}

sub text    { return $_[0]->{text} }
sub _export { $_[0]->{export} ||= {} }

sub execute {
    my ( $self, $Args ) = @_;
    local( $_ );

    $self->{ERROR} = undef;
    ( my $globals = $self->globals )->reset_eflags;

    return ( $self->{ERROR} = ${$globals->get_global( q/_DIE/ )} )
        unless ( my $root_expr = $self->root_expr() );

    my ($err, $text)
      = $root_expr->execute( $self->{engine}, $Args );

    return ($err, $self->{text} = $text) if wantarray;
    return $self->{text} = $text;
}

sub virtual {
    my ( $self, $Args ) = @_;
    unless ( exists $self->{engine} ) {
        my ($err) = $self->execute($Args);
        die $err if $err;
    }
    return Text::Wigwam::Virtual->new($self);
}

sub debug {
    my ($self, $beau, $tdebug) = @_;
    local ( $ERROR, $_ );

    unless ( $self->_valid_beautifier($beau) ) {
        warn 'Invalid beautifier class: '
            . ( ref $beau || $beau )
            . ' - Using default beautifier.';
        $beau = $self->new_beautifier('Text::Wigwam::Beautify::Text');
    }
    my $deftemp = $beau->can('default_template')
         ? $beau->default_template
         : "No default template exists for the "
           . ( ref($beau) || $beau )
           . " beautifier class."
    ;
    unless ( blessed($tdebug) && $tdebug->isa(q/Text::Wigwam/) ) {
        $tdebug = $deftemp
            || $self->default_template() or return( $ERROR );
    }

    unless ( exists $self->{engine} ) {
        return (
            'Debug is only useful after the execute method has been invoked.'
        );
        return ( $self->{ERROR} = $ERROR )
            unless $self->root_expr;
    }
    my $bugs  = $self->get_global( q/_BUGS/ )
             || Text::Wigwam::Debug->new;

    my ( $error, $eflag );
    $eflag = $self->eflag
        and $error = "$eflag: " . ${$self->get_global($eflag)};

    my $debug = {
        docu => $self->doc,
        text => $self->text,
        size => $self->root_expr->size,
        beau => $self->beautify( $beau->new() ),
        erro => $error,
        bugs => [],
    };
    my $id = 0;
    foreach my $bug ( @{$bugs->get} ) {
        push(
            @{ $debug->{bugs} },
            {   docu => $bug->doc,
                engn => $bug->API->name,
                text => $self->text,
                size => $bug->expr->size,
                comm => $bug->get_comments,
                erro => $bug->error,
                opts => $bug->options,
                beau => $bug->expr->beautify(
                    $bug->API,
                    $beau->new(
                        $bug->addr, $bug->error, $bug->get_comments, $id,
                    )
                ),
                uniq => $id++,
            }
        );
    }
    return $tdebug->execute( { DEBUG => $debug, TEMPLATE => $deftemp } );
}

sub doc  { $_[0]->{document} }
sub size { $_[0]->root_expr->size }
sub paths { $_[0]->{paths} }
sub config { $_[0]->{config} }
sub engine { $_[0]->root_expr->engine }
sub globals { $_[0]->{globals} }
sub stash { $_[0]->{stash} }
sub beautify {
    my ( $self, $beau ) = @_;
    local ( $_, $ERROR );

    unless ( $self->_valid_beautifier($beau) ) {
        warn 'Invalid beautifier class: '
            . ( ref $beau || $beau )
            . ' - Using default beautifier.';
        $beau = $self->new_beautifier('Text');
    }
    return ( $self->{ERROR} = $ERROR ) unless $self->root_expr;
    return $self->root_expr->beautify( $self->{engine},
        ref $beau ? $beau : $beau->new );
}

sub root_expr { return $_[0]->{engine}{root_expr} ||= $_[0]->_init }

sub prime {
    my ($self) = @_;
    $self->root_expr->prime( $self->{engine} );
    return $self;
}

sub version {
    no strict 'refs';
    my ($self) = @_;
    my $pkg = ref $self || $self;
    return ${"${pkg}::VERSION"};
}

sub _new {
    no strict 'refs';
    my ( $class, $type, $doc, $config, $paths, $globals, $u_vspace ) = @_;
    my $self = bless {}, ref($class) || $class;
    $type = lc $type;
    @$self{qw/ config paths globals /}
        = ( $config, $paths, $globals );

#    return ( undef, "Unsupported template retrieval method: $type" )
#        unless Text::Wigwam::Fetch->can($type);
#    my $fetcher = "Text::Wigwam::Fetch::$type";
#    ( $ERROR, $self->{document}, $self->{template} )
#        = $fetcher->( $self, $doc, $paths->finder(q/template/) );

    return ( undef, "Unsupported template retrieval method: $type" )
        unless Text::Wigwam::Fetch->can($type);
    ( $ERROR, $self->{document}, $self->{template} )
        = Text::Wigwam::Fetch->$type($self, $doc);

    return if $ERROR;

    # We need to peek at the parameters early in order to determine
    # what config tag delimiters to look for within the template.
    my $restrict = $config->crank(
        qw/
            restrict
            options
            settings
            defaults
        /
    );

    $self->{template} = $config->parse_config(
        $self->{template},
        $restrict->{config_open},
        $restrict->{config_term},
    );

    $config->cache( options => qw/restrict options settings defaults/ );

    $self->{stash} = Text::Wigwam::Stash->new(
        varspace => $u_vspace,
        globals  => $globals,
    );

    return $self;
}

sub _default_config {
    my ( $self, $opts ) = @_;
    my $wwconfig = Text::Wigwam::Config->new(
        restrict => {
            config_open    => '<<',
            config_term    => '>>',
            default_engine => 'Fusion',
        },

        # Preset root template settings
        settings => { plugins => 'DirectiveSet', },

        # Preset global defaults
        defaults => {},
        options  => undef,
        aliases  => undef,
    );

    # Use presets for the following classes only when user settings are
    # not provided.
    $wwconfig->concede(qw/settings/);

    # Confine the following class parameters to the current template
    # only.
    $wwconfig->confine(qw/ settings options /);

    # Give priority to inherited settings over conftag settings for the
    # specified classes.
    $wwconfig->promote(qw/restrict/);

    # Exclude the following classes' conftag parameters when generating
    # an options hash.
    $wwconfig->exclude(qw/restrict defaults settings/);

    $wwconfig->inherit(
        restrict => $opts->{restrict},
        defaults => $opts->{defaults},
        settings => $opts->{settings},
        aliases  => $opts->{aliases},
        options  => {},
    );
    return $wwconfig;
}

sub _valid_beautifier {
    no strict 'refs';
    my ( $self, $pkg ) = @_;
    unless ( ref($pkg) ) {
        return unless $pkg && length($pkg);
        return $pkg ne 'Text::Wigwam::Beautifier'
            && $pkg->isa(q/Text::Wigwam::Beautifier/);
    }
    return unless blessed $pkg;
    return $pkg->isa('Text::Wigwam::Beautifier');
}

sub _init_globals {
    my ( $class, $uglobals ) = @_;
    my $globals = Text::Wigwam::Globals->new();

    $globals->new_fatal(  GLOBAL => q/_DIE/ );
    $globals->new_global( GLOBAL => q/args/ );
    $globals->new_global( GLOBAL => global  => $uglobals || {} );
#    $globals->new_global( GLOBAL => template => {} );
    $globals->new_global( GLOBAL => _BUGS   => Text::Wigwam::Debug->new() );
#    $globals->new_global( GLOBAL => plugins => $plugins );
    return $globals;
}

sub _init {
    my ( $self ) = @_;
    no warnings q/uninitialized/;
    local $_;
    my $defaults = $self->config->crank(q/defaults/);
    my $options  = $self->config->cache(q/options/);

    if ( my @plugins = split( /\s*,\s*/, $self->get_options(qw/plugins/) ) ) {
          my $err = Text::Wigwam::Directives->load_up(
            $plugins,    # Plugin init cache
            $self->config->cache(q/aliases/),
            Text::Wigwam::ApiJr->new( $self->globals ),
            $self->paths->finder(q/plugins/),
            @plugins,
          );
#          $self->set_global( _DIE => $err )
          $self->set_global( _DIE => \($self->doc . " - " . $err) )
            if $err;
    }
    if ( my @modules = split( /\s*,\s*/, $self->get_options(qw/modules/) ) ) {
          my $err = Text::Wigwam::Directives->load_up(
            {},          # Pass in a dummy cache hash ref
            {},          # Ditto - no alias definitions allowed here
            Text::Wigwam::ApiJr->new( $self->globals ),
            $self->paths->finder(q/modules/),
            @modules,
          );
          $self->set_global( _DIE => \($self->doc . " - " . $err) )
#          $self->set_global( _DIE => $err )
            if $err;
    }
    return $self->{engine}->init( $self->{template} )
      if $self->_init_engine;
    return;
}

sub _init_engine {    # Engine/API init routine
    no strict 'refs';
    no warnings 'uninitialized';
    my ($self) = @_;

    my $defaults = $self->config->crank(q/defaults/);
    my $options  = $self->config->cache(q/options/);

     # Ignore duplicate engine names
    my %done;
    my @engines = grep { ++$done{ucfirst lc $_} == 1 } (
        split( /\s*,\s*/, $options->{engine} ),
        split( /\s*,\s*/, $options->{default_engine} ),
    );

    foreach my $eng (@engines) {
        $options->{engine} = $eng = ucfirst lc $eng;
        if ( my $pkg = $self->_is_loaded($eng) ) {
            my $eng_defaults = ($plugins->{$pkg} || sub{ {} })->();
#            my $eng_defaults = $plugins->{$pkg}->() || {};
            if( ref $eng_defaults eq 'HASH' ){
                %$defaults = ( %$eng_defaults, %$defaults );
                %$options  = ( %$eng_defaults, %$options );
            }

            my $engine = $pkg->new(

                # Exclusive Engine data
                Dtree => Text::Wigwam::Dtree->new(
                    q/Text::Wigwam/,
                    q/Directives/,
                    $options->{directive_root},
                    [ split( /\s*,\s*/, $options->{directive_path} ) ],
                    ( $options->{drip_cache} & 1 ),
                ),

                # Common API/Engine data
                Doc     => $self->doc,
                Globals => $self->globals,
                Stash   => $self->stash,
                Modtime => $self->{mod_time},
                Config  => $self->config,
                Paths   => $self->paths,
                Export  => $self->_export,

                # Exclusive API data
            );
            return $self->{engine} = $engine;
        }
    }
    $self->globals->set_global(
      _DIE => \("Unable to initialize Wigwam engine: " . join( "; ", @engines ))
    );
    return;
}

sub _is_loaded {
    no strict 'refs';
    my ( $self, $engine ) = @_;

    return if $engine =~ /^\s*$/;

    my $pkg = "Text::Wigwam::Engine::$engine";
    return $pkg if $pkg->isa(q/Text::Wigwam::Engine/);

    my $engfile = $self->paths->find_file( engines => "$engine.pm" )
        or return;

    my ($engdir) = $engfile =~ /^(.*)\.pm/;
    local @INC = ( $engdir, @INC ) if -d $engdir;

    local $@;
    $plugins->{$pkg} ||= eval { require $engfile };

    if ($@){
        $self->globals->set_global( _DIE => \$@ );
    }
    else {
        $plugins->{$pkg} = sub { }
            unless ref( $plugins->{$pkg} ) eq 'CODE';
        return $pkg
          if $pkg->isa(q/Text::Wigwam::Engine/);
    }
    delete $plugins->{$pkg};
    return;
}

sub new_beautifier {
    my ( $self, $beauname ) = @_;
    $beauname = ucfirst lc $beauname;
    my $beaut = $beautifiers->{$beauname}
        ||= $self->fetch_beautifier($beauname)
          or return ( $ERROR );

    return $beaut->new;
      # if ref $beaut && blessed $beaut && $beaut->can( 'new' );
}

sub fetch_beautifier {
    my ( $self, @blist ) = @_;
    my @loaded = eval {
        Text::Wigwam::Loader->fetch_package(
            $beautifiers,
            Text::Wigwam::ApiJr->new( $self->globals ),
            $self->paths->finder(q/beautifier/), @blist,
        );
    };
    $ERROR = $@ and return;
    for my $rval (@loaded) {
        if ( ref($rval) eq 'CODE' ) {
            $rval = $rval->();
#            $rval = eval { $rval->() };
#            die $@ if $@;
        }

        # $fname =~ s{/}{::}g;
    }
    return wantarray ? @loaded : $loaded[0]
        unless $@;
    $ERROR = $@;
    return;
}

sub default_template {
    return Text::Wigwam->new(
        find     => '/Core/Beautify/Text',
        restrict => {
            config_open    => q/<</,
            config_term    => q/>>/,
            default_engine => q/Totem, Fusion/,
        },
        defaults => {
            strict_tags    => 1,
            text_open      => q/!!>/,
            text_term      => q/<!!/,
            code_open      => q/[!!/,
            code_term      => q/!!]/,
            text_open_trim => q/~!>/,
            text_term_trim => q/<!~/,
            code_open_trim => q/[!~/,
            code_term_trim => q/~!]/,
        },
        settings => {
            plugins  => 'DirectiveSet',
        },
    ) or die $Text::Wigwam::ERROR;
}

1;
