/****************************************************************************
**
** Copyright (C) 2003-2006 Tilo Riemer <riemer@crossvc.com>,
**                         Frank Hemer <frank@hemer.org>
**
**
**----------------------------------------------------------------------------
**
**----------------------------------------------------------------------------
**
** CrossVC is available under two different licenses:
**
** If CrossVC is linked against the GPLed version of Qt 
** CrossVC is released under the terms of GPL also.
**
** If CrossVC is linked against a nonGPLed version of Qt 
** CrossVC is released under the terms of the 
** CrossVC License for non-Unix platforms (CLNU)
**
**
** CrossVC License for non-Unix platforms (CLNU):
**
** Redistribution and use in binary form, without modification, 
** are permitted provided that the following conditions are met:
**
** 1. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 2. It is not permitted to distribute the binary package under a name
**    different than CrossVC.
** 3. The name of the authors may not be used to endorse or promote
**    products derived from this software without specific prior written
**    permission.
** 4. The source code is the creative property of the authors.
**    Extensions and development under the terms of the Gnu Public License
**    are limited to the Unix platform. Any distribution or compilation of 
**    the source code against libraries licensed other than gpl requires 
**    the written permission of the authors.
**
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
** WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
** ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 
** DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 
** GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
** WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**
**
**
** CrossVC License for Unix platforms:
**
** 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, version 2 of the License.
** 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 version 2 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
**
*****************************************************************************/

#include "config.h"

//----------------------------------------------------------------------------

#include <qfont.h>
#include <qregexp.h>

//----------------------------------------------------------------------------

#include "globals.h"
#include "SyntaxHighlighter.h"

//----------------------------------------------------------------------------

int CSyntaxHighlighter::highlightParagraph(QString & text, int endStateOfLastPara)
{
   if ( ( (text.startsWith("cvs [")) || (text.startsWith("cvsnt [")) ) && (text.find("aborted]:") > 4) ) {
      replaceTags(text);
      setHighlightFormat(text, HighlightProperties::g_scmErr, 0, text.length());
      return 0;
   }
	
   switch (m_mode) {
      case SyntaxHighlighter::HM_NONE:
	 replaceTags(text);
	 //nothing todo
	 return 0;
      case SyntaxHighlighter::HM_UPDATE:
	 return update(text, endStateOfLastPara);
      case SyntaxHighlighter::HM_DIFF:
	 return diff(text, endStateOfLastPara);
      case SyntaxHighlighter::HM_DIFFSBS:
	 return diffSideBySide(text, endStateOfLastPara);
      case SyntaxHighlighter::HM_STATUS: {
	 return status(text, endStateOfLastPara);
      }
      case SyntaxHighlighter::HM_LOGIN:
	 return login(text, endStateOfLastPara);
   }

   return 0;   //should never reach this line
}

//----------------------------------------------------------------------------

int CSyntaxHighlighter::highlightLine(QString & text, int state) {

   int pos;
   unsigned int npos = 0;
   while ( (pos = text.find('\n',npos)) > -1) {
      QString txt = text.mid(npos,pos-npos);
      state = static_cast<SyntaxHighlighter::HighlightState>(highlightParagraph(txt,state));
      text.replace(npos,pos-npos,txt);
      npos = pos+1 +txt.length()-(pos-npos);
   }
   return state;
}

//----------------------------------------------------------------------------

int CSyntaxHighlighter::replaceTags(QString & txt, int from /*=0*/, int len /*=-1*/) {
   int deltaLen;
   if ( (from > 0) || (len > -1) ) {
      if (len == -1) len = txt.length()-from;
      QString fullTxt = txt;
      txt = txt.mid(from,len);
      txt.replace(QRegExp("&"),"&amp;");
      txt.replace(QRegExp("<"),"&lt;");
      txt.replace(QRegExp(">"),"&gt;");
      deltaLen = txt.length();
      txt = fullTxt.left(from)+txt+fullTxt.mid(from+len);
   } else {
      txt.replace(QRegExp("&"),"&amp;");
      txt.replace(QRegExp("<"),"&lt;");
      txt.replace(QRegExp(">"),"&gt;");
      deltaLen = txt.length();
   }
   return deltaLen;
}

//----------------------------------------------------------------------------

void CSyntaxHighlighter::setMode(SyntaxHighlighter::HighlightMode mode)
{
   if (HighlightProperties::g_bUseSyntaxHighlighting) {
      m_mode = mode;
   } else {
      m_mode = SyntaxHighlighter::HM_NONE;   //no syntax highlighting
   }
}

//----------------------------------------------------------------------------

int CSyntaxHighlighter::update(QString & text, int)
{
   replaceTags(text);
   if ((text[0] == '?') && (text[1] == ' ')) {//?
      setHighlightFormat(text, HighlightProperties::g_updateQuestionMark, 0, text.length());
   } else if ((text[0] == 'M') && (text[1] == ' ')) {
      setHighlightFormat(text, HighlightProperties::g_updateModified, 0, text.length());
   } else if (( (text[0] == 'U') || (text[0] == 'P')) && (text[1] == ' ')) {
      setHighlightFormat(text, HighlightProperties::g_updateNeedsUpdate, 0, text.length());
   } else if ( ((text[0] == 'C') && (text[1] == ' ')) ||
	 (text.startsWith("rcsmerge: warning: conflicts during merge")) ||
	 (text.startsWith("cvs server: conflicts found")) ) {
      setHighlightFormat(text, HighlightProperties::g_updateConflict, 0, text.length());
   } else if (text.startsWith("Merging differences between")) {
      setHighlightFormat(text, HighlightProperties::g_updateMerging, 0, text.length());
   } else if ((text[0] == 'A') && (text[1] == ' ')) {
      setHighlightFormat(text, HighlightProperties::g_updateAdded, 0, text.length());
   } else if ((text[0] == 'R') && (text[1] == ' ')) {
      setHighlightFormat(text, HighlightProperties::g_updateRemoved, 0, text.length());
   } else if ( (text.startsWith("cvs server:")) &&
	 ((text.find("is not (any longer) pertinent" ) > 0) ||
	       (text.find("is no longer in the repository") > 0)) ) {
      setHighlightFormat(text, HighlightProperties::g_updateRemovedByOther, 0, text.length());
   }
   
   return 0;   
}

//----------------------------------------------------------------------------

int CSyntaxHighlighter::diff(QString & text, int)
{
   if ((text[0] == '<') && (text[1] == ' ')) {//old
      replaceTags(text);
      setHighlightFormat(text, HighlightProperties::g_diffOld, 0, text.length());
   } else if ((text[0] == '>')  && (text[1] == ' ')) {//new
      replaceTags(text);
      setHighlightFormat(text, HighlightProperties::g_diffNew, 0, text.length());
   }

   return 0;
}

//----------------------------------------------------------------------------

#define COLUMNWIDTH ((SIDEWIDTH-3)>>1)
#define DELIMITERWIDTH (SIDEWIDTH-(COLUMNWIDTH<<1))

int CSyntaxHighlighter::diffSideBySide(QString & text, int)
{
   int pos = COLUMNWIDTH + 1;
   QChar marker = text.at(pos);

   int leftLen = replaceTags(text,0,COLUMNWIDTH);
   int delimiterLen = replaceTags(text,leftLen,DELIMITERWIDTH);
   int rightLen = replaceTags(text,leftLen+delimiterLen);

   switch(marker) {
      case '|': {
         int addedLen = setHighlightFormat(text, HighlightProperties::g_diffOld, 0, leftLen);
         setHighlightFormat(text, HighlightProperties::g_diffNew, leftLen+delimiterLen+addedLen, rightLen);
         break;
      }
      case '>': {
         setHighlightFormat(text, HighlightProperties::g_updateAdded, leftLen+delimiterLen, rightLen );
         break;
      }
      case '<': {
         setHighlightFormat(text, HighlightProperties::g_updateRemoved, 0, leftLen);
         break;
      }
      default: {
         
      }
   }
   return 0;
}

//----------------------------------------------------------------------------

int CSyntaxHighlighter::status(QString & text, int endStateOfLastPara)
{
   replaceTags(text);
   if ((text[0] == '?') && (text[1] == ' ')) {//?
      setHighlightFormat(text, HighlightProperties::g_updateQuestionMark, 0, text.length());
      return 0;
   }

   if ((text[0] == '=') && (text[1] == '=')) {//start of next block reached
      return 0;
   }

   if (text.startsWith("cvs server:")) {//end of block reached
      return 0;
   }

   switch (endStateOfLastPara) {
      case SyntaxHighlighter::HS_UPTODATE:
      return SyntaxHighlighter::HS_UPTODATE;
      
      case SyntaxHighlighter::HS_MODIFIED:
      setHighlightFormat(text, HighlightProperties::g_updateModified, 0, text.length());
      return SyntaxHighlighter::HS_MODIFIED;
      
      case SyntaxHighlighter::HS_NEEDS_UPDATE:
      setHighlightFormat(text, HighlightProperties::g_updateNeedsUpdate, 0, text.length());
      return SyntaxHighlighter::HS_NEEDS_UPDATE;
      
      case SyntaxHighlighter::HS_NEEDS_MERGE:
      setHighlightFormat(text, HighlightProperties::g_updateMerging, 0, text.length());
      return SyntaxHighlighter::HS_NEEDS_MERGE;
      
      case SyntaxHighlighter::HS_CONFLICT:
      setHighlightFormat(text, HighlightProperties::g_updateConflict, 0, text.length());
      return SyntaxHighlighter::HS_CONFLICT;
      
      case SyntaxHighlighter::HS_ADDED:
      setHighlightFormat(text, HighlightProperties::g_updateAdded, 0, text.length());
      return SyntaxHighlighter::HS_ADDED;
      
      case SyntaxHighlighter::HS_REMOVED:
      setHighlightFormat(text, HighlightProperties::g_updateRemoved, 0, text.length());
      return SyntaxHighlighter::HS_REMOVED;
   }
   
   if (text.left(5) == "File:") {//line containing the state info found
      if (text.find("Locally Modified") > 5) {
         setHighlightFormat(text, HighlightProperties::g_updateModified, 0, text.length());
         return SyntaxHighlighter::HS_MODIFIED;
      }
      if ((text.find("Needs Patch") > 5) || (text.find("Needs Checkout") > 5)) {
         setHighlightFormat(text, HighlightProperties::g_updateNeedsUpdate, 0, text.length());
         return SyntaxHighlighter::HS_NEEDS_UPDATE;
      }
      if (text.find("Needs Merge") > 5) {
         setHighlightFormat(text, HighlightProperties::g_updateMerging, 0, text.length());
         return SyntaxHighlighter::HS_NEEDS_MERGE;
      }
      if (text.find("File had conflicts on merge") > 5) {
         setHighlightFormat(text, HighlightProperties::g_updateConflict, 0, text.length());
         return SyntaxHighlighter::HS_CONFLICT;
      }
      if (text.find("Locally Added") > 5) {
         setHighlightFormat(text, HighlightProperties::g_updateAdded, 0, text.length());
         return SyntaxHighlighter::HS_ADDED;
      }
      if (text.find("Locally Removed") > 5) {
         setHighlightFormat(text, HighlightProperties::g_updateRemoved, 0, text.length());
         return SyntaxHighlighter::HS_REMOVED;
      }
   }

   return 0;
}

int CSyntaxHighlighter::login(QString & text, int endStateOfLastPara) {

   replaceTags(text);
   switch(endStateOfLastPara) {
      case 1: {
         setHighlightFormat(text, HighlightProperties::g_updateModified, 0, text.length() );
         return 1;
      }
      case 2: {
         setHighlightFormat(text, HighlightProperties::g_updateNeedsUpdate, 0, text.length() );
         return 2;
      }
      case 3: {
         setHighlightFormat(text, HighlightProperties::g_updateConflict, 0, text.length() );
         return 3;
      }
      default: {
         if (text.startsWith("trying to login:")) {
            setHighlightFormat(text, HighlightProperties::g_updateNeedsUpdate, 0, text.length() );
            return 0;
         } else if (text.startsWith("(HTTP tunneling through ")) {
            setHighlightFormat(text, HighlightProperties::g_updateNeedsUpdate, 0, text.length() );
            return 0;
         } else if (text.startsWith("Action interrupted by user")) {
            setHighlightFormat(text, HighlightProperties::g_updateModified, 0, text.length() );
            return 0;
         } else if (text.find("authentication failed:") > -1) {
            setHighlightFormat(text, HighlightProperties::g_updateModified, 0, text.length() );
            return 1;
         } else if (text.find("login succeeded:") > -1) {
            setHighlightFormat(text, HighlightProperties::g_updateNeedsUpdate, 0, text.length() );
            return 2;
         } else if (text.startsWith("Login:")) {
            if (text.find("not found") > -1) {
               setHighlightFormat(text, HighlightProperties::g_updateModified, 0, text.length() );
               return 1;
            } else {
               setHighlightFormat(text, HighlightProperties::g_updateNeedsUpdate, 0, text.length() );
               return 2;
            }
         } else if (text.stripWhiteSpace().isEmpty() ) {
            return endStateOfLastPara;
         } else {
            setHighlightFormat(text, HighlightProperties::g_updateConflict, 0, text.length() );
            return 3;
         }
      }
   }
}

//----------------------------------------------------------------------------

int CSyntaxHighlighter::setHighlightFormat(QString & text, HighlightProperties::Properties prop,
      int start, int count)
{
   int addLen = 0;
   if (prop.style != HighlightProperties::FS_NORMAL) {
      //font and color
      if (prop.style & HighlightProperties::FS_BOLD) {
         text.insert(start+count,"</b>");
         text.insert(start,"<b>");
         addLen += 7;
      }
      if (prop.style & HighlightProperties::FS_ITALIC) {
         text.insert(start+count+addLen,"</i>");
         text.insert(start,"<i>");
         addLen += 7;
      }
   }

   //calc color
   QString hexCol = getHexCol(prop.color);

   text.insert(start+count+addLen,"</font>");
   text.insert(start,"<font color=#"+hexCol+">");
   addLen += 27;

   return addLen;
}

//----------------------------------------------------------------------------

QString CSyntaxHighlighter::getHexCol(const QColor & col) {

   int colVal[6];

   colVal[1] = col.red() % 16;
   colVal[0] = (col.red() - colVal[1]) / 16;
   colVal[3] = col.green() % 16;
   colVal[2] = (col.green() - colVal[3]) / 16;
   colVal[5] = col.blue() % 16;
   colVal[4] = (col.blue() - colVal[5]) / 16;

   QString hx;
   int i;
   for (i=0; i<6; ++i) {
      switch (colVal[i]) {
         case 15:
         hx += "f";
         break;
         case 14:
         hx += "e";
         break;
         case 13:
         hx += "d";
         break;
         case 12:
         hx += "c";
         break;
         case 11:
         hx += "b";
         break;
         case 10:
         hx += "a";
         break;
         default: hx += QString::number(colVal[i]);
      }
   }
   return hx;
}

//----------------------------------------------------------------------------



syntax highlighted by Code2HTML, v. 0.9.1