#!/usr/bin/perl -w
$|++;
BEGIN { unshift(@INC,  q|c:/Dokument/Project/Dev/CPAN/Devel-PerlySense/trunk/source/lib|); }
use strict;

use Getopt::Long;
use Pod::Usage;
use File::Basename;
use File::Path;
use File::Find;
use Path::Class;
use Cache::FileCache;
use List::Util qw/ max /;

use lib "../lib";
use Devel::PerlySense;
use Devel::PerlySense::Home;
use Devel::PerlySense::Util::Log;
use Devel::PerlySense::Editor::Emacs;
use Time::HiRes qw/time/;
use YAML::Tiny;

use Data::Dumper;
use Devel::TimeThis;

local $SIG{__WARN__} = sub { debug(@_); };

my $countCommand = 0;
if (($ARGV[0] || "") eq "--shell") {
    while(1) {
        $countCommand++;
        print 'PerlySense (' . $countCommand . ')$ ';
        my $command = <STDIN>;
        $command =~ /^(quit)|(exit)$/ and exit(0);

        ###TODO: parse into tokens according to shell rules (what about spaces in embedded strings for one thing?)
        local @ARGV = split(/\s+/, $command);

        print "---START OF PERLYSENSE OUTPUT ($countCommand)---\n";
        eval { main_perly_sense() };
        $@ and print $@;
        print "\n---END OF PERLYSENSE OUTPUT ($countCommand)---\n";
    }
}
else {
#     my $total = Devel::TimeThis->new("Total loop");
#      for (1..5) {
#          my $iteration = Devel::TimeThis->new("Iteration");
#          local @ARGV = @ARGV;
#          sleep(5);
    caller() or main_perly_sense();
#      }
}



#Oh yes, this file is in serious need of a makeover...
sub main_perly_sense {
    my ($dirOrigin, $fileOrigin, $module, $dir, $file, $row, $col, $nameClass, $nameMethod, $clearCache, $widthDisplay);
    debug("ARGV: (" . join(" ", "perly_sense",  @ARGV) . ")");

    GetOptions(
        "module:s" => \$module,
        "file:s" => \$file,
        "dir:s" => \$dir,
        "row:i" => \$row,
        "col:i" => \$col,
        "file_origin:s" => \$fileOrigin,
        "dir_origin:s" => \$dirOrigin,
        "class_name:s" => \$nameClass,
        "method_name:s" => \$nameMethod,
        "clear_cache" => \$clearCache,
        "width_display:s" => \$widthDisplay,
        );
    my ($command) = @ARGV;

    $command or syntax("Missing command");

    my $oPs = Devel::PerlySense->new();

    my $oHome = $oPs->oHome;
    my $dirHomeCache = $oHome->dirHomeCache
            or warn(
                "Could not create cache dir in either of:\n",
                join(":", $oHome->aDirHomeCandidate) . "\n",
            );
    if($dirHomeCache && $clearCache) {
        warn("Clearing cache directory ($dirHomeCache)\n");
        rmtree([$dirHomeCache]); -d $dirHomeCache and die("Could not clear cache ($dirHomeCache)\n");
        mkpath([$dirHomeCache]); -d $dirHomeCache or die("Could not re-create cache dir ($dirHomeCache)\n");
    }
    my $oCache = Cache::FileCache->new({cache_root => $dirHomeCache})
            or warn("Could not create cache in ($dirHomeCache)\n");
    $oPs->oCache($oCache);



    my $oEditor = Devel::PerlySense::Editor::Emacs->new(
        oPerlySense => $oPs,
        widthDisplay => $widthDisplay,
    );



    if($command eq "external_dir") {
        print Devel::PerlySense::Editor::Emacs->dirExtenal;
    } elsif($command eq "find_module_source_file") {
        $fileOrigin and $dirOrigin = dirname($fileOrigin);
        $dirOrigin ||= ".";
        $module or syntax("Missing option --module");

        eval {
            my $file = $oPs->fileFindModule(nameModule => $module, dirOrigin => $dirOrigin) || "";
            print $file;
        };
        $@ and warn("Internal error: $@\n");

    } elsif($command eq "display_module_pod") {
        $fileOrigin and $dirOrigin = dirname($fileOrigin);
        $dirOrigin ||= ".";
        $module or syntax("Missing option --module");

        eval {
            if(my $file = $oPs->fileFindModule(nameModule => $module, dirOrigin => $dirOrigin)) {
                my $pod = $oPs->podFromFile(file => $file) || "";
                print $pod;
            } else {
                warn("Module ($module) not found\n");
            }
        };
        $@ and warn("Internal error: $@\n");

    } elsif($command eq "display_file_pod") {
        $file or syntax("Missing option --file");

        eval {
            my $pod = $oPs->podFromFile(file => $file) || "";
            print $pod;
        };
        $@ and warn("Internal error: $@\n");

    } elsif($command eq "smart_go_to") {
        $file or syntax("Missing option --file");
        $row or syntax("Missing option --row");
        $col or syntax("Missing option --col");
#my $tt = Devel::TimeThis->new("Entire goto");

        eval {
            if(my $oLocation = $oPs->oLocationSmartGoTo(file => $file, row => $row, col => $col)) {
                print join("\t", $oLocation->file, $oLocation->row, $oLocation->col);
            }
        };
        $@ and warn("Error: $@\n");

    } elsif($command eq "smart_doc") {
        $file or syntax("Missing option --file");
        $row or syntax("Missing option --row");
        $col or syntax("Missing option --col");

        eval {
            if(my $oLocation = $oPs->oLocationSmartDoc(file => $file, row => $row, col => $col)) {
                print join("\t",  map { $_ => $oLocation->rhProperty->{$_} } qw/ found name docType / ) . "\n";
                print $oLocation->rhProperty->{text} || "";
            }
        };
        $@ and warn("Error: $@\n");

    } elsif($command eq "class_overview") {
        $file || $nameClass or syntax("Missing option --file or --class_name");

        eval {

            my $oClass;
            if($file) {
                $row or syntax("Missing option --row");
                $col or syntax("Missing option --col");

                $oClass = $oClass = $oPs->classAt(
                    file => $file,
                    row => $row,
                    col => $col,
                );
            }
            else {
                $dirOrigin or syntax("Missing option --dir_origin");

                $oClass = $oClass = $oPs->classByName(
                    name => $nameClass,
                    dirOrigin => $dirOrigin,
                );
            }

            if($oClass) {
                my $overview = $oEditor->formatOutputDataStructure(
                    rhData => {
                        class_name => $oClass->name,
                        class_overview => $oEditor->classOverview(oClass => $oClass),
                        message => "Class Overview for (" . $oClass->name . ")",
                        dir => $oClass->dirModule,
                    },
                );
                debug($overview);
                print $overview;
            } else {
                print $oEditor->formatOutputDataStructure(
                    rhData => {
                        message => "No class found",
                    },
                );
            }
        };
        $@ and warn("Error: $@\n");

    } elsif($command eq "method_doc") {
        $nameClass or syntax("Missing option --class_name");
        $nameMethod or syntax("Missing option --method_name");
        $dirOrigin or syntax("Missing option --dir_origin");

        eval {
            my $oClass = $oPs->classByName(
                name => $nameClass,
                dirOrigin => $dirOrigin,
            );
            if($oClass) {
                if (my $oLocation = $oClass->oLocationMethodDoc(method => $nameMethod)) {
                    print join("\t",  map { $_ => $oLocation->rhProperty->{$_} } qw/ found name docType / ) . "\n";
                    print $oLocation->rhProperty->{text} || "";
                }
            };
            $@ and warn("Error: $@\n");
        }

    } elsif($command eq "method_go_to") {
        $nameClass or syntax("Missing option --class_name");
        $nameMethod or syntax("Missing option --method_name");
        $dirOrigin or syntax("Missing option --dir_origin");

        eval {
            my $oClass = $oPs->classByName(
                name => $nameClass,
                dirOrigin => $dirOrigin,
            );
            if($oClass) {
                if (my $oLocation = $oClass->oLocationMethodGoTo(method => $nameMethod)) {
                    print join("\t", $oLocation->file, $oLocation->row, $oLocation->col);                }
            };
            $@ and warn("Error: $@\n");
        }

    } elsif($command eq "run_file") {
        $file or syntax("Missing option --file");

        eval {
            print $oEditor->formatOutputDataStructure(
                rhData => $oPs->rhRunFile(file => $file),
            );
        };
        if(my $err = $@) {
            chomp($err);
            print $oEditor->formatOutputDataStructure( rhData => { message => $err } );
        }

    } elsif($command eq "flymake_file") {
        $file or syntax("Missing option --file");

        $oPs->flymakeFile(file => $file);

    } elsif($command eq "create_project") {
        $dir ||= ".";

        $oPs->createProject(dir => $dir);
        print "PerlySense Project Created\n";

    } elsif($command eq "class_api") {
        $file or syntax("Missing option --file");
        $row or syntax("Missing option --row");
        $col or syntax("Missing option --col");

        eval {
            my ($package, $oApi) = $oPs->aApiOfClass(file => $file, row => $row, col => $col);
            if($oApi) {
                my $rhSub = $oApi->rhSub;
                print "$package\n" . join("\n",
                           map {
                               sprintf("%s:%d %s", $_->{file}, $_->{row}, $_->rhProperty->{sub});
                           } sort {
                               $a->{file} cmp $b->{file}
                                       or
                               $a->{row} <=> $b->{row}
                           }
                           values %$rhSub
                       );
            }
        };
        $@ and warn("Error: $@\n");

    } elsif($command eq "process_inc") {
        my @aFile;
        find({ wanted => sub {
                   /\.pm$/
#                  && @aFile < 5
                           and push(@aFile, file($_)->absolute . "");
               },
               no_chdir => 1,
           }, @INC);

        my %hFileTime;
        for my $file (@aFile) {
            debug("Processing file ($file)");
            my $timeStart = time();
            eval {
                use Devel::TimeThis;
#                my $t = Devel::TimeThis->new("parse");
                $oPs->oDocumentParseFile($file);
            };
            $@ and debug($@);
            my $timeDuration = time() - $timeStart;
            $hFileTime{$file} = $timeDuration;
            debug(sprintf("  %0.6fs", $timeDuration));
        }

        debug("REPORT");
        for my $file (
            sort { $hFileTime{$a} <=> $hFileTime{$b} }
                    keys %hFileTime) {
            debug(sprintf("%0.6f : $file", $hFileTime{$file}));
        }

    } elsif($command eq "info") {
        $dir ||= ".";
        $oPs->setFindProject(dir => $dir);

#        print "PROJECT CONFIG: " . YAML::Tiny::Dump($oPs->rhConfig);
        print "VERSION: (" . Devel::PerlySense->VERSION . ")\n";
        print "PROJECT: (" . $oPs->oProject->dirProject . ")\n";
        print "HOME: (" . $oPs->oHome->dirHome . ")\n";
        print "HOME DEBUG LOG: (" . file($oPs->oHome->dirHome, "log", "debug.log") . ")\n";

    } else {
        syntax("Invalid command ($command)");
    }

    return(1);
}



sub syntax {
    my ($message) = @_;
    print "Error: $message\n\n";    #pod2usage -msg doesn't do this for me... :(
    pod2usage(-verbose => 2, exitval => "NOEXIT");
    die("\n");
}



__END__

=head1 NAME - perly_sense

IntelliSense like editor support for Perl



=head1 OPTIONS

perly_sense COMMAND OPTIONS

=head2 smart_go_to --file, --row, --col

Look in --file at --row, --col and print a location for the origin of
the source at that location,


=head2 smart_doc --file, --row, --col

Look in --file at --row, --col and print a smart doc text for the
source at that location


=head2 class_overview --file, --row, --col

Look in --file at --row, --col and find the class overview for the
package at that location. Return keys:

If a class was found: class_overview, class_name, message.

If a class was not found: message.

On error: error.


=head2 process_inc

Process and cache all modules found in @INC


=head2 --clear_cache

Clears the cache before doing anything else

=head2 --width_display=COLUMNS

Sets the display width COLUMNS wide. Default can be configured per
Project
.


=head1 SYNOPSIS

perly_sense smart_doc --file=perly_sense --row=102 --col=42

perly_sense smart_go_to --file=Foo/Bar.pm --row=32 --col=3

perly_sense smart_doc --file=Foo/Bar.pm --row=32 --col=3

perly_sense process_inc



=head1 EDITOR INTEGRATION

If you want to use this from within an editor/IDE you need to be able to shell
out to call this program and to do something useful with the result using the
editor's macro/programming facilities.

=cut
