The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Kephra::Menu;
our $VERSION = '0.18';

use strict;
use warnings;

my %menu;
sub _all { \%menu }
sub _ref {
	if    ( is($_[1]) )                { $menu{$_[0]}{ref} = $_[1] }
	elsif ( exists $menu{$_[0]}{ref} ) { $menu{$_[0]}{ref} }
}
sub _data        { $menu{$_[0]} if stored($_[0])  }
sub is           { 1 if ref $_[0] eq 'Wx::Menu'   }
sub stored       { 1 if ref $menu{$_[0]} eq 'HASH'}
sub set_absolete { $menu{$_[0]}{absolete} = 1     }
sub not_absolete { $menu{$_[0]}{absolete} = 0     }
sub is_absolete  { $menu{$_[0]}{absolete}         }
sub set_update   { $menu{$_[0]}{update} =  $_[1] if ref $_[1] eq 'CODE' }
sub get_update   { $menu{$_[0]}{update} }
sub no_update    { delete $menu{$_[0]}{update} if stored($_[0]) }
sub add_onopen_check {
	return until ref $_[2] eq 'CODE';
	$menu{ $_[0] }{onopen}{ $_[1] } = $_[2];
}
sub del_onopen_check {
	return until $_[1];
	delete $menu{$_[0]}{onopen}{$_[1]} if exists $menu{$_[0]}{onopen}{$_[1]};
}


sub ready          { # make menu ready for display
	my $id = shift;
	if ( stored($id) ){
		my $menu = _data($id);
		if ($menu->{absolete} and $menu->{update}) {
			 $menu->{absolete} = 0 if $menu->{update}() }
		if (ref $menu->{onopen} eq 'HASH')
			{ $_->() for values %{$menu->{onopen}} }
		_ref($id);
	}
}


sub create_dynamic { # create on runtime changeable menus
	my ( $menu_id, $menu_name ) = @_ ;

	if ($menu_name eq '&insert_templates') {

		set_absolete($menu_id);
		set_update($menu_id, sub {
			my $cfg = Kephra::API::settings()->{file}{templates}; 
			my $file = Kephra::Config::filepath($cfg->{directory}, $cfg->{file});
			my $tmp = Kephra::Config::File::load( $file );
			my @menu_data;
			if (exists $tmp->{template}){
				$tmp = Kephra::Config::Tree::_convert_node_2_AoH(\$tmp->{template});
				my $untitled = Kephra::Config::Localisation::strings()->{app}{general}{untitled};
				my $filepath = Kephra::Document::Data::get_file_path() || "<$untitled>";
				my $filename = Kephra::Document::Data::file_name() || "<$untitled>";
				my $firstname = Kephra::Document::Data::first_name() || "<$untitled>";
				for my $template ( @{$tmp} ) {
					my %item;
					$item{type} = 'item';
					$item{label}= $template->{name};
					$item{call} = sub {
						my $content = $template->{content};
						$content =~ s/\[\$\$firstname\]/$firstname/g;
						$content =~ s/\[\$\$filename\]/$filename/g;
						$content =~ s/\[\$\$filepath\]/$filepath/g;
						Kephra::Edit::insert_text($content);
					};
					$item{help} = $template->{description};
					push @menu_data, \%item; 
					eval_data($menu_id, \@menu_data);
				}
				return 1;
			}
		});

	} elsif ($menu_name eq '&file_history'){

		set_absolete($menu_id);
		set_update($menu_id, sub {
			my @menu_data = @{assemble_data_from_def
				( ['item file-session-history-open-all', undef] )};
			my $history = Kephra::File::History::get();
			if (ref $history eq 'ARRAY') {
				my $nr = 0;
				for ( @$history ) {
					my $file = $_->{file_path};
					push @menu_data, {
						type => 'item',
						label => ( File::Spec->splitpath( $file ) )[2],
						help => $file,
						call => eval 'sub {Kephra::File::History::open( '.$nr++.' )}',
					};
				}
			}
			eval_data($menu_id, \@menu_data);
			return Kephra::File::History::had_init() ? 1 : 0;
			1; # it was successful
		});

		Kephra::EventTable::add_call (
			'document.list', 'menu_'.$menu_id, sub {
				set_absolete( $menu_id ) if Kephra::File::History::update(); 
			}
		);
	} 
	elsif ($menu_name eq '&document_change') {

		set_update( $menu_id, sub {
			return unless exists $Kephra::temp{document}{buffer};
			my $filenames = Kephra::Document::Data::all_file_names();
			my $pathes = Kephra::Document::Data::all_file_pathes();
			my $untitled = Kephra::Config::Localisation::strings()->{app}{general}{untitled};
			my $space = ' ';
			my @menu_data;
			for my $nr (0 .. @$filenames-1){
				my $item = \%{$menu_data[$nr]};
				$space = '' if $nr == 9;
				$item->{type} = 'radioitem';
				$item->{label} = $filenames->[$nr] 
					? $space.($nr+1)." - $filenames->[$nr] \t - $pathes->[$nr]"
					: $space.($nr+1)." - <$untitled> \t -";
				$item->{call} = eval 'sub {Kephra::Document::Change::to_nr('.$nr.')}';
			}
		});

		#add_onopen_check( $menu_id, 'select', sub {
		#	my $menu = _ref($menu_id);
		#	$menu->FindItemByPosition
		#		( Kephra::Document::Data::current_nr() )->Check(1) if $menu;
		#});
		#Kephra::EventTable::add_call (
		#	'document.list', 'menu_'.$menu_id, sub { set_absolete($menu_id) }
		#);
	}
}


sub create_static  { # create solid, not on runtime changeable menus
	my ($menu_id, $menu_def) = @_;
	return unless ref $menu_def eq 'ARRAY';
	not_absolete($menu_id);
	eval_data($menu_id, assemble_data_from_def($menu_def));
}

sub create_menubar {
	#my $menubar    = Wx::MenuBar->new();
	#my $m18n = Kephra::Config::Localisation::strings()->{app}{menu};
	#my ($pos, $menu_name);
	#for my $menu_def ( @$menubar_def ){
		#for my $menu_id (keys %$menu_def){
			# removing the menu command if there is one
			#$pos = index $menu_id, ' ';
			#if ($pos > -1){
				#if ('menu' eq substr $menu_id, 0, $pos ){
					#$menu_name = substr ($menu_id, $pos+1);
				# ignoring menu structure when command other that menu or blank
				#} else { next }
			#} else { 
				#$menu_name = $menu_id;
			#}
			#$menubar->Append(
				#Kephra::Menu::create_static( $menu_name, $menu_def->{$menu_id}),
				#$m18n->{label}{$menu_name}
			#);
		#}
	#}
}

# create menu data structures (MDS) from menu skeleton definitions (command list)
sub assemble_data_from_def {
	my $menu_def = shift;
	return unless ref $menu_def eq 'ARRAY';

	my $menu_l18n = Kephra::Config::Localisation::strings()->{app}{menu};
	my ($cmd_name, $cmd_data, $type_name, $pos, $sub_id);
	my @mds = (); # menu data structure
	for my $item_def (@$menu_def){
		my %item;
		# creating separator
		if (not defined $item_def){
			$item{type} = ''
		# sorting commented lines out
		} elsif (substr($item_def, -1) eq '#'){
			next;
		# creating separator
		} elsif ($item_def eq '' or $item_def eq 'separator') {
			$item{type} = ''
		# eval a sublist
		} elsif (ref $item_def eq 'HASH'){
			$sub_id = $_ for keys %$item_def;
			$pos = index $sub_id, ' ';
			# make submenu if keyname is without command
			if ($pos == -1){
				$item{type} = 'menu';
				$item{id} = $sub_id;
				$item{label} = $menu_l18n->{label}{$sub_id};
				$item{help} = $menu_l18n->{help}{$sub_id} || '';
				$item{data} = assemble_data_from_def($item_def->{$sub_id}); 
			} else {
				my @id_parts = split / /, $sub_id;
				$item{type} = $id_parts[0];
				# make submenu when finding the menu command
				if ($item{type} eq 'menu'){
					$item{id}   = $id_parts[1];
					$item{label}= $menu_l18n->{label}{$id_parts[1]};
					$item{help} = $menu_l18n->{help}{$id_parts[1]} || '';
					$item{data} = assemble_data_from_def($item_def->{$sub_id}); 
					$item{icon} = $id_parts[2] if $id_parts[2];
				}
			}
		# menu items
		} else {
			$pos = index $item_def, ' ';
			next if $pos == -1;
			$item{type} = substr $item_def, 0, $pos;
			$cmd_name = substr $item_def, $pos+1;
			if ($item{type} eq 'menu'){
				$item{id} = $cmd_name;
				$item{label} = $menu_l18n->{label}{$cmd_name};
			} else {
				$cmd_data = Kephra::CommandList::get_cmd_properties( $cmd_name );
				# skipping when command call is missing
				next unless ref $cmd_data and exists $cmd_data->{call};
				for ('call','enable','state','label','help','icon'){
					$item{$_} = $cmd_data->{$_} if $cmd_data->{$_};
				}
				$item{label} .= "\t  " . $cmd_data->{key} . "`" if $cmd_data->{key};
			}
		}
		push @mds, \%item;
	}
	return \@mds;
}

sub eval_data { # eval menu data structures (MDS) to wxMenus
	my $menu_id = shift;
	return unless defined $menu_id;
	#emty the old or create new menu under the given ID
	my $menu = _ref($menu_id);
	if (defined $menu and $menu) { $menu->Delete( $_ ) for $menu->GetMenuItems } 
	else                         { $menu = Wx::Menu->new() }

	my $menu_data = shift;
	unless (ref $menu_data eq 'ARRAY') {
		_ref($menu_id, $menu); 
		return $menu;
	}

	my $win = Kephra::App::Window::_ref();
	my $kind;
	my $item_id = defined $menu{$menu_id}{item_id}
		? $menu{$menu_id}{item_id}
		: $Kephra::app{GUI}{masterID}++ * 100;
	$menu{$menu_id}{item_id} = $item_id;

	for my $item_data (@$menu_data){
		if (not $item_data->{type} or $item_data->{type} eq 'separator'){
			$menu->AppendSeparator;
		}
		elsif ($item_data->{type} eq 'menu'){
			my $submenu = ref $item_data->{data} eq 'ARRAY'
				? eval_data( $item_data->{id}, $item_data->{data} )
				: ready( $item_data->{id} );
			$item_data->{help} = '' unless defined $item_data->{help};
			my @params = ( $menu, $item_id++, $item_data->{label},$item_data->{help},
				&Wx::wxITEM_NORMAL
			);
			push @params, $submenu if is ($submenu);
			my $menu_item = Wx::MenuItem->new( @params );
			if (defined $item_data->{icon}) {
				my $bmp = Kephra::CommandList::get_cmd_property
					( $item_data->{icon}, 'icon' );
				$menu_item->SetBitmap( $bmp )
					if ref $bmp eq 'Wx::Bitmap' and not Wx::wxMAC();
			}
			#Wx::Event::EVT_MENU_HIGHLIGHT($win, $item_id-1, sub {
			#	Kephra::App::StatusBar::info_msg( $item_data->{help} )
			#});
			$menu->Append($menu_item);
		} 
		else { # create normal items
			if    ($item_data->{type} eq 'checkitem'){$kind = &Wx::wxITEM_CHECK}
			elsif ($item_data->{type} eq 'radioitem'){$kind = &Wx::wxITEM_RADIO}
			elsif ($item_data->{type} eq 'item')     {$kind = &Wx::wxITEM_NORMAL}
			else                                     { next; }

			my $menu_item = Wx::MenuItem->new
				($menu, $item_id, $item_data->{label}||'', '', $kind);
			if ($item_data->{type} eq 'item') {
				if (ref $item_data->{icon} eq 'Wx::Bitmap') {
					$menu_item->SetBitmap( $item_data->{icon} ) unless Wx::wxMAC();
				}
				else {
					# insert fake empty icons
					# $menu_item->SetBitmap($Kephra::temp{icon}{empty}) 
				}
			}

			add_onopen_check( $menu_id, 'enable_'.$item_id, sub {
				$menu_item->Enable( $item_data->{enable}() );
			} ) if ref $item_data->{enable} eq 'CODE';
			add_onopen_check( $menu_id, 'check_'.$item_id, sub {
				$menu_item->Check( $item_data->{state}() )
			} ) if ref $item_data->{state} eq 'CODE';

			Wx::Event::EVT_MENU          ($win, $menu_item, $item_data->{call} );
			Wx::Event::EVT_MENU_HIGHLIGHT($win, $menu_item, sub {
				Kephra::App::StatusBar::info_msg( $item_data->{help} )
			}) if $item_data->{help} ;
			$menu->Append( $menu_item );
			$item_id++; 
		}
	1; # sucess
	}

	Kephra::EventTable::add_call('menu.open', 'menu_'.$menu, sub {ready($menu_id)});
	_ref($menu_id, $menu);
	return $menu;
}

sub destroy {
	my $menu_ID = shift;
	my $menu = _ref( $menu_ID );
	return unless $menu;
	$menu->Destroy;
	Kephra::EventTable::del_own_subscriptions( $menu_ID );
}

1;