#!/usr/bin/perl

use strict;
use warnings;
use lib './tools/lib';
# use lib './lib';
use Syntax::Kamelon::XMLData;
use Syntax::Kamelon::Debugger;
use Wx;


my $help = <<__EOF;
This is a viewer/debugger for the Kamelon syntax highlight engine.

kamelon_debug_wx takes the following arguments:

-help
   Displays this text.

-i
-index
   Uses the index file in the xml folder if present.Otherwise it just 
   indexes the xml files everytime at startup. This takes a penalty.
-s
-sampledir
   Sets the samplefolder where the samplefiles for the viewer are stored
   They are recognized on the basis of their extension.

-e
-section
   Loads only the XML files specified in the menu section in the list.

-x
-xmldir
   Specifies the XML directory. By default it uses Kamelon's.
__EOF

my $noindex = 1;
my $sampledir = '.';
my $section = '';
my $xmldir;
my %shortargs = (
	'-e' => sub { 
		$section = shift @ARGV;
		print "section: $section\n";
	},
	'-i' => sub { 
		$noindex = 0;
		print "Using index\n";
	},
	'-s' => sub  {
		$sampledir = shift @ARGV;
		unless (-e $sampledir) { die "$sampledir does not exist" }
		unless (-d $sampledir) { die "$sampledir is not a directory" }
		print "sampledir: $sampledir\n"
	},
	'-x' => sub  {
		$xmldir = shift @ARGV;
		unless (-e $xmldir) { die "$xmldir does not exist" }
		unless (-d $xmldir) { die "$xmldir is not a directory" }
		print "xmldir: $xmldir\n"
	},
);

my %args = (%shortargs,
	'-index' => $shortargs{'-i'},
	'-section' => $shortargs{'-e'},
	'-sampledir' => $shortargs{'-s'},
	'-xmldir' => $shortargs{'-x'},
	'-help' => sub { print "$help\n"; exit }
);

while (@ARGV) {
	my $t = shift @ARGV;
	if (exists $args{$t}) {
		my $call = $args{$t};
		&$call;
	} else {
		my @op = sort keys %args;
		my $opstr = "";
		for (@op) {
			$opstr = "$opstr $_"
		}
		die "Invalid option $t. Available options:$opstr\n";
	}
}

###########################################################################################

my $cursample;
my $frame;
my $firstsel = 1;

my $highlighter = Syntax::Kamelon::Debugger->new(
	verbose => 1,
	noindex => $noindex,
	xmlfolder => $xmldir,
);


$xmldir = $highlighter->GetIndexer->XMLFolder;

my $curentry = '';
#my $curtest;
my $spaces = "                                                                 ";

###########################################################################################

package ListPanel;

use Wx qw(:sizer :panel :window :id :listbox :dialog :filedialog wxDefaultPosition);
use base qw( Wx::Panel );
use Wx::Event qw( EVT_BUTTON EVT_LISTBOX );
use File::Copy;
use File::Basename;

sub new {
	my $class = shift;
	my $self = $class->SUPER::new(@_);

	my $buttonbar = Wx::Panel->new($self, -1);
	my $bsiz = Wx::BoxSizer->new(wxHORIZONTAL);
	$buttonbar->SetSizer($bsiz);
	
	my $id = Wx::NewId;
	my $list = Wx::ListBox->new($self, $id, [-1,-1],[200,250], [], wxLB_NEEDED_SB | wxLB_HSCROLL | wxLB_SINGLE);
	$self->{List} = $list;
	
	EVT_LISTBOX($self, $list, \&listOpen);

	my $sizer = Wx::BoxSizer->new(wxVERTICAL);
	$sizer->Add($buttonbar, 0, wxEXPAND | wxALL, 2);
	$sizer->Add($list, 1, wxEXPAND | wxALL, 2);
	$self->SetSizer($sizer);

	$self->{ListCallback} = sub { print "opening " . shift . "\n" };
	my @l = $highlighter->AvailableSyntaxes;
	my @li = ();
	for (@l) {
		if ($section ne '') {
			if ($highlighter->GetIndexer->InfoSection($_) eq $section) {
				push @li, $_
			}
		} else {
			push @li, $_
		}
	}
	$list->Set(\@li);

	return $self;
}

sub lastSel {
	my $self = shift;
	if (@_) { $self->{LastSel} = shift; }
	return $self->{LastSel}
}

sub listCallback {
	my $self = shift;
	if (@_) { $self->{ListCallback} = shift; }
	return $self->{ListCallback}
}

sub listOpen {
	my ($self, $event) = @_;
	my $lang = $event->GetString;
	my $sub = $self->listCallback;
	if (&$sub($lang)) {
		$self->lastSel($lang)
	} 
}

sub listRemove {
}

###########################################################################################

package PagePanel;

use Wx qw(:sizer :panel :window :id :textctrl :dialog );
use base qw( Wx::Panel );
use Wx::Event qw( EVT_BUTTON );

sub new {
	my $class = shift;
	my $self = $class->SUPER::new(@_);
	
	my $buttonbar = Wx::Panel->new($self, -1);
	my $bsiz = Wx::BoxSizer->new(wxHORIZONTAL);
	$buttonbar->SetSizer($bsiz);
	$self->{BAR} = $buttonbar;
	$buttonbar->Hide;

	my $sizer = Wx::BoxSizer->new(wxVERTICAL);
	$sizer->Add($buttonbar, 0, wxEXPAND|wxALL, 2);
	$self->SetSizer($sizer);
	
	return $self;
}

sub addButton {
	my ($self, $label, $call) = @_;
	my $bb = $self->{BAR};
	my $b = Wx::Button->new($bb, -1, $label);
	$bb->GetSizer->Add($b, 0, wxALL, 2);
	EVT_BUTTON($self, $b, $call);
	$bb->Show;
}

sub Bar {
	my $self = shift;
	return $self->{BAR};
}

sub Base {
	my $self = shift;
	return $self->GetParent->GetParent
}

sub entryCanClose {
	return 1
}

sub entryClose {
	return 1
}

sub entryOpen {
	return 1
}

sub NotImpl {
	my $self = shift;
	Wx::MessageBox("Not implemented.", "Sorry!", wxOK|wxCENTRE, $self);
}

###########################################################################################

package Debugger;

use Wx qw(:sizer :panel :window :id :textctrl :dialog :filedialog);
use Syntax::Kamelon::Wx::LogTextCtrl;
use base qw( PagePanel );
use Wx::Event qw( EVT_BUTTON EVT_TIMER );

sub new {
	my $class = shift;
	my $self = $class->SUPER::new(@_);
	
	my $sizer = Wx::BoxSizer->new(wxVERTICAL);

	my $b = Wx::Button->new($self, -1, 'Debug');
	$sizer->Add($b, 0, wxALL|wxEXPAND, 2);
	EVT_BUTTON($self, $b, \&Debug);
	my $dblog = Syntax::Kamelon::Wx::LogTextCtrl->new($self, -1, "", [-1, -1], [20,20]);
	$self->{DBLOG} = $dblog;
	$sizer->Add($dblog, 1, wxALL|wxEXPAND, 2);
	$self->SetSizer($sizer);
	my $id = Wx::NewId;
	$self->{TIMER} = Wx::Timer->new($self, $id);
   EVT_TIMER($self, $id, \&DebugFinish);
	
	return $self;
}

sub Debug {
	my $self = shift;
	my $log = $self->{DBLOG};
	$log->Clear;
	my $txt = $self->{TXT};
	my ($pos, $line) = $txt->PositionToXY($txt->GetInsertionPoint);
	$line++;
	$log->WriteStyle("Debugging\n", 'title');
	$log->WriteHash({
		Line => $line,
		Column => $pos,
	});
	$log->WriteStyle("\n");
	$log->WriteStyle("Please wait ...", 'title');
	$highlighter->Reset;
	$highlighter->SetWatch($line, $pos);
	$highlighter->SetTasks('watch');
	$txt->FindPlugin('Highlighter')->Purge(0);
	$highlighter->Reset;
	$self->{TIMER}->Start(200);
}

sub DebugFinish {
	my $self = shift;
	my ($result, $res) = $highlighter->WatchResult;
	unless ($result eq '') {
		my $inf = shift @$res;
		my $log = $self->{DBLOG};
		$self->{TIMER}->Stop;
		$highlighter->SetTasks();
		$self->DeleteLastLine;
		$log->WriteHash({
			Result => $result,
			Path => $inf->{path},
		});
		$log->WriteStyle("\n");
		$log->WriteStyle("Details\n", 'title');
		$log->WriteStyle("\n");
		$log->WriteHash($inf, qw/ path /);
		$log->WriteStyle("\n");
		my $parsed = shift @$res;
		if (defined $parsed) {
			$log->WriteHash({ 
				Parsed => "'$parsed'",
			});
			$log->WriteStyle("\n");
		}
		my $stack = shift @$res;
		if (defined $stack) {
			$log->WriteTable($stack, [18, 18, 6], qw/Syntax Context Captures / );
			$log->WriteStyle("\n");
		}
	}
}

sub DeleteLastLine {
	my $self = shift;
	my $log = $self->{DBLOG};
	my $logend = $log->GetLastPosition;
	my ($x, $y) = $log->PositionToXY($logend);
	my $lastline = $log->XYToPosition(0, $y);
	$log->Remove($lastline, $logend);
}

sub SetTextCtrl {
	my ($self, $t) = @_;
	$self->{TXT} = $t;
}

###########################################################################################

package SamplePage;

use Wx qw(:sizer :panel :window :id :textctrl :dialog :filedialog);
use base qw( PagePanel );
use Wx::Event qw( EVT_BUTTON );
use File::Basename;
use File::Copy;
use Syntax::Kamelon::Wx::PluggableTextCtrl;

sub new {
	my $class = shift;
	my $self = $class->SUPER::new(@_);
	
	$self->addButton('&Save', \&Save);
	$self->addButton('&Load', \&SetSampleFile);
	
	my $parent = $self;
	my $widget;
	my $split;
	my $size = [350, 270];
	my $tsiz = $self->GetSizer;
	$split = Wx::SplitterWindow->new($self, wxID_ANY, [-1, -1], $size);
	$size = [20, 20];
	$parent = Wx::Panel->new($split, -1);
	$tsiz = Wx::BoxSizer->new(wxVERTICAL);
	$parent->SetSizer($tsiz);
	my $txtc = Syntax::Kamelon::Wx::PluggableTextCtrl->new($parent, -1, "", [-1, -1], $size, wxHSCROLL|wxTE_MULTILINE|wxTE_PROCESS_TAB|wxTE_RICH2);
	$self->{TXTC} = $txtc;
	$txtc->LoadPlugin('UndoRedo');
	print ref $highlighter, "\n";
	$txtc->LoadPlugin('Highlighter', $highlighter);
	print ref $highlighter, "\n";
	$tsiz->Add($txtc, 1, wxEXPAND|wxALL, 2);
	my $debugger = Debugger->new($split, wxID_ANY, [-1, -1], [20, 20]);
	$self->{DEBUGGER} = $debugger;
	$debugger->SetTextCtrl($txtc);
	$split->SplitVertically($parent, $debugger);
	$split->SetSashPosition(270);
	$split->SetSashGravity(0.8);
	$widget = $split;
	$self->GetSizer->Add($widget, 1, wxEXPAND|wxALL|wxFIXED_MINSIZE, 2);
	
	return $self;
}

sub entryClose {
	my $self = shift;
	if ($curentry eq '') { return 1 }
	my $t = $self->{TXTC};
	my $sf = "$sampledir/sample.$curentry";
	if (($sf ne '') and ($t->IsModified)) {
		my $answer = Wx::MessageBox("Sample file for $curentry altered. Save changes?", "Save modifications?", wxYES_NO|wxCANCEL|wxCENTRE, $self);
		if ($answer eq wxCANCEL) {
			return 0
		}
		if ($answer eq wxYES) {
 			$t->SaveFile($sf);
		}
	}
	my $db = $self->{DEBUGGER};
	if (defined $db) {
		$self->{DEBUGGER}->{DBLOG}->Clear;
	}
	$t->Clear;
	return 1;
}

my $debughelp = <<__EOF;
Position the cursor
just before or inside
the item you want 
debugged. 

then click 'Debug'.

The result will show
you if a match occured
or not and give you
the pathway and the
details of the matching
rule.

__EOF

sub entryOpen {
	my ($self, $item) = @_;
	my $t = $self->{TXTC};
	my $ext = $highlighter->GetIndexer->InfoExtensions($item);
	my @e = split /;/, $ext;
	for (@e) {
		my $f = $_;
		$f =~ s/^\*//;
		my $sf = "$sampledir/highlight$f";
		if (-e $sf) {
			$t->LoadFile($sf);
			$t->Syntax($item);
			my $db = $self->{DEBUGGER};
			if (defined $db) {
				$db->{DBLOG}->WriteStyle($debughelp, 'message');
			}
			return 1
		}
	}
	$t->WriteText("No sample file\n");
	return 1;
}

sub Save {
	my $self = shift;
	if ($curentry eq '') { return }
	my $t = $self->{TXTC};
	my $sf = "$sampledir/sample.$curentry";
	$self->{TXTC}->SaveFile($sf);
}

sub SetSampleFile {
	my $self = shift;
	my $samplefile = '';
	my $filedlg = Wx::FileDialog->new($self, 'Set sample file', '', '', "Any file (*)|*", wxOPEN);
	if ($filedlg->ShowModal==wxID_OK) {
		$samplefile = $filedlg->GetPath;
	}
	if ($samplefile ne '') {
		my $t = $self->{TXTC};
		$t->LoadFile($samplefile);
# 		$t->SaveFile;
	}
}

###########################################################################################

package BrowsePage;

use Wx qw(:sizer :panel :window :id :textctrl :dialog :combobox);
use Syntax::Kamelon::Wx::LogTextCtrl;
use base qw( PagePanel );
use Wx::Event qw( EVT_BUTTON EVT_COMBOBOX );

sub new {
	my $class = shift;
	my $self = $class->SUPER::new(@_);
	my $bar = $self->Bar;
	my $bsiz = $bar->GetSizer;
	
	my @boxes = (
		'Context', 'CCOMBO', \&OnSelectContext,
		'Rule', 'RCOMBO', \&OnSelectRule,
	);
	$bar->Show;
	while (@boxes) {
		my $label = shift @boxes;
		my $varname = shift @boxes;
		my $callback = shift @boxes;
		
		#create container panel
		my $pan = Wx::Panel->new($bar, -1);
		my $psiz = Wx::BoxSizer->new(wxHORIZONTAL);
		$pan->SetSizer($psiz);
		
		#create label and combobox
		my $clab = Wx::StaticText->new($pan, -1, $label);
		$psiz->Add($clab, 0, wxALL|wxALIGN_CENTRE_VERTICAL, 2);
		my $id = Wx::NewId;
		my $cb = Wx::ComboBox->new($pan, $id, "", [-1, -1], [150, 25], [], wxCB_READONLY);
		$self->{$varname} = $cb;
		EVT_COMBOBOX($self, $id, $callback);
		$psiz->Add($cb, 1, wxALL, 2);

		#add the panel
		$bsiz->Add($pan, 1, wxALL, 2);

	}
	$bar->Layout;
	my $split = Wx::SplitterWindow->new($self, wxID_ANY);
	my $ctxt = Syntax::Kamelon::Wx::LogTextCtrl->new($split, -1, "", [-1, -1], [20,80]);
	$self->{CTXT} = $ctxt;
	my $rtxt = Syntax::Kamelon::Wx::LogTextCtrl->new($split, -1, "", [-1, -1], [20,20]);
	$self->{RTXT} = $rtxt;
	$split->SplitHorizontally($ctxt, $rtxt);
	$split->SetSashGravity(0.3);
	$self->GetSizer->Add($split, 1, wxALL|wxEXPAND, 2);
	return $self;
}

sub entryClose {
	my $self = shift;
	for ($self->{CCOMBO}, $self->{RCOMBO}) {
		$_->Clear;
		$_->SetValue("");
	}
	for ($self->{CTXT}, $self->{RTXT}) {
		$_->Clear;
	}
	return 1;
}

sub entryOpen {
	my ($self, $item) = @_;
	my $hl = $highlighter->GetLexer($item);
	$self->{HL} = $hl;
	my $cb = $self->{CCOMBO};
	my $ctx = $hl->{contexts};
	my @contexts = sort keys %$ctx;
	for (@contexts) { $cb->Append($_) }
	return 1;
}

sub OnSelectContext {
	my $self = shift;
	my $context = $self->{CCOMBO}->GetValue;
	my $ct = $self->{HL}->{contexts}->{$context};
	my $db = $ct->{debug};
	my %rules = ();
	my $rb = $self->{RCOMBO};
	$rb->Clear;
	for (@$db) {
		my $path = $_->{path};
		$rb->Append($path);
		$rules{$path} = $_;
	}
	$self->{RULES} = \%rules;
	$self->{RTXT}->Clear;
	my $txt = $self->{CTXT};
	$txt->Clear;
	$txt->WriteStyle("Context: $context\n", 'title');
	$txt->WriteStyle("\n");
	$txt->WriteHash($ct->{info}, qw/ items /);
	
}

sub OnSelectRule {
	my $self = shift;
	my $txt = $self->{RTXT};
	$txt->Clear;
	my $rule = $self->{RCOMBO}->GetValue;
	my $rules = $self->{RULES};
	$txt->WriteStyle("Rule: $rule\n", 'title');
	$txt->WriteStyle("\n");
	$txt->WriteHash($rules->{$rule}, qw / path /);
}

###########################################################################################

package LogPage;

use Wx qw(:sizer :panel :window :id :textctrl );
use Syntax::Kamelon::Wx::LogTextCtrl;
use base qw( PagePanel );
use Wx::Event qw( EVT_BUTTON );

sub new {
	my $class = shift;
	my $self = $class->SUPER::new(@_);
	my $bar = $self->Bar;
	my $bsiz = $bar->GetSizer;
	
	my $txt = Syntax::Kamelon::Wx::LogTextCtrl->new($self, -1, "", [-1, -1], [20,20]);
	$self->{TXT} = $txt;
	$self->GetSizer->Add($txt, 1, wxALL|wxEXPAND, 2);
	$highlighter->LogCallSet(sub {
		my $m = shift;
		$txt->WriteStyle("$m\n", 'message');
	});
	$self->{CUR} = "";
	$self->{LOGS} = {};
	$bar->Layout;
	return $self;
}

sub entryClose {
	my $self = shift;
	my $txt = $self->{TXT};
	my $cur = $self->{CUR};
	my $log = $txt->GetRange(0, $txt->GetLastPosition);
	$self->{LOGS}->{$cur} = $log;
	$self->{CUR} = "";
	$txt->Clear;
	return 1;
}

sub entryOpen {
	my ($self, $item) = @_;
	$self->{TXT}->WriteStyle($self->{LOGS}->{$item}, 'message');
	$self->{CUR} = $item;
	return 1;
}

###########################################################################################

package ContentPanel;

use Wx qw(:sizer :panel :window :id :textctrl :dialog :listbox);
use base qw( PagePanel );
use Wx::Event qw( EVT_BUTTON EVT_LISTBOX );

my $not = "Nothing open";

sub new {
	my $class = shift;
	my $self = $class->SUPER::new(@_);
	
	my $label = Wx::StaticText->new($self, -1, $not, [-1, -1], [-1, -1], wxALIGN_LEFT);
	$self->{LABEL} = $label;
	my $nb = Wx::Notebook->new($self, -1); 
	$self->{NB} = $nb;
	
	my %pages = ();
	
	my @defpages = (
		Sample => 'SamplePage',
		Browse => 'BrowsePage',
		Log => 'LogPage',
	);
	
	while (@defpages) {
		my $lab = shift @defpages;
		my $mod = shift @defpages;
		my $pag = $mod->new($nb, -1);
		$nb->AddPage($pag, $lab);
		$pages{$lab} = $pag;
	}

	
	$self->{PAGES} = \%pages;
	
	
	my $sizer = Wx::BoxSizer->new(wxVERTICAL);
	$sizer->Add($label, 0, wxEXPAND|wxALL, 1);
	$sizer->Add($nb, 1, wxEXPAND|wxALL, 1);
	$self->SetSizer($sizer);
	
	return $self;
}

sub entryClose {
	my $self = shift;
	my $p = $self->{PAGES};
	my $success = 1;
	foreach (keys %$p) {
		unless ($p->{$_}->entryCanClose) { $success = 0 }
	}
	if ($success) {
		foreach (keys %$p) {
			$p->{$_}->entryClose
		}
	}
	return $success;
}

sub entryOpen {
	my ($self, $item) = @_;
	if ($self->entryClose) {
		my $p = $self->{PAGES};
		my $success = 1;
		foreach (keys %$p) {
			unless ($p->{$_}->entryOpen($item)) { $success = 0 }
		}
		if ($success) {
			$curentry = $item;
			$self->{LABEL}->SetLabel($item);
			return 1;
		}
	}
	return 0;
}

sub Render {
	my ($self, $page) = @_;
	my $pgs = $self->{PAGES};
	if (my $p = $pgs->{$page}) {
		if ($p->entryClose) {
			return $p->entryOpen($curentry);
		}
	}
	return 0;
}

###########################################################################################

package ConsoleFrame;

use strict;
use warnings;
use Carp;

use Wx qw( :frame :textctrl :sizer :panel :window :id);
use base qw( Wx::Frame );

sub new {
	my $class = shift;
	my $self = $class->SUPER::new(@_);
	
	#Create widgets and controls
	my $splitter = Wx::SplitterWindow->new($self, wxID_ANY);
	my $listpane = ListPanel->new($splitter, wxID_ANY);
	my $contentpane = ContentPanel->new($splitter, wxID_ANY);
	$splitter->SplitVertically($listpane, $contentpane);
	$splitter->SetSashPosition(200);
	
	$self->{CONTENT} = $contentpane;
	
	$listpane->listCallback(sub {
		if ($firstsel) {
			$firstsel = 0;
			return 0;
		}
		my $item = shift;
		if ($contentpane->entryOpen($item)) {
			return 1
		}
		return 0;
	});
	
	#Do layout
	my $topsizer = Wx::BoxSizer->new(wxHORIZONTAL);
	$topsizer->Add($splitter, 1, wxEXPAND);
	$self->SetSizer($topsizer);
	$topsizer->Fit($self);
	$self->Layout();

	return $self;
}

sub entryClose {
	my $self = shift;
	return $self->{CONTENT}->entryClose;
}

###########################################################################################

package ConsoleApp;

use base qw(Wx::App);   # Inherit from Wx::App

sub OnInit {
	my $self = shift;
	$frame = ConsoleFrame->new( 
		undef,         # Parent window
		-1,            # Window id
		'Kamelon Debugger Console',  # Title
		[1200, 800],
	);
	$self->SetTopWindow($frame);    
	$frame->Show(1);                
}

sub OnExit {
 	my $self = shift;
}

package main;

my $wxobj = ConsoleApp->new(); 
$wxobj->MainLoop;
