/***************************************************************************
 *   Copyright (C) 2005-2007 by Rajko Albrecht                             *
 *   ral@alwins-world.de                                                   *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/
#include "svnlogdlgimp.h"
#include "tdesvnsettings.h"
#include "log_entry.h"
#include "sub2qt.h"
#include "svnactions.h"
#include "revisionbuttonimpl.h"

#include <tdelistview.h>
#include <ktextbrowser.h>
#include <kpushbutton.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#include <kapp.h>
#include <tdeconfigbase.h>
#include <tdeconfig.h>
#include <ktabwidget.h>
#include <kdebug.h>

#include <tqdatetime.h>
#include <tqheader.h>
#include <tqsplitter.h>
#include <tqtextstream.h>
#include <tqpopupmenu.h>

#include <list>


const char* SvnLogDlgImp::groupName = "log_dialog_size";

class LogListViewItem:public TDEListViewItem
{
public:
    LogListViewItem (TDEListView *parent,const svn::LogEntry&);
    virtual int compare( TQListViewItem* i, int col, bool ascending ) const;

    static const int COL_MARKER,COL_REV,COL_AUTHOR,COL_DATE,COL_MSG;
    const TQString&message()const;
    svn_revnum_t rev()const{return _revision;}
    void showChangedEntries(TDEListView*);
    unsigned int numChangedEntries(){return changedPaths.count();}
    void setChangedEntries(const svn::LogEntry&);
    void setRealName(const TQString&_n){_realName=_n;}
    const TQString&realName()const{return _realName;}

    bool copiedFrom(TQString&_n,long&rev)const;
    static bool isParent(const TQString&_par,const TQString&tar);

protected:
    svn_revnum_t _revision;
    TQDateTime fullDate;
    TQString _message,_realName;
    TQValueList<svn::LogChangePathEntry> changedPaths;
};

const int LogListViewItem::COL_MARKER = 0;
const int LogListViewItem::COL_REV = 2;
const int LogListViewItem::COL_AUTHOR = 1;
const int LogListViewItem::COL_DATE = 3;
const int LogListViewItem::COL_MSG = 4;

class LogChangePathItem:public TDEListViewItem
{
public:
    LogChangePathItem(TDEListView*parent,const svn::LogChangePathEntry&);
    virtual ~LogChangePathItem(){}

    TQChar action() const{return _action;}
    const TQString& path() const{return _path;}
    const TQString& source() const{return _source;}
    svn_revnum_t revision() const{ return _revision;}

protected:
    TQString _path,_source;
    TQChar _action;
    svn_revnum_t _revision;
};

LogListViewItem::LogListViewItem(TDEListView*_parent,const svn::LogEntry&_entry)
    : TDEListViewItem(_parent),_realName(TQString())
{
    setMultiLinesEnabled(false);
    _revision=_entry.revision;
    fullDate=svn::DateTime(_entry.date);
    setText(COL_REV,TQString("%1").arg(_revision));
    setText(COL_AUTHOR,_entry.author);
    setText(COL_DATE,helpers::sub2qt::apr_time2qtString(_entry.date));
    _message = _entry.message;
    TQStringList sp = TQStringList::split("\n",_message);
    if (sp.count()==0) {
        setText(COL_MSG,_message);
    } else {
        setText(COL_MSG,sp[0]);
    }
    changedPaths = _entry.changedPaths;
    //setText(COL_MSG,_entry.message.c_str());
}

const TQString&LogListViewItem::message()const
{
    return _message;
}

int LogListViewItem::compare( TQListViewItem* item, int col, bool ) const
{
    LogListViewItem* k = static_cast<LogListViewItem*>( item );
    if (col==COL_REV) {
        return _revision-k->_revision;
    }
    if (col==COL_DATE) {
        return k->fullDate.secsTo(fullDate);
    }
    return text(col).localeAwareCompare(k->text(col));
}

void LogListViewItem::showChangedEntries(TDEListView*where)
{
    if (!where)return;
    where->clear();
    if (changedPaths.count()==0) {
        return;
    }
    for (unsigned i = 0; i < changedPaths.count();++i) {
        new LogChangePathItem(where,changedPaths[i]);
    }
}

LogChangePathItem::LogChangePathItem(TDEListView*parent,const svn::LogChangePathEntry&e)
    :TDEListViewItem(parent)
{
    _action = TQChar(e.action);
    setText(0,_action);
    _path = e.path;
    setText(1,e.path);
    _revision = e.copyFromRevision;
    _source = e.copyFromPath;
    if (e.copyFromRevision>-1)
    {
        setText(2,i18n("%1 at revision %2").arg(e.copyFromPath).arg(e.copyFromRevision));
    }
}

void LogListViewItem::setChangedEntries(const svn::LogEntry&_entry)
{
    changedPaths = _entry.changedPaths;
}

bool LogListViewItem::copiedFrom(TQString&_n,long&_rev)const
{
    for (unsigned i = 0; i < changedPaths.count();++i) {
        if (changedPaths[i].action=='A' &&
            !changedPaths[i].copyFromPath.isEmpty() &&
            isParent(changedPaths[i].path,_realName)) {
            kdDebug()<<_realName<< " - " << changedPaths[i].path << endl;
            TQString tmpPath = _realName;
            TQString r = _realName.mid(changedPaths[i].path.length());
            _n=changedPaths[i].copyFromPath;
            _n+=r;
            _rev = changedPaths[i].copyFromRevision;
            kdDebug()<<"Found switch from  "<< changedPaths[i].copyFromPath << " rev "<<changedPaths[i].copyFromRevision<<endl;
            kdDebug()<<"Found switch from  "<< _n << " rev "<<_rev<<endl;
            return true;
        }
    }
    return false;
}

bool LogListViewItem::isParent(const TQString&_par,const TQString&tar)
{
    if (_par==tar) return true;
    TQString par = _par+(_par.endsWith("/")?"":"/");
    return tar.startsWith(par);
}

SvnLogDlgImp::SvnLogDlgImp(SvnActions*ac,TQWidget *parent, const char *name,bool modal)
    :SvnLogDialogData(parent, name,modal),_name("")
{
    m_LogView->setSorting(LogListViewItem::COL_REV);
    m_LogView->setSortOrder(TQt::Descending);
    resize(dialogSize());
    m_ControlKeyDown = false;
    m_first = 0;
    m_second = 0;

    if (Kdesvnsettings::self()->log_always_list_changed_files()) {
        buttonListFiles->hide();
    } else {
        m_ChangedList->hide();
    }
    m_Actions = ac;
    TDEConfigGroup cs(Kdesvnsettings::self()->config(), groupName);
    TQString t1 = cs.readEntry("logsplitter",TQString());
    if (!t1.isEmpty()) {
        TQTextStream st2(&t1,IO_ReadOnly);
        st2 >> *m_centralSplitter;
    }
    t1 = cs.readEntry("right_logsplitter",TQString());
    if (!t1.isEmpty()) {
        if (cs.readBoolEntry("laststate",false)==m_ChangedList->isHidden()) {
            TQTextStream st2(&t1,IO_ReadOnly);
            st2 >> *m_rightSplitter;
        }
    }
}

SvnLogDlgImp::~SvnLogDlgImp()
{
    TQString t1,t2;
    TQTextStream st1(&t1,IO_WriteOnly);
    st1 << *m_rightSplitter;
    TQTextStream st2(&t2,IO_WriteOnly);
    st2 << *m_centralSplitter;
    TDEConfigGroup cs(Kdesvnsettings::self()->config(), groupName);
    cs.writeEntry("right_logsplitter",t1);
    cs.writeEntry("logsplitter",t2);
    cs.writeEntry("laststate",m_ChangedList->isHidden());
}

void SvnLogDlgImp::dispLog(const svn::SharedPointer<svn::LogEntriesMap>&_log,const TQString & what,const TQString&root,const svn::Revision&peg,const TQString&pegUrl)
{
    m_peg = peg;
    m_PegUrl = pegUrl;
    m_first = m_second = 0;
    m_startRevButton->setNoWorking(m_PegUrl.isUrl());
    m_endRevButton->setNoWorking(m_PegUrl.isUrl());
    if (!m_PegUrl.isUrl() || Kdesvnsettings::remote_special_properties()) {
        TQString s = m_Actions->searchProperty(_bugurl,"bugtraq:url",pegUrl,peg,true);
        if (!s.isEmpty() ){
            TQString reg;
            s = m_Actions->searchProperty(reg,"bugtraq:logregex",pegUrl,peg,true);
            if (!s.isNull() && !reg.isEmpty()) {
                TQStringList s1 = TQStringList::split("\n",reg);
                if (s1.size()>0) {
                    _r1.setPattern(s1[0]);
                    if (s1.size()>1) {
                        _r2.setPattern(s1[1]);
                    }
                }
            }
        }
    }
    _base = root;
    m_first = m_second = 0;
    m_Entries = _log;
    kdDebug()<<"What: "<<what << endl;
    if (!what.isEmpty()){
        setCaption(i18n("SVN Log of %1").arg(what));
    } else {
        setCaption(i18n("SVN Log"));
    }
    _name = what;
    dispLog(_log);
}

void SvnLogDlgImp::dispLog(const svn::SharedPointer<svn::LogEntriesMap>&_log)
{
    m_LogView->clear();
    m_LogView->header()->setLabel(0, " ");
    m_LogView->setColumnWidth(0,10);
    if (!_log) {
        return;
    }
    svn::LogEntriesMap::const_iterator lit;
    LogListViewItem * item;
    TQMap<long int,TQString> namesMap;
    TQMap<long int,LogListViewItem*> itemMap;
    long min,max;
    min = max = -1;
    for (lit=_log->begin();lit!=_log->end();++lit) {
        item = new LogListViewItem(m_LogView,(*lit));
        if ((*lit).revision>max) max = (*lit).revision;
        if ((*lit).revision<min || min == -1) min = (*lit).revision;
        itemMap[(*lit).revision]=item;
    }
    if (itemMap.count()==0) {
        return;
    }
    m_startRevButton->setRevision(max);
    m_endRevButton->setRevision(min);
    m_LogView->setSelected(m_LogView->firstChild(),true);
    TQString bef = _name;
    long rev;
    // YES! I'd checked it: this is much faster than getting list of keys
    // and iterating over that list!
    for (long c=max;c>-1;--c) {
        if (!itemMap.contains(c)) {
            continue;
        }
        if (itemMap[c]->realName().isEmpty()) {
            itemMap[c]->setRealName(bef);
        }
        itemMap[c]->copiedFrom(bef,rev);
    }
}

TQString SvnLogDlgImp::genReplace(const TQString&r1match)
{
    static TQString anf("<a href=\"");
    static TQString mid("\">");
    static TQString end("</a>");
    TQString res("");
    if (_r2.pattern().length()<1) {
        res = _bugurl;
        res.replace("%BUGID%",_r1.cap(1));
        res = anf+res+mid+r1match+end;
        return res;
    }
    int pos=0;
    int count=0;
    int oldpos;

    kdDebug()<<"Search second pattern: "<<TQString(_r2.pattern())<<" in "<<r1match<<endl;

    while (pos > -1) {
        oldpos = pos+count;
        pos = r1match.find(_r2,pos+count);
        if (pos==-1) {
            break;
        }
        count = _r2.matchedLength();
        res+=r1match.mid(oldpos,pos-oldpos);
        TQString sub = r1match.mid(pos,count);
        TQString _url = _bugurl;
        _url.replace("%BUGID%",sub);
        res+=anf+_url+mid+sub+end;
    }
    res+=r1match.mid(oldpos);
    return res;
}

void SvnLogDlgImp::replaceBugids(TQString&msg)
{
    msg = TQStyleSheet::convertFromPlainText(msg);
    if (!_r1.isValid() || _r1.pattern().length()<1 || _bugurl.isEmpty()) {
        return;
    }
    kdDebug()<<"Try match "<< TQString(_r1.pattern()) << endl;
    int pos = 0;
    int count = 0;

    pos = _r1.search(msg,pos+count);
    count = _r1.matchedLength();

    while (pos>-1) {
        kdDebug()<<"Found at "<<pos << " length "<<count << " with " << TQString(_r1.pattern())<< endl;
        TQString s1 = msg.mid(pos,count);
        kdDebug()<<"Sub: "<<s1 << endl;
        kdDebug()<<TQString(_r1.cap(1)) << endl;
        TQString rep = genReplace(s1);
        kdDebug()<<"Replace with "<<rep << endl;
        msg = msg.replace(pos,count,rep);

        pos = _r1.search(msg,pos+rep.length());
        count = _r1.matchedLength();
    }
}

/*!
    \fn SvnLogDlgImp::slotItemClicked(TQListViewItem*)
 */
void SvnLogDlgImp::slotSelectionChanged(TQListViewItem*_it)
{
    if (!_it) {
        m_DispPrevButton->setEnabled(false);
        buttonListFiles->setEnabled(false);
        buttonBlame->setEnabled(false);
        m_ChangedList->clear();
        return;
    }
    LogListViewItem* k = static_cast<LogListViewItem*>( _it );
    if (k->numChangedEntries()==0) {
        buttonListFiles->setEnabled(true);
        if (m_ChangedList->isVisible()){
            m_ChangedList->hide();
        }
    } else {
        buttonListFiles->setEnabled(false);
        if (!m_ChangedList->isVisible()){
            m_ChangedList->show();
        }
    }
    TQString msg = k->message();
    replaceBugids(msg);
    m_LogDisplay->setText(msg);

    k->showChangedEntries(m_ChangedList);
    buttonBlame->setEnabled(true);

    k = static_cast<LogListViewItem*>(_it->nextSibling());
    if (!k) {
        m_DispPrevButton->setEnabled(false);
    } else {
        m_DispPrevButton->setEnabled(true);
    }
}


/*!
    \fn SvnLogDlgImp::slotDispPrevious()
 */
void SvnLogDlgImp::slotDispPrevious()
{
    LogListViewItem* k = static_cast<LogListViewItem*>(m_LogView->selectedItem());
    if (!k) {
        m_DispPrevButton->setEnabled(false);
        return;
    }
    LogListViewItem* p = static_cast<LogListViewItem*>(k->nextSibling());
    if (!p) {
        m_DispPrevButton->setEnabled(false);
        return;
    }
    TQString s,e;
    s = _base+k->realName();
    e = _base+p->realName();
    emit makeDiff(e,p->rev(),s,k->rev(),this);
}


/*!
    \fn SvnLogDlgImp::saveSize()
 */
void SvnLogDlgImp::saveSize()
{
    int scnum = TQApplication::desktop()->screenNumber(parentWidget());
    TQRect desk = TQApplication::desktop()->screenGeometry(scnum);
    TDEConfigGroupSaver cs(Kdesvnsettings::self()->config(), groupName);
    TQSize sizeToSave = size();
    Kdesvnsettings::self()->config()->writeEntry( TQString::fromLatin1("Width %1").arg( desk.width()),
        TQString::number( sizeToSave.width()), true, false);
    Kdesvnsettings::self()->config()->writeEntry( TQString::fromLatin1("Height %1").arg( desk.height()),
        TQString::number( sizeToSave.height()), true, false);
}

TQSize SvnLogDlgImp::dialogSize()
{
    int w, h;
    int scnum = TQApplication::desktop()->screenNumber(parentWidget());
    TQRect desk = TQApplication::desktop()->screenGeometry(scnum);
    w = sizeHint().width();
    h = sizeHint().height();
    TDEConfigGroupSaver cs(Kdesvnsettings::self()->config(), groupName);
    w = Kdesvnsettings::self()->config()->readNumEntry( TQString::fromLatin1("Width %1").arg( desk.width()), w );
    h = Kdesvnsettings::self()->config()->readNumEntry( TQString::fromLatin1("Height %1").arg( desk.height()), h );
    return( TQSize( w, h ) );
}

void SvnLogDlgImp::slotItemClicked(int button,TQListViewItem*item,const TQPoint &,int)
{
    if (!item) {
        m_ChangedList->clear();
        return;
    }
    LogListViewItem*which = static_cast<LogListViewItem*>(item);
    /* left mouse */
    if (button == 1&&!m_ControlKeyDown) {
        if (m_first) m_first->setText(0,"");
        if (m_first == which) {
            m_first = 0;
        } else {
            m_first = which;
            m_first->setText(0,"1");
        }
        if (m_first==m_second) {
            m_second = 0;
        }
        m_startRevButton->setRevision(which->rev());

    /* other mouse or ctrl hold*/
    } else {
        if (m_second) m_second->setText(0,"");
        if (m_second == which) {
            m_second = 0;
        } else {
            m_second = which;
            m_second->setText(0,"2");
        }
        if (m_first==m_second) {
            m_first = 0;
        }
        m_endRevButton->setRevision(which->rev());
    }
    m_DispSpecDiff->setEnabled(m_first!=0 && m_second!=0);
}

void SvnLogDlgImp::slotRevisionSelected()
{
    m_goButton->setFocus();
    //m_DispSpecDiff->setEnabled( m_first && m_second && m_first != m_second);
}

void SvnLogDlgImp::slotDispSelected()
{
    if (!m_first || !m_second) return;
    emit makeDiff(_base+m_first->realName(),m_first->rev(),_base+m_second->realName(),m_second->rev(),this);
}

bool SvnLogDlgImp::getSingleLog(svn::LogEntry&t,const svn::Revision&r,const TQString&what,const svn::Revision&peg,TQString&root)
{
    root = _base;
    if (m_Entries->find(r.revnum()) == m_Entries->end())
    {
        return m_Actions->getSingleLog(t,r,what,peg,root);
    }
    t=(*m_Entries)[r.revnum()];
    return true;
}

void SvnLogDlgImp::slotGetLogs()
{
    kdDebug()<<"Displog: "<<m_peg.toString()<<endl;
    svn::SharedPointer<svn::LogEntriesMap> lm = m_Actions->getLog(m_startRevButton->revision(),
            m_endRevButton->revision(),m_peg,
            _base+"/"+_name,Kdesvnsettings::self()->log_always_list_changed_files(),0,this);
    if (lm) {
        dispLog(lm);
    }
}

void SvnLogDlgImp::slotListEntries()
{
    LogListViewItem * it = static_cast<LogListViewItem*>(m_LogView->selectedItem());
    if (!it||it->numChangedEntries()>0||!m_Actions) {
        buttonListFiles->setEnabled(false);
        return;
    }
    svn::SharedPointer<svn::LogEntriesMap>_log = m_Actions->getLog(it->rev(),it->rev(),it->rev(),_name,true,0);
    if (!_log) {
        return;
    }
    if (_log->count()>0) {
        it->setChangedEntries((*_log)[it->rev()]);
        it->showChangedEntries(m_ChangedList);
        if (!m_ChangedList->isVisible()) m_ChangedList->show();
    }
    buttonListFiles->setEnabled(false);
}

void SvnLogDlgImp::keyPressEvent (TQKeyEvent * e)
{
    if (!e) return;
    if (e->text().isEmpty()&&e->key()==Key_Control) {
        m_ControlKeyDown = true;
    }
    SvnLogDialogData::keyPressEvent(e);
}

void SvnLogDlgImp::keyReleaseEvent (TQKeyEvent * e)
{
    if (!e) return;
    if (e->text().isEmpty()&&e->key()==TQt::Key_Control) {
        m_ControlKeyDown = false;
    }
    SvnLogDialogData::keyReleaseEvent(e);
}

void SvnLogDlgImp::slotBlameItem()
{
    LogListViewItem* k = static_cast<LogListViewItem*>(m_LogView->selectedItem());
    if (!k) {
        buttonBlame->setEnabled(false);
        return;
    }
    svn::Revision start(svn::Revision::START);
    m_Actions->makeBlame(start,k->rev(),_base+k->realName(),kapp->activeModalWidget(),k->rev(),this);
}

void SvnLogDlgImp::slotEntriesSelectionChanged()
{
}

void SvnLogDlgImp::slotSingleContext(TQListViewItem*_item, const TQPoint & e, int)
{
    if (!_item)
    {
        return;
    }

    LogChangePathItem* item = static_cast<LogChangePathItem*>(_item);
    LogListViewItem* k = static_cast<LogListViewItem*>(m_LogView->selectedItem());
    if (!k) {
        kdDebug()<<"????"<<endl;
        return;
    }
    TQPopupMenu popup;
    TQString name = item->path();
    TQString action = item->action();
    TQString source =item->revision()>-1?item->source():item->path();
    svn_revnum_t prev = item->revision()>0?item->revision():k->rev()-1;
    if (action != "D") {
        popup.insertItem(i18n("Annotate"),101);
        if (action != "A" || item->revision()>-1) {
            popup.insertItem(i18n("Diff previous"),102);
        }
        popup.insertItem(i18n("Cat this version"),103);
    }
    int r = popup.exec(e);
    svn::Revision start(svn::Revision::START);
    switch (r)
    {
        case 101:
        {
            m_Actions->makeBlame(start,k->rev(),_base+name,kapp->activeModalWidget(),k->rev(),this);
            break;
        }
        case 102:
        {
            emit makeDiff(_base+source,prev,_base+name,k->rev(),this);
            break;
        }
        case 103:
        {
            emit makeCat(k->rev(),_base+source,source,k->rev(),kapp->activeModalWidget());
        }
        default:
            break;
    }
}

void SvnLogDlgImp::slotSingleDoubleClicked(TQListViewItem*_item)
{
    if (!_item)
    {
        return;
    }

    LogChangePathItem* item = static_cast<LogChangePathItem*>(_item);
    LogListViewItem* k = static_cast<LogListViewItem*>(m_LogView->selectedItem());
    if (!k) {
        kdDebug()<<"????"<<endl;
        return;
    }
    TQString name = item->path();
    TQString action = item->action();
    TQString source =item->revision()>-1?item->source():item->path();
    //svn_revnum_t prev = item->revision()>0?item->revision():k->rev()-1;
    svn::Revision start(svn::Revision::START);
    if (action != "D") {
        m_Actions->makeBlame(start,k->rev(),_base+name,kapp->activeModalWidget(),k->rev(),this);
    }
}

#include "svnlogdlgimp.moc"
