package Kephra::Edit;
our $VERSION = '0.42';

use strict;
use warnings;
#
# internal helper function
#
sub _ep_ref { Kephra::App::EditPanel::_ref() }
sub _keep_focus{ Wx::Window::SetFocus( _ep_ref() ) }
sub _let_caret_visible {
	my $ep = _ep_ref();
	my ($selstart, $selend) = $ep->GetSelection;
	my $los = $ep->LinesOnScreen;
	if ( $selstart == $selend ) {
		$ep->ScrollToLine($ep->GetCurrentLine - ( $los / 2 ))
			unless $ep->GetLineVisible( $ep->GetCurrentLine() );
	} else {
		my $startline = $ep->LineFromPosition($selstart);
		my $endline = $ep->LineFromPosition($selend);
		$ep->ScrollToLine( $startline - (($los - $endline - $startline) / 2) )
			unless $ep->GetLineVisible($startline)
			and $ep->GetLineVisible($endline);
	}
	$ep->EnsureCaretVisible;
}

sub _center_caret {
	my $ep = _ep_ref();
	my $line = $ep->GetCurrentLine();
	$ep->ScrollToLine( $line - ( $ep->LinesOnScreen / 2 ));
	$ep->EnsureVisible($line);
	$ep->EnsureCaretVisible();
}

my @pos_stack;
sub _save_positions {
	my $ep = _ep_ref();
	my %pos;
	$pos{document}  = Kephra::Document::Data::current_nr();
	$pos{pos}       = $ep->GetCurrentPos;
	$pos{line}      = $ep->GetCurrentLine;
	$pos{col}       = $ep->GetColumn( $pos{pos} );
	$pos{sel_begin} = $ep->GetSelectionStart;
	$pos{sel_end}   = $ep->GetSelectionEnd;
	push @pos_stack, \%pos;
}

sub _restore_positions {
	my $ep = _ep_ref();
	my %pos = %{ pop @pos_stack };
	if (%pos) {
		Kephra::Document::Change::to_number( $pos{document} )
			if $pos{document} != Kephra::Document::Data::current_nr();
		$ep->SetCurrentPos( $pos{pos} );
		$ep->GotoLine( $pos{line} ) if $ep->GetCurrentLine != $pos{line};
		if ( $ep->GetColumn( $ep->GetCurrentPos ) == $pos{col} ) {
			$ep->SetSelection( $pos{sel_begin}, $pos{sel_end} );
		} else {
			my $npos = $ep->PositionFromLine( $pos{line} ) + $pos{col};
			my $max = $ep->GetLineEndPosition( $pos{line} );
			$npos = $max if $npos > $max;
			$ep->SetCurrentPos($npos);
			$ep->SetSelection( $npos, $npos );
		}
	}
	&_let_caret_visible;
}

sub _select_all_if_none {
    my $ep = _ep_ref();
    my ($start, $end) = $ep->GetSelection;
    if ( $start == $end ) {
        $ep->SelectAll;
        ($start, $end) = $ep->GetSelection;
    }
    return $ep->GetTextRange( $start, $end );
}

sub _selection_left_to_right {
	my $ep = shift || _ep_ref();
	my ($start, $end) = $ep->GetSelection;
	my $pos = $ep->GetCurrentPos;
	return -1 if $start == $end;
	return $start == $pos ? 0 : 1;
}
sub _nearest_grid_pos { # position in document from given line and column
	my $line = shift;
	my $col = shift;
	my $ep = shift || _ep_ref();
	return unless defined $col;

	# first staight foreward attempt
	my $lpos = $ep->PositionFromLine($line);
	my $pos = $lpos + $col;
	return $pos if $ep->GetColumn($pos) == $col
	            and $ep->LineFromPosition($pos) == $line;

	# if line too short take last pos of line 
	my $endpos = $ep->GetLineEndPosition($line);
	return $endpos if $ep->GetColumn($endpos) < $col;

	# if tabs used calculate
	my $ipos = $ep->GetLineIndentation($line);
	my $icol = $ep->GetColumn($ipos);
	return $ipos + $col - $icol if $icol <= $col;

	# if between indenting tabs take neares
	my $tabsize = $ep->GetTabWidth();
	my $tabs = $col / $tabsize; 
	return ($col % $tabsize < $tabsize / 2) ? $lpos + $tabs : $lpos + $tabs + 1;
}
sub can_paste   { _ep_ref()->CanPaste }
sub can_copy    { Kephra::Document::Data::attr('text_selected') }
#
# simple textedit
#
sub cut         { _ep_ref()->Cut }
sub copy        {
	my $ep = _ep_ref();
	$ep->Copy;
	$ep->SelectionIsRectangle()
		? Kephra::Document::Data::set_value('copied_rect_selection',get_clipboard_text())
		: Kephra::Document::Data::set_value('copied_rect_selection','');
}
sub paste       {
	my $lch = Kephra::Document::Data::get_value('copied_rect_selection');
	my $cb = get_clipboard_text();
	(defined $lch and $lch eq $cb) ? paste_rectangular($cb) :  _ep_ref()->Paste;
}
sub paste_rectangular {
	my $text = shift || get_clipboard_text();
	my $ep = shift || _ep_ref();
	my $dragpos = shift;
	my $droppos = shift;
	# all additional parameters have to be provided or no one
	return -1 if defined $dragpos and not defined $droppos;

	my @lines = split( /[\r\n]+/, $text);
	$droppos = $ep->GetCurrentPos unless defined $dragpos;
	my $linenr = $ep->LineFromPosition( $droppos );
	my $colnr = $ep->GetColumn($droppos );

	if (defined $dragpos){
		# calculate real drop position if dragged foreward
		# because selection is cut out before inserted and this changed droppos
		if ($dragpos <= $droppos){
			my $selwidth = length $lines[0];
			my $dnddelta = $linenr - $ep->LineFromPosition( $dragpos );
			my $max = scalar @lines;

			#$dnddelta = $max < $dnddelta ? $max : $dnddelta;
			#$dnddelta *= $selwidth;
			#$droppos -= $dnddelta;
			#print "$dragpos ---$droppos\n";
		}
	}

	$ep->BeginUndoAction;
	$ep->ReplaceSelection(''),$ep->SetCurrentPos($droppos) if defined $dragpos;

	my $insertpos;
	for my $line (@lines){
		$insertpos = $ep->PositionFromLine($linenr) + $colnr;
		$insertpos += $colnr - $ep->GetColumn( $insertpos ) ;
		$insertpos = $ep->GetLineEndPosition($linenr)
			if $ep->LineFromPosition( $insertpos ) > $linenr;
		$ep->InsertText( $insertpos, $line);
		$linenr++;
	}
	$ep->EndUndoAction;
}
sub replace     {
	my $ep = _ep_ref();
	my $text = get_clipboard_text();
	copy();
	_ep_ref()->ReplaceSelection($text);
}

sub clear       { _ep_ref()->Clear }
sub get_clipboard_text {
	my $cboard = &Wx::wxTheClipboard;
	my $text;
	$cboard->Open;
	if ( $cboard->IsSupported( &Wx::wxDF_TEXT ) ) {
		my $data = Wx::TextDataObject->new;
		my $ok = $cboard->GetData( $data );
		if ( $ok ) {
			$text = $data->GetText;
		} else {
			# todo: error handling
		}
	}
	$cboard->Close;
	return defined $text ? $text : -1;
}

sub del_back_tab{
	my $ep = _ep_ref();
	my $pos = $ep->GetCurrentPos();
	my $tab_size = Kephra::Document::Data::attr('tab_size');
	my $deltaspace = $ep->GetColumn($pos--) % $tab_size;
	$deltaspace = $tab_size unless $deltaspace;
	do { $ep->CmdKeyExecute(&Wx::wxSTC_CMD_DELETEBACK) }
	while $ep->GetCharAt(--$pos) == 32 and --$deltaspace;
}

#
# Edit Selection
#
sub get_selection  { _ep_ref()->GetSelectedText() }
sub move_target {
	my $linedelta = shift;
	return unless defined $linedelta;
	my $ep = shift || _ep_ref(); 
	my $targetstart = $ep->GetTargetStart();
	my $targettext = $ep->GetTextRange($targetstart, $ep->GetTargetEnd());
	$ep->BeginUndoAction;
	$ep->ReplaceTarget('');
	$ep->InsertText($targetstart+$linedelta, $targettext);
	$ep->EndUndoAction;
}
sub move_selection {
	my $linedelta = shift;
	return unless defined $linedelta;
	my $ep = shift || _ep_ref(); 
	my ($selbegin, $selend) = $ep->GetSelection();
	my $targettext = $ep->GetSelectedText();
	$ep->BeginUndoAction;
	$ep->ReplaceSelection('');
	my $pos = $ep->GetCurrentPos;
	$pos += $linedelta;
	$ep->InsertText($pos, $targettext);
	$ep->SetSelection($pos, $pos + $selend - $selbegin);
	$ep->EndUndoAction;
}
sub move_lines {
	my $linedelta = shift;
	return unless defined $linedelta;
	my $ep = shift || _ep_ref();

	my ( $selbegin, $selend) = $ep->GetSelection();
	my $sellength = $selend - $selbegin;
	my $selstartline = $ep->LineFromPosition($selbegin);
	my $targetstart = $ep->GetTargetStart();
	my $targetend = $ep->GetTargetEnd();
	my $blockbegin = $ep->PositionFromLine($selstartline);
	my $blockend = $ep->PositionFromLine( $ep->LineFromPosition($selend)+1 );
	my $selcolumn = $selbegin - $blockbegin;

	# endmode is taken when last line on start or end of operation has no EOL
	# then i take the the EOL char from the line before instead and have to
	# insert in a pos before to keep consistent
	my $endmode;
	if ($blockend == $ep->GetLength()
	or  $ep->LineFromPosition($selend) + $linedelta >= $ep->GetLineCount()-1 ) {
		$blockbegin = $ep->GetLineEndPosition($selstartline-1);
		$blockend = $ep->GetLineEndPosition( $ep->LineFromPosition($selend) );
		$endmode = 1;
	}
	$selstartline += $linedelta;
	my $blocktext = $ep->GetTextRange($blockbegin, $blockend);
	$ep->BeginUndoAction;
	$ep->SetTargetStart( $blockbegin );
	$ep->SetTargetEnd( $blockend );
	$ep->ReplaceTarget('');
	$selstartline = 0 if $selstartline < 0;
	$selstartline = $ep->GetLineCount() if $selstartline > $ep->GetLineCount();
	my $target = $endmode
		? $ep->GetLineEndPosition($selstartline-1)
		: $ep->PositionFromLine($selstartline);
	$ep->InsertText($target, $blocktext);
	$selbegin = $ep->PositionFromLine($selstartline) + $selcolumn;
	$ep->SetSelection($selbegin, $selbegin + $sellength);
	$ep->SetTargetStart($targetstart );
	$ep->SetTargetEnd( $targetend );
	$ep->EndUndoAction;
}

sub selection_move_left {
	my $ep = shift || _ep_ref();
	my ($selbegin, $selend) = $ep->GetSelection();
	if ( $selbegin == $selend
	or $ep->LineFromPosition( $selbegin ) != $ep->LineFromPosition( $selend ) ) {
		Kephra::Edit::Format::dedent_tab();
	} 
	else {
		my $newpos = $ep->WordStartPosition($selbegin, 1);
		my $move_delta = $newpos == $selbegin ? -1 : $newpos - $selbegin;
		move_selection( $move_delta );
	}
}

sub selection_move_right{
	my $ep = _ep_ref();
	my ($selbegin, $selend) = $ep->GetSelection();
	my $endline = $ep->LineFromPosition( $selend );
	if ( $selbegin == $selend or $ep->LineFromPosition( $selbegin ) != $endline ) {
		Kephra::Edit::Format::indent_tab();
	} 
	else { 
		my $newpos = $ep->WordEndPosition($selend, 1);
		my $move_delta = $newpos == $selend ? 1 : $newpos - $selend;
		move_selection( $move_delta ) 
			unless $endline == $ep->GetLineCount() - 1
			and $ep->GetLineEndPosition($endline) == $selend;
	}
}

sub selection_move_up   {
	my $ep = shift || _ep_ref();
	my ($selbegin, $selend) = $ep->GetSelection();
	my $firstline = $ep->LineFromPosition( $selbegin );
	my $lastline = $ep->LineFromPosition( $selend );

	if ( $selbegin != $selend and $firstline == $lastline) {
		my $line = $firstline;
		my $col  = $ep->GetColumn( $selbegin );
		return unless $line;
		$line--;
		move_selection( _nearest_grid_pos($line, $col) - $selbegin );
	}
	else { move_lines( -1, $ep ) }
}

sub selection_move_down {
	my $ep = shift || _ep_ref();
	my ($selbegin, $selend) = $ep->GetSelection();
	my $firstline = $ep->LineFromPosition( $selbegin );
	my $lastline = $ep->LineFromPosition( $selend );

	if ($selbegin != $selend and $firstline == $lastline) {
		my $line = $firstline;
		my $col  = $ep->GetColumn( $selbegin );
		return if $line+1 == $ep->GetLineCount();
		$line++;
		move_selection( _nearest_grid_pos($line, $col) - $selend );
	}
	else { move_lines( 1, $ep ) }
}

sub selection_move_page_up   {
	my $ep = shift || _ep_ref();
	my ($selbegin, $selend) = $ep->GetSelection();
	my $firstline = $ep->LineFromPosition( $selbegin );
	my $lastline = $ep->LineFromPosition( $selend );
	my $linedelta = $ep->LinesOnScreen;

	if ($selbegin != $selend and $firstline == $lastline) {
		my $line = $firstline;
		my $col  = $ep->GetColumn( $selbegin );
		return unless $line;
		$line -= $linedelta;
		$line = 0 if $line < 0;
		move_selection( _nearest_grid_pos($line, $col) - $selbegin );
	} 
	else { move_lines( -$linedelta, $ep ) }
}

sub selection_move_page_down {
	my $ep = shift || _ep_ref();
	my ($selbegin, $selend) = $ep->GetSelection();
	my $firstline = $ep->LineFromPosition( $selbegin );
	my $lastline = $ep->LineFromPosition( $selend );
	my $linedelta = $ep->LinesOnScreen;

	if ($selbegin != $selend and $firstline == $lastline) {
		my $line = $firstline;
		my $col  = $ep->GetColumn( $selbegin );
		return if $line+1 == $ep->GetLineCount();
		$line += $linedelta;
		$line = $ep->GetLineCount()-1 if $line >= $ep->GetLineCount();
		move_selection( _nearest_grid_pos($line, $col) - $selend );
	} 
	else { move_lines( $linedelta, $ep ) }
}

#
sub insert {
	my ($text, $pos) = @_;
	return unless $text;
	my $ep = _ep_ref();
	$pos = $ep->GetCurrentPos unless defined $pos;
	$ep->InsertText($pos, $text);
	$pos += length $text;
	$ep->SetSelection($pos, $pos);
}
sub insert_text   { insert(@_) }
sub insert_at_pos { insert(@_) }
#
# Edit Line
#
sub cut_current_line { _ep_ref()->CmdKeyExecute(&Wx::wxSTC_CMD_LINECUT) }
sub copy_current_line{ _ep_ref()->CmdKeyExecute(&Wx::wxSTC_CMD_LINECOPY)}
sub double_current_line {
	my $ep = _ep_ref();
	my $pos = $ep->GetCurrentPos;
	$ep->BeginUndoAction;
	$ep->CmdKeyExecute(&Wx::wxSTC_CMD_LINECOPY);
	$ep->CmdKeyExecute(&Wx::wxSTC_CMD_PASTE);
	$ep->GotoPos($pos);
	$ep->EndUndoAction;
}

sub replace_current_line {
	my $ep = _ep_ref();
	my $line = $ep->GetCurrentLine;
	$ep->BeginUndoAction;
	$ep->GotoLine($line);
	$ep->Paste;
	$ep->SetSelection( 
		$ep->GetSelectionEnd,
		$ep->GetLineEndPosition( $ep->GetCurrentLine )
	);
	$ep->Cut;
	$ep->GotoLine($line);
	$ep->EndUndoAction;
}

sub del_current_line{_ep_ref()->CmdKeyExecute(&Wx::wxSTC_CMD_LINEDELETE)}
sub del_line_left   {_ep_ref()->DelLineLeft() }
sub del_line_right  {_ep_ref()->DelLineRight()}

sub eval_newline_sub{}
1;
__END__
=head1 NAME

Kephra::Edit - basic edit menu calls and internals for editing

=head1 DESCRIPTION

=cut