The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#
# This file is part of Config-Model-TkUI
#
# This software is Copyright (c) 2016 by Dominique Dumont.
#
# This is free software, licensed under:
#
#   The GNU Lesser General Public License, Version 2.1, February 1999
#

package Config::Model::Tk::Wizard;
$Config::Model::Tk::Wizard::VERSION = '1.351';
use strict;
use warnings;
use Carp;
use Try::Tiny;

use base qw/Tk::Toplevel/;
use vars qw/$icon_path/;
use Log::Log4perl;

use Config::Model::Tk::LeafEditor;
use Config::Model::Tk::CheckListEditor;
use Config::Model::Tk::ListEditor;
use Config::Model::Tk::HashEditor;

Construct Tk::Widget 'ConfigModelWizard';

my $logger = Log::Log4perl::get_logger('Tk::Wizard');

my @fbe1 = qw/-fill both -expand 1/;
my @fxe1 = qw/-fill x    -expand 1/;
my @fx   = qw/-fill x    /;

sub ClassInit {
    my ( $class, $mw ) = @_;

    # ClassInit is often used to define bindings and/or other
    # resources shared by all instances, e.g., images.

    # cw->Advertise(name=>$widget);
}

sub Populate {
    my ( $cw, $args ) = @_;

    foreach my $parm (qw/-root/) {
        my $attr = $parm;
        $attr =~ s/^-//;
        $cw->{$attr} = delete $args->{$parm}
            or croak "Missing $parm arg\n";
    }

    foreach my $parm (qw/-from_widget -stop_on_important -store_cb -show_cb -end_cb/) {
        my $attr = $parm;
        $attr =~ s/^-//;
        $cw->{$attr} = delete $args->{$parm};
    }

    $logger->info("Creating wizard widget");
    $cw->{show_cb}  ||= sub { };
    $cw->{store_cb} ||= sub { };
    $cw->{has_stopped} = 0;

    my $title = delete $args->{'-title'}
        || "config wizard " . $cw->{root}->config_class_name;

    $cw->Label(
        -text => "Configuration of " . $cw->{root}->config_class_name,
        -font => [ -size => 20 ],
    )->pack;

    my $ed = $cw->{ed_frame} = $cw->Frame->pack(qw/-pady 0 -fill both -expand 1 -anchor n/);
    $cw->{ed_frame}->packPropagate(0);

    $args->{-title} = $title;
    $cw->SUPER::Populate($args);

    $cw->Advertise( ed_frame => $ed, );

    $cw->ConfigSpecs(

        #-background => ['DESCENDANTS', 'background', 'Background', $background],
        #-selectbackground => [$hlist, 'selectBackground', 'SelectBackground',
        #                      $selectbackground],
        -width  => [ $ed, undef, undef, 600 ],
        -height => [ $ed, undef, undef, 400 ],
        DEFAULT => [$ed] );

}

sub save {
    my $cw = shift;

    $cw->check();

    $logger->info("Saving data in default directory with instance write_back");
    $cw->{root}->instance->write_back();
}

sub leaf_cb {
    my ( $cw, $scanner, $data_ref, $node, $element_name, $index, $leaf_object ) = @_;
    $cw->{has_stopped} = 1;

    # cleanup existing widget contained in this frame
    $cw->{show_cb}->($leaf_object);
    $cw->{ed_w} = $cw->{ed_frame}->ConfigModelLeafEditor(
        -item     => $leaf_object,
        -store_cb => $cw->{store_cb},
    )->pack(@fbe1);
}

sub list_element_cb {
    my ( $cw, $scanner, $data_ref, $node, $element_name, @indexes ) = @_;
    $cw->{has_stopped} = 1;

    # cleanup existing widget contained in this frame
    my $obj = $node->fetch_element($element_name);
    $cw->{show_cb}->($obj);
    $cw->{ed_w} = $cw->{ed_frame}->ConfigModelListEditor(
        -item     => $obj,
        -store_cb => $cw->{store_cb},
    )->pack(@fbe1);
}

sub hash_element_cb {
    my ( $cw, $scanner, $data_ref, $node, $element_name, @keys ) = @_;
    $cw->{has_stopped} = 1;

    # cleanup existing widget contained in this frame
    my $obj = $node->fetch_element($element_name);
    $cw->{show_cb}->($obj);
    $cw->{ed_w} = $cw->{ed_frame}->ConfigModelHashEditor(
        -item     => $obj,
        -store_cb => $cw->{store_cb},
    )->pack(@fbe1);
}

sub check_list_element_cb {
    my ( $cw, $scanner, $data_ref, $node, $element_name, @items ) = @_;
    $cw->{has_stopped} = 1;

    # cleanup existing widget contained in this frame
    my $obj = $node->fetch_element($element_name);
    $cw->{show_cb}->($obj);
    $cw->{ed_w} = $cw->{ed_frame}->ConfigModelCheckListEditor(
        -item     => $obj,
        -store_cb => $cw->{store_cb},
    )->pack(@fbe1);
}

sub prepare_wizard {
    my ( $cw, %args ) = @_;

    my $text =
          'The wizard will scan all configuration items and stop on '
        . '"important" items or on error (like missing mandatory values). If no '
        . '"important" item and no error are found, the wizard will exit immediately';

    my $edf = $cw->{ed_frame};

    my $textw =
        $edf->ROText( qw/-relief flat -wrap word -height 8/, -font => [ -family => 'Arial' ] );
    $textw->insert( end => $text );
    $textw->pack( qw/-side top -anchor n/, @fxe1 );

    my $stop_on_warn = 0;
    $edf->Checkbutton( -text => 'stop on warning', -variable => \$stop_on_warn )
        ->pack(qw/-side top -anchor w/);

    $edf->Button(
        -text    => 'OK',
        -command => sub { $cw->start_wizard($stop_on_warn) } )->pack(qw/-side right -anchor e/);
    $edf->Button(
        -text    => 'cancel',
        -command => sub { $cw->destroy_wizard() } )->pack(qw/-side left -anchor w/);
}

sub start_wizard {
    my ( $cw, %args ) = @_;

    my $button_f = $cw->Frame->pack(qw/-pady 0 -fill x -expand 1/);
    $cw->{has_stopped} = 0;

    my $back = $button_f->Button(
        -text    => 'Back',
        -command => sub {
            $cw->{keep_wiz_editor} = 0;
            $cw->{ed_w}->store if $cw->{ed_w}->can('store');
            $cw->{wizard}->go_backward;
        } );
    $back->pack(qw/-side left -fill x -expand 1/);

    my $stop = $button_f->Button(
        -text    => 'Store and stop',
        -command => sub {
            $cw->{ed_w}->store if $cw->{ed_w}->can('store');
            $cw->{keep_wiz_editor} = 0;
            $cw->{wizard}->bail_out;
        } );
    $stop->pack(qw/-side left -fill x -expand 1/);

    my $quit = $button_f->Button(
        -text    => 'quit wizard',
        -command => sub {
            $cw->{keep_wiz_editor} = 0;
            $cw->{wizard}->bail_out;
        } );
    $quit->pack(qw/-side left -fill x -expand 1/);

    my $forw = $button_f->Button(
        -text    => 'Next',
        -command => sub {
            $cw->{keep_wiz_editor} = 0;
            $cw->{ed_w}->store if $cw->{ed_w}->can('store');
            $cw->{wizard}->go_forward;
        } );
    $forw->pack(qw/-side right -fill x -expand 1/);

    my ( $sort_element, $sort_idx );
    $cw->{keep_wiz_editor} = 1;

    my %cb_table;

    # a local event loop is run within the call-back
    foreach my $cb_key (
        qw/leaf_cb check_list_element_cb
        list_element_cb hash_element_cb/
        ) {
        $cb_table{$cb_key} = sub {
            my ( $scanner, $data_ref, $node, $element_name ) = @_;
            my @all_args = @_;    # @_ does not work in try blocks
            $logger->info( "$cb_key (element $element_name) called on '",
                $node->name, "'->'$element_name'" );
            map { $_->destroy if Tk::Exists($_) } $cw->{ed_frame}->children;
            $cw->{keep_wiz_editor} = 1;
            try {
                $cw->$cb_key(@all_args);
            }
            catch {
                $cw->{keep_wiz_editor} = 0;    # destroy wizard in case of error
            };
            my $loop_c = 0;
            $logger->debug( "$cb_key wizard entered local loop ", ++$loop_c );
            $cw->DoOneEvent() while $cw->{keep_wiz_editor};
            $logger->debug( "$cb_key wizard exited local loop ", $loop_c );
        };
    }

    my @wiz_args = ( %cb_table );

    foreach (qw/warning important/) {
        push @wiz_args, "call_back_on_$_" => $args{"stop_on_$_"}
            if defined $args{"stop_on_$_"};
    }

    #Tk::ObjScanner::scan_object(\@wiz_args) ;
    $cw->{wizard} = $cw->{root}->instance->iterator(@wiz_args);

    # exits when wizard is done
    $cw->{wizard}->start;
    $cw->destroy_wizard;
}

sub destroy_wizard {
    my $cw = shift;

    delete $cw->{ed_w};
    delete $cw->{wizard};

    # print "Destroying wizard\n" ;
    $logger->debug("Destroying wizard");
    $cw->destroy;

    if ( defined $cw->{end_cb} ) {
        $logger->debug("Calling end_cb");
        $cw->{end_cb}->( $cw->{has_stopped} );
    }
}

1;