#include "KBEditor.h"
#include "FNU.h"
#include <kfiledialog.h>
#include <klocale.h>
#include <kapplication.h>
#include <kmessagebox.h>
#include <klocale.h>
#include <qhbox.h>
#include <qfont.h>
#include <qfontmetrics.h>
#include <qpoint.h>
#include <qevent.h>
#include <qapplication.h>
#include <qfile.h>
#include <qstring.h>
#include <qstringlist.h>
#include <qtextedit.h>

#include "PopupWindow.h"

#include <iostream>

#include <ktexteditor/printinterface.h>
#include <ktexteditor/document.h>


using namespace std ;

KBEditor::KBEditor(QWidget *parent) :
        QVBox(parent)  , doc(0), view(0), old_read_write_state(true), selected_line(-1)
{
    // populateList();
    KLibFactory *factory ;
    factory = KLibLoader::self()->factory("libkatepart") ;

    KTextEditor::Document  *tmp ;
    tmp = (KTextEditor::Document *)
          factory->create(0L,"kate", "KTextEditor::Document") ;

    doc = (  Kate::Document * ) tmp ;

    // find HL scheme for ASM sources
    for( picHLNumber = 0 ; (picHLNumber < doc->hlModeCount())  && (doc->hlModeName( picHLNumber ) != "XPicAsm"); ++picHLNumber)  ;
    if(   picHLNumber == doc->hlModeCount() )
        for( picHLNumber = 0 ; (picHLNumber < doc->hlModeCount())  && (doc->hlModeName( picHLNumber ) != "PicAsm"); ++picHLNumber)  ;

    split = new QSplitter(this) ; // ready for multiview

    QHBox *bottom = new QHBox(this) ;

    lab_URL =   new QLabel("" ,bottom ) ;
    bottom->setStretchFactor( new QHBox(bottom ) ,5) ;
    lab_status =new QLabel("RW" ,bottom ) ;
    new QLabel(i18n("   Line: ") ,bottom ) ;
    lab_linenum = new QLabel("0" ,bottom ) ;
    new QLabel(i18n("   Column: ") ,bottom ) ;
    lab_colnum = new QLabel("0" ,bottom ) ;
    QFontMetrics fm(font()) ;
    bottom->setMaximumHeight(2 +  fm.height()) ;

#if 0
    //Create a box to put my new bar into
    QHBox *help = new QHBox(this);
    lab_curword = new QLabel("Pic Instruction Help",help);
    //Set the height of the bars
    help->setMaximumHeight(2 + fm.height());
#endif

    connect(doc, SIGNAL(undoChanged()), this, SIGNAL(undoRedoStateChanged()) ) ;
    connect(doc, SIGNAL(selectionChanged()), this, SIGNAL(selectionStateChanged()) ) ;

    connect(doc, SIGNAL(textChanged()), this, SIGNAL(textChanged()) ) ;
    connect(doc, SIGNAL(fileNameChanged()), this, SLOT(slotFileNameChanged()) ) ;

    connect(doc, SIGNAL(hlChanged()), this, SLOT(slotHlChanged()) ) ;


    setFrameStyle ( QFrame::TabWidgetPanel |  QFrame::Sunken);

    setAcceptDrops(true) ;

    slotAddView() ;
}
//
// create a new view
//
void KBEditor::slotAddView()
{
    KTextEditor::View *v = doc->createView(split) ;
    if(v)
    {
        connect( v, SIGNAL(gotFocus(Kate::View *)), this, SLOT(slotGotFocus(Kate::View * )) ) ;
        connect( v, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()) ) ;
        connect( v, SIGNAL(dropEventPass(QDropEvent *)), this, SIGNAL(dropEventPass(QDropEvent *) ) ) ;
        connect( v, SIGNAL(newStatus()) , this, SLOT(slotStatusChanged()) );
        //Change 2-30-04 Connecting to the cursorChange signal to allow the routine to run
        //on a loaded file with no text change
        //connect(v, SIGNAL(cursorPositionChanged()), this, SLOT(slotChangedText()) );

        // I don't know why this is needed for the new view to appears
        v->show();
        v->setFocus() ;

        // dispatch available space between views
        QValueList<int> list = split->sizes();
        QValueList<int>::Iterator it;
        int sum ;
        for (it = list.begin( ), sum = 0  ;  it != list.end()  ; ++it)   sum += *it;
        sum  /=   list.size() ;
        for (it = list.begin( )  ;  it != list.end()  ; ++it)   *it = sum ;
        split->setSizes(list) ;
        if( view == 0) view = (Kate::View * )v ; // if first view

        v->installEventFilter(this) ;

        emit viewsStateChanged() ;
    }
}
//
// Called when the focus is transfered to another view
//
void  KBEditor::slotGotFocus(Kate::View * v)
{
    setFocusProxy( view = v ) ;
    slotCursorPositionChanged() ;
    slotStatusChanged() ;
    emit gotFocus() ;
}

void KBEditor::slotCursorPositionChanged()
{
    uint l,c ;
    Kate::View *v =  currentView()   ;
    if( v != 0)
    {
        v->cursorPosition(&l, &c) ;
        // emit cursorPositionChanged(l,c) ;
        QString s;
        lab_linenum->setText(s.setNum(l+1)) ;
        lab_colnum->setText(s.setNum(c+1)) ;
    }
}
//
// remove current view of document if it is NOT the last view
//
void KBEditor::slotRemoveCurrentView()
{
    if( viewCount() > 1 )
    {
        delete currentView()  ;
        // transfer focus to the last view   , which becomes  the proxy of KBEditor  AND current view
        doc->views().last() ->setFocus() ;
        emit viewsStateChanged() ;
    }
}

int KBEditor::viewCount()
{
    return  doc->views().count() ;
}

int KBEditor::undoCount()
{
    return  doc->undoCount() ;
}

int KBEditor::redoCount()
{
    return  doc->redoCount() ;
}

int  KBEditor::selectionCount()
{
    return doc->hasSelection()  ? 1 : 0 ;
}
bool KBEditor::textIsModified()
{
    return doc->isModified();
}

void KBEditor::slotUndo()
{
    doc->undo() ;
}

void KBEditor::slotRedo()
{
    doc->redo();
}
//
//
//
Kate::View *KBEditor::currentView()
{
    return view ;
}

void KBEditor::slotCut()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->cut() ;
    }
}

void KBEditor::slotCopy()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->copy() ;
    }
}

void KBEditor::slotPaste()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->paste() ;
    }
}

void KBEditor::slotSearch()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->find() ;
    }
}

void KBEditor::slotReplace()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->replace() ;
    }
}

void KBEditor::slotGotoLine()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->gotoLine() ;
    }
}
void KBEditor::slotSave()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        /* Kate::View::saveResult s = */
        if( v->save() == Kate::View::SAVE_OK ) emit textChanged() ;
    }
}

void KBEditor::slotSaveAs()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        /*Kate::View::saveResult s = */
        if( v->saveAs() == Kate::View::SAVE_OK ) emit textChanged() ;
    }
}

void KBEditor::slotOpen()
{
    KURL url ;

    url = KFileDialog::getOpenURL ( "." , "All (*)",  this ) ;

    if( url.isValid()) slotOpen(url) ;
}

void KBEditor::slotOpen(const KURL& url)
{
    doc->openURL(url) ;
    QString name = url.fileName() ;

    // set the DOS EOL status to the proper value
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->setEol( isDosFile( filePathName())  ? 1 : 0 ) ;
    }

    emit eolChanged() ; // update interface
    slotStatusChanged() ; // update local status bar

    doc->setModified(false) ;  // tag buffer as "clean"

    if( FNU::isAsmSource(name) || FNU::isHexFile(name) ||
            FNU::isCSource(name) || FNU::isIncFile(name)
            || FNU::isSrcLib(name) || FNU::isCHeader(name) )
    {
        // add url to recently loaded files  list
        KMainWindow *main_window = dynamic_cast< KMainWindow *>(kapp->mainWidget()) ;
        if( main_window )
        {
            KAction *aa = main_window->actionCollection()->action("open_recent_file")  ;
            KRecentFilesAction *a = dynamic_cast< KRecentFilesAction *>(aa)  ;
            if( a) a->addURL( url ) ;
        }
    }
    slotHlChanged() ;
}

void KBEditor::slotToggleLineNumbersOn()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->toggleLineNumbersOn() ;
    }
}


bool KBEditor::lineNumbersOn()
{
    Kate::View *v = currentView() ;
    bool r = false ;
    if(v != 0)
    {
        r = v->lineNumbersOn() ;
    }
    return r ;
}

KURL KBEditor::fileURL()
{
    return doc->url() ;
}

QString KBEditor::filePathName()
{
    return fileURL().directory(false) +  fileURL().fileName();
}

QString KBEditor::fileName()
{
    return  fileURL().fileName();
}

void  KBEditor::slotFileNameChanged()
{
    lab_URL->setText("  "+fileURL().prettyURL() ) ;

    emit fileNameChanged() ;
}
void  KBEditor::slotSetReadWrite(bool rw)
{
    doc->setReadWrite(rw) ;
}

bool KBEditor::isReadWrite()
{
    return doc->isReadWrite();
}
void  KBEditor::slotStatusChanged()
{
    // the only status tested is RW (and file type)
    // but other documents attributes
    // can be monitored here

    QString stat(doc->hlModeName( doc->hlMode() )) ; // syntax highlight mode name

    stat += "   R" ;
    if( isReadWrite() ) stat += "W" ;
    if( wrapOn() ) stat += "-wrap" ;
    if( dosEOLOn() ) stat += "-Dos EOL" ;
    lab_status->setText(stat) ;
}
void KBEditor::slotToggleBorderOn()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->toggleIconBorder() ;
    }
}
bool KBEditor::borderOn()
{
    Kate::View *v = currentView() ;
    bool r = false ;
    if(v != 0)
    {
        r = v->iconBorder() ;
    }
    return r ;
}
void KBEditor::slotToggleDosEOLOn()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        // note : EOL codes
        // 0 = Unix, 1 = Dos, 2 = Mac
        v->setEol(dosEOLOn() ? 0 : 1 ) ;
        slotStatusChanged() ;
        emit textChanged() ; // changing buffer status is equivalent to change text
    }

}
bool KBEditor::dosEOLOn()
{
    Kate::View *v = currentView() ;
    bool r = false ;
    if(v != 0)
    {
        r = ( v->getEol() == 1 /* Kate::Document::eolDos */ );
    }
    return r ;
}
void KBEditor::slotToggleWrapOn()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->setDynWordWrap(!wrapOn()) ;
        slotStatusChanged() ;
    }
}
bool KBEditor::wrapOn()
{
    Kate::View *v = currentView() ;
    bool r = false ;
    if(v != 0)
    {
        r = v->dynWordWrap() ;
    }
    return r ;
}
///
// temporary mark a line as selected
// for the purpose of highlighting asm errors
// To avoid text corruption, the buffer is write-disabled
// when the line is selected.
// See   unSelectLine()  routine
//
void KBEditor::slotSelectLine( )
{
    Kate::View *v = currentView() ;
    // if already in readonly mode => the mouse has been clicked again
    // before unSelectLine has been called, so ignore the click	.
    if(v != 0 && selected_line != -1 )
    {
        v->setCursorPosition(selected_line,0) ;
        old_read_write_state =  isReadWrite()  ;   // save old R/W flag
        slotSetReadWrite(false) ;           // disable W
        doc->setSelection(selected_line, 0, selected_line+1, 0) ;
    }
}
void KBEditor::slotUnSelectLine()
{
    if( selected_line != -1)
    {
        doc->clearSelection() ;
        selected_line = -1;
        slotSetReadWrite(old_read_write_state) ;     // restore previous R/W state
        if(currentView()) currentView()->setFocus() ;
    }
}


void KBEditor::updateBookmarksMenu(KPopupMenu *pop)
{
    pop->clear ();
    QPtrList<KTextEditor::Mark> m = doc->marks ();

    for (int i=0; (uint) i < m.count(); i++)
    {
        if (m.at(i)->type& (1 << 0))
        {
            QString linetxt = doc->textLine(m.at(i)->line);
            linetxt.truncate(48);
            linetxt.append ("...");
            pop->insertItem ( QString("%1 - \"%2\"").arg(m.at(i)->line+1).arg(linetxt),
                              this, SLOT (slotGotoBookmark(int)), 0, i );
        }
    }
}

void KBEditor::slotGotoBookmark(int i)
{
    currentView()->gotoMark(doc->marks().at(i)) ;
}

/** No descriptions */
void KBEditor::slotComment()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->comment() ;
    }
}
/** Uncomment current selection
 */
void KBEditor::slotUncomment()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->uncomment() ;
    }
}
/** Extract the path from the URL of the the file */
QString KBEditor::pathName()
{
    return fileURL().directory(false)  ;  // not finished
}
/** Set the line to be highlighted
\return true if OK, false if already selected
 */
bool KBEditor::selectLine(int line)
{
    if( selected_line != -1) return false ;
    selected_line = line ;
    return true ;
}

/** Scan a file and detect DOS  (ie: CR-LF) end of lines
\return true if the file is detect as a DOS file
 */
bool KBEditor::isDosFile(const QString &name)
{
    QFile f(name) ;

    if( ! f.open( IO_Raw | IO_ReadOnly ) ) return false ;

    int c, oldc=0 ;
    for( c= 0 ; (c=f.getch()) != -1 ; )
    {
        if( c == '\n' && oldc == '\r') return true ; // file closed by destructor
        oldc = c ;
    }
    return false ;
}

// Begin my slot
#if 0
void KBEditor::slotChangedText()
{
    //This slot runs the instrucion set help bar,
    // finds the current command word and compares it to the set of instrucions
    //Found in the descript Qstringlist.


    QString currentword;
    QString testval;

    Kate::View *v =  currentView();
    currentword = v->currentTextLine();
    //prepare the string for compareing
    currentword = currentword.simplifyWhiteSpace();
    currentword = currentword.upper();

    for ( QStringList::Iterator it = descript.begin(); it != descript.end(); ++it )
    {
        testval = *it;
        if(testval.startsWith(currentword.left(5)))
        {
            //if (testval.left(5) == currentword.left(5) ) {
            lab_curword->setText(*it);
        }
        //	else {
        //		lab_curword->setText("Pic Instruction Help");
        //	}
    }


}
#endif
#if 0
void KBEditor::populateList()
{
    //Populate the qstringlist with the pic instruction set, and the text to go along with them

    descript	+= "CLRF  : Clear F	OPERANDS: f";
    descript	+= "ADDWF : Add W to F	OPERANDS: f,d";
    descript	+= "ANDWF : AND W with F OPERANDS: f,d";
    descript	+= "CLRW  : Clear W	OPERANDS: NONE";
    descript	+= "COMF  : Complement F OPERANDS: f,d";
    descript 	+= "DECF  : Decrement  F OPERANDS: f,d";
    descript 	+= "DECSSZ: Decrement F, Skip 0 OPERANDS: f,d";
    descript	+= "INCF  : Increment F  OPERANDS: f,d";
    descript	+= "INCFSZ: Increment F, Skip 0 OPERANDS: f,d";
    descript	+= "IORWF : Inclusive OR W with F OPERANDS: f,d";
    descript	+= "MOVF  : Move F,  OPERANDS: f,d";
    descript 	+= "MOVWF : Move W to F OPERANDS: f,d";
    descript	+= "NOP   : No Operation OPERANDS: NONE";
    descript	+= "RLF   : Rotate Left F through Carry OPERANDS: f,d";
    descript	+= "RRF   : Rotate Right F through Carry OPERANDS: f,d";
    descript	+= "SUBWF : Subtract W from F OPERANDS: f,d";
    descript	+= "SWAPF : Swap Nibbles in F OPERANDS: f,d";
    descript	+= "XORWF : Exclusive OR W with F OPERANDS: f,s";
    descript	+= "BCF   : Bit Clear F OPERANDS: f,b";
    descript	+= "BSF   : Bit Set F   OPERANDS: f,b";
    descript	+= "BTFSC : Bit Test F, Skip if Clear OPERANDS: f,b";
    descript	+= "BTFSS : Bit Test F, Skip if Set   OPERANDS: f,b";
    descript	+= "ADDLW : Add Literal and W OPERANDS: k";
    descript	+= "ANDLW : And Literal and W OPERANDS: k";
    descript	+= "CLRWDT: Clear Watchdog Timer OPERANDS: NONE";
    descript 	+= "CALL  : Call Subroutine OPERANDS: k";
    descript	+= "GOTO  : Goto address OPERANDS: k";
    descript	+= "IORLW : Inclusive OR literal with W OPERANDS: k";
    descript	+= "MOVLW : Move Literal with W  OPERANDS: k";
    descript	+= "RETFIE: Return From Interrupt OPERANDS: NONE";
    descript	+= "RETLW : Return with Literal in W OPERANDS: k";
    descript	+= "RETURN: Return from subroutine OPERANDS: NONE";
    descript	+= "SLEEP : Go into Standby Mode OPERANDS: NONE";
    descript	+= "SUBLW : Subtract W from Literal OPERANDS: k";
    descript	+= "XORLW : Exclusive OR Literal with W OPERANDS: k";

}
#endif

/** No descriptions */
void KBEditor::slotFindNext()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->findAgain() ;
    }
    else QApplication::beep() ;
}
/** No descriptions */
void KBEditor::slotFindPrevious()
{
    Kate::View *v = currentView() ;
    if(v != 0)
    {
        v->findPrev() ;
    }
    else QApplication::beep() ;
}
/** No descriptions */
void KBEditor::slotPrintDialog()
{
    if(doc) KTextEditor::printInterface(doc)->printDialog ();
}
/** No descriptions */
bool KBEditor::eventFilter(QObject *target, QEvent *event)
{
    //  static PopupWindow *q   = 0 ;
    //  if( target == view && FNU::isAsmSource(fileName()))
    //  {
    //		if(event->type() == QEvent::ContextMenu)
    //		{
    //			QContextMenuEvent *ev = dynamic_cast<QContextMenuEvent *>(event) ;
    //			int line = view->cursorLine() ;
    //			q = new PopupWindow(this,"Ceci est un texte d'aide") ;
    //			q->move(ev->globalX(), ev->globalY()) ;
    //			return true ;
    //		}
    //		else if( event->type() == QEvent::MouseButtonRelease)
    //		{
    //			  if(q)
    //			  {
    //					 delete q ; q = 0 ;
    //						return true ;
    //			  }
    //		}
    //
    //  }
    return QHBox::eventFilter(target,event) ;
}
/** No descriptions */
void KBEditor::slotHlChanged()
{
    // cout << "current HL = " << doc->hlMode() << " (" << doc->hlModeName( doc->hlMode() ) << ")" << endl ;
    // QString modeName(doc->hlModeName( doc->hlMode() )) ;
    // modeName =   modeName.lower() ;

    if(  picHLNumber ==  doc->hlModeCount()) return ; // no HL for pic ASM

    QString fn = fileName() ;

    // because of collisions between file extensions
    // used by PiKdev and other languages/softwares
    // (ex: other ASM and also PHP ..)
    // I have to manage HL myself
    if( (FNU::isIncFile(fn)   ||   FNU::isAsmSource(fn) ||   FNU::isSrcLib(fn))  &&  (doc->hlMode() != picHLNumber) )
    {
        doc->setHlMode(picHLNumber ) ;
    }
}
/** No descriptions */
void KBEditor::slotConfigEditor()
{
    doc->configDialog() ;
}


/** No descriptions */
KBEditor::~KBEditor()
{
    delete doc ;
}
/** Indent C files thru "astyle" utility */
void KBEditor::slotIndent()
{
	QString fullpath ; 
    QString fn = filePathName() ;
    if( (FNU::isCHeader(fn) || FNU::isCSource(fn)) && isReadWrite() )
    {

        if( FNU::appFound("astyle", fullpath ))
        {
            uint l, c ;

            Kate::View *v =  currentView()   ;

            if(v) v->cursorPosition( &l, &c) ;
            
            if( textIsModified())
            {
                slotSave() ;
            }

            QString cmd="astyle --style=ansi --pad=oper --indent=spaces=3 " ;
            cmd += fn ;
            cmd +=  " 2>&1" ;

            FILE *f = popen(cmd.latin1(),"r") ;
            pclose(f);

            slotOpen(KURL(fn)) ;
            c = 0 ;
            if(v) v->setCursorPosition(l,c) ;

        }
        else
        {
            KMessageBox::detailedSorry( this,  i18n("Cannot indent"),
                                        i18n("Please install <b>astyle</b> utility."  )) ;
        }
    }
}
