// Code from Amarok.

/***************************************************************************
 *   Copyright (C) 2005 Max Howell <max.howell@methylblue.com>             *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

//#include "amarok.h"
//#include "amarokconfig.h"
#include "crashhandler.h"
//#include "debug.h"
#include "config.h"

#include <tdeapplication.h> //invokeMailer()
#include <tdeaboutdata.h>
#include <tdeversion.h>
#include <tdelocale.h>
#include <tdetempfile.h>

#include <tqfile.h>
#include <tqregexp.h>
#include <tqtextstream.h>

#include <cstdio>         //popen, fread
#include <iostream>
#include <sys/types.h>    //pid_t
#include <sys/wait.h>     //waitpid
//#include <taglib/taglib.h>
#include <unistd.h>       //write, getpid



//#ifndef TAGLIB_PATCH_VERSION
//// seems to be wheel's style
//#define TAGLIB_PATCH_VERSION 0
//#endif


    #if 0
    class CrashHandlerWidget : public KDialog {
    public:
        CrashHandlerWidget();
    };
    #endif

    static TQString
    runCommand( const TQCString &command )
    {
        static const uint SIZE = 40960; //40 KiB
        static char stdoutBuf[ SIZE ];

//        debug() << "Running: " << command << endl;

        FILE *process = ::popen( command, "r" );
        stdoutBuf[ std::fread( static_cast<void*>( stdoutBuf ), sizeof(char), SIZE-1, process ) ] = '\0';
        ::pclose( process );

        return TQString::fromLocal8Bit( stdoutBuf );
    }

    void
    Crash::crashHandler( int /*signal*/ )
    {
        // we need to fork to be able to get a
        // semi-decent bt - I dunno why
        const pid_t pid = ::fork();

        if( pid <= 0 )
        {
            // we are the child process (the result of the fork)
//            debug() << "amaroK is crashing...\n";

            TQString subject = "[basket-crash] " VERSION " ";
            TQString body = i18n(
                    "%1 has crashed! We're sorry about this.\n"
                    "\n"
                    "But, all is not lost! You could potentially help us fix the crash. "
                    "Information describing the crash is below, so just click send, "
                    "or if you have time, write a brief description of how the crash happened first.\n\n"
                    "Many thanks." ).arg(tdeApp->aboutData()->programName()) + "\n\n";
            body += "\n\n\n\n\n\n" + i18n(
                    "The information below is to help the developers identify the problem, "
                    "please do not modify it." ) + "\n\n\n\n";


            body += "======== DEBUG INFORMATION  =======\n"
                    "Version:    " VERSION "\n"
                    "Build date: " __DATE__ "\n"
                    "CC version: " __VERSION__ "\n" //assuming we're using GCC
                    "KDElibs:    " TDE_VERSION_STRING "\n"
;//                    "TagLib:     %2.%3.%4\n";

/*            body = body
                    .arg( TAGLIB_MAJOR_VERSION )
                    .arg( TAGLIB_MINOR_VERSION )
                    .arg( TAGLIB_PATCH_VERSION );*/

            #ifdef NDEBUG
            body += "NDEBUG:     true";
            #endif
            body += "\n";

            /// obtain the backtrace with gdb

            KTempFile temp;
            temp.setAutoDelete( true );

            const int handle = temp.handle();

//             TQCString gdb_command_string =
//                     "file amarokapp\n"
//                     "attach " + TQCString().setNum( ::getppid() ) + "\n"
//                     "bt\n" "echo \\n\n"
//                     "thread apply all bt\n";

            const TQCString gdb_batch =
                    "bt\n"
                    "echo \\n\\n\n"
                    "bt full\n"
                    "echo \\n\\n\n"
                    "echo ==== (gdb) thread apply all bt ====\\n\n"
                    "thread apply all bt\n";

            ::write( handle, gdb_batch, gdb_batch.length() );
            ::fsync( handle );

            // so we can read stderr too
            ::dup2( fileno( stdout ), fileno( stderr ) );


            TQCString gdb;
            gdb  = "gdb --nw -n --batch -x ";
            gdb += temp.name().latin1();
            gdb += " basket ";
            gdb += TQCString().setNum( ::getppid() );

            TQString bt = runCommand( gdb );

            /// clean up
            bt.remove( "(no debugging symbols found)..." );
            bt.remove( "(no debugging symbols found)\n" );
            bt.replace( TQRegExp("\n{2,}"), "\n" ); //clean up multiple \n characters
            bt.stripWhiteSpace();

            /// analyze usefulness
            bool useful = true;
            const TQString fileCommandOutput = runCommand( "file `which basket`" );

            if( fileCommandOutput.find( "not stripped", false ) == -1 )
                subject += "[___stripped]"; //same length as below
            else
                subject += "[NOTstripped]";

            if( !bt.isEmpty() ) {
                const int invalidFrames = bt.contains( TQRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in \\?\\?") );
                const int validFrames = bt.contains( TQRegExp("\n#[0-9]+\\s+0x[0-9A-Fa-f]+ in [^?]") );
                const int totalFrames = invalidFrames + validFrames;

                if( totalFrames > 0 ) {
                    const double validity = double(validFrames) / totalFrames;
                    subject += TQString("[validity: %1]").arg( validity, 0, 'f', 2 );
                    if( validity <= 0.5 ) useful = false;
                }
                subject += TQString("[frames: %1]").arg( totalFrames, 3 /*padding*/ );

                if( bt.find( TQRegExp(" at \\w*\\.cpp:\\d+\n") ) >= 0 )
                    subject += "[line numbers]";
            }
            else
                useful = false;

//            subject += TQString("[%1]").arg( AmarokConfig::soundSystem().remove( TQRegExp("-?engine") ) );

//            debug() << subject << endl;


            //TODO -fomit-frame-pointer buggers up the backtrace, so detect it
            //TODO -O optimization can rearrange execution and stuff so show a warning for the developer
            //TODO pass the CXXFLAGS used with the email

            if( useful ) {
                body += "==== file `which basket` ==========\n";
                body += fileCommandOutput + "\n";
                body += "==== (gdb) bt =====================\n";
				body += bt;//+ "\n\n";
//                body += "==== kdBacktrace() ================\n";
//                body += kdBacktrace();

                //TODO startup notification
                tdeApp->invokeMailer(
                        /*to*/          "kelvie@ieee.org",
                        /*cc*/          TQString(),
                        /*bcc*/         TQString(),
                        /*subject*/     subject,
                        /*body*/        body,
                        /*messageFile*/ TQString(),
                        /*attachURLs*/  TQStringList(),
                        /*startup_id*/  "" );
            }
            else {
				std::cout << ("\n" + i18n( "%1 has crashed! We're sorry about this.\n\n"
                                          "But, all is not lost! Perhaps an upgrade is already available "
                                          "which fixes the problem. Please check your distribution's software repository." )
						.arg(tdeApp->aboutData()->programName())).local8Bit().data() << std::endl;
            }

            //_exit() exits immediately, otherwise this
            //function is called repeatedly ad finitum
            ::_exit( 255 );
        }

        else {
            // we are the process that crashed

            ::alarm( 0 );

            // wait for child to exit
            ::waitpid( pid, NULL, 0 );
            ::_exit( 253 );
        }
    }
