/* ****************************************************************************
  This file is part of KBabel

  Copyright (C) 1999-2000 by Matthias Kiefer
                            <matthias.kiefer@gmx.de>
		2001-2003 by Stanislav Visnovsky
			    <visnovsky@kde.org>

  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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************************** */

#include "gettextimport.h"

#include <catalogitem.h>
#include <resources.h>

#include <qfile.h>
#include <qfileinfo.h>
#include <qregexp.h>
#include <qtextcodec.h>

#include <kapplication.h>
#include <kdebug.h>
#include <kgenericfactory.h>
#include <klocale.h>

K_EXPORT_COMPONENT_FACTORY( kbabel_gettextimport, KGenericFactory<GettextImportPlugin> )

using namespace KBabel;

GettextImportPlugin::GettextImportPlugin(QObject* parent, const char* name, const QStringList &) : CatalogImportPlugin(parent,name)
{
}

ConversionStatus GettextImportPlugin::load(const QString& filename, const QString&)
{
   if ( filename.isEmpty() )
    kdDebug(KBABEL) << "fatal error: empty filename to open" << endl;

   QFileInfo info(filename);

   if(!info.exists() || info.isDir())
      return NO_FILE;

   if(!info.isReadable())
      return NO_PERMISSIONS;

   QFile file(filename);


   bool recoveredError=false;
   bool docbookContent=false;
   bool docbookFile=false;
   	
   if(file.open(IO_ReadOnly))
   {
      uint oldPercent = 0;
      emit signalResetProgressBar(i18n("loading file"),100);
      
      QByteArray ba = file.readAll();
      
      // find codec for file
	  bool hadCodec;
      QTextCodec* codec=codecForFile(file,&hadCodec);

      file.close();

      QTextStream stream(ba,IO_ReadOnly);
      if(codec)
          stream.setCodec(codec);
      QIODevice *dev = stream.device();
	  int fileSize = dev->size();

      // if somethings goes wrong with the parsing, we don't have deleted the old contents
      CatalogItem tempHeader;
      QStringList tempObsolete;


      kdDebug(KBABEL) << "start parsing..." << endl;

      // first read header
      ConversionStatus status = readHeader(stream);
      
      if(status!=OK)
      {
          emit signalClearProgressBar();
	  
	  if( status == RECOVERED_PARSE_ERROR ) status = HEADER_ERROR;

          return status;
      }
      
      tempHeader.setMsgid( _msgid );
      tempHeader.setMsgstr( _msgstr );
      tempHeader.setComment( _comment );
      if(tempHeader.isFuzzy())
      {
    	     tempHeader.removeFuzzy();
      }

      // check if header seems to indicate docbook content generated by xml2pot
      docbookContent = tempHeader.msgstr().contains("application/x-xml2pot");

      // now parse the rest of the file
      ConversionStatus success=OK;
      uint counter=0;
      QValueList<uint> errorIndex;

      while(!stream.eof() && success==OK )
      {
         kapp->processEvents(10);
	 if( isStopped() )
	 {
	    return STOPPED;
	 }

         success=readEntry(stream);

         if(success==OK)
         {
	    if( _obsolete )
	    {
		success=OK;
		tempObsolete.append(_comment);
	    }
	    else 
	    {
		CatalogItem tempCatItem;
		tempCatItem.setMsgid( _msgid );
		tempCatItem.setMsgstr( _msgstr );
		tempCatItem.setComment( _comment );
		tempCatItem.setGettextPluralForm( _gettextPluralForm );
		
        	// add new entry to the list of entries
        	appendCatalogItem(tempCatItem);
        	// check if first comment seems to indicate a docbook source file
        	if(counter==0)
            	    docbookFile = tempCatItem.comment().contains(".docbook");
	    }
         }
         else if(success==RECOVERED_PARSE_ERROR)
         {
            success=OK;
            recoveredError=true;
            errorIndex.append(counter);
	    
	    CatalogItem tempCatItem;
	    tempCatItem.setMsgid( _msgid );
	    tempCatItem.setMsgstr( _msgstr );
	    tempCatItem.setComment( _comment );
	    tempCatItem.setGettextPluralForm( _gettextPluralForm );

            // add new entry to the list of entries
            appendCatalogItem(tempCatItem);
         }	 
	 counter++;

         if((100*dev->at())/fileSize > oldPercent)
	 {
	    oldPercent = (100*dev->at())/fileSize;
	    emit signalProgress(oldPercent);
	 }
      }


      // to be sure it is set to 100, if someone don't connect to
      // signalClearProgressBar()
      emit signalProgress(100);

      emit signalClearProgressBar();

      kdDebug(KBABEL) << "ready." << endl;

      if(success!=PARSE_ERROR)
      {
         setGeneratedFromDocbook(docbookContent || docbookFile);

         setHeader(tempHeader);
	 setCatalogExtraData(tempObsolete);
         setErrorIndex(errorIndex);

         if(hadCodec)
            setFileCodec(codec);
         else
            setFileCodec(0);
      }
      else
      {
         return PARSE_ERROR;
      }
   }
   else
   {
      return NO_PERMISSIONS;
   }
   
   setMimeTypes( "application/x-gettext" );

   if(recoveredError)
   	return RECOVERED_PARSE_ERROR;

   return OK;
}

QTextCodec* GettextImportPlugin::codecForFile(QFile& file, bool* hadCodec)
{
   bool wasOpen=true;
   int fileIndex=0;
   
   if(hadCodec)
   {
      *hadCodec=false;
   }

   if(!file.isOpen())
   {
      wasOpen=false;

      if(!file.open(IO_ReadOnly))
      {
         kdDebug(KBABEL) << "wasn't able to open file" << endl;
         return 0;
      }
   }
   else
   {
      fileIndex=file.at();
		file.at(0);
   }

   QTextStream stream(&file);

   // first read header
   ConversionStatus status = readHeader(stream);
   if(status!=OK && status != RECOVERED_PARSE_ERROR)
   {
       kdDebug(KBABEL) << "wasn't able to read header" << endl;
       if(!wasOpen)
          file.close();
       return 0;
   }

   QString charset;

   QString head = _msgstr.first();

   QRegExp r("Content-Type:\\s*\\w+/[-\\w]+;\\s*charset\\s*=\\s*[^\\\"\\n]+");
   int begin=r.search(head);
   int len=r.matchedLength();
   if(begin<0) {
   	kdDebug(KBABEL) << "no charset entry found" << endl;
   	return 0;
   }	
   	
   head = head.mid(begin,len);

   QRegExp regexp("charset *= *([^\\\\\\\"]+)");
   if( regexp.search( head ) > -1 )
   {
       charset = regexp.cap(1);
       kdDebug(KBABEL) << QString("charset: ")+charset << " for " << file.name() << endl;
   }

   QTextCodec* codec=0;

   if(!charset.isEmpty())
   {
      // "CHARSET" is the default charset entry in a template (pot).
      // characters in a template should be either pure ascii or 
      // at least utf8, so utf8-codec can be used for both.
      if( charset == "CHARSET")
      {
          if(hadCodec)
             *hadCodec=false;

          codec=QTextCodec::codecForName("utf8");
          kdDebug(KBABEL) 
              << QString("file seems to be a template: using utf8 encoding.")
              << endl;
      }
      else
      {
         codec=QTextCodec::codecForName(charset.latin1());
         if(hadCodec)
            *hadCodec=true;
      }

      if(!codec)
      {
         kdWarning() << "charset found, but no codec available, using UTF8 instead" << endl;
	 codec=QTextCodec::codecForName("utf8");
      }
   }

   if(!wasOpen)
   {
       file.close();
   }
   else
   {
       file.at(fileIndex);
   }

   return codec;
}

ConversionStatus GettextImportPlugin::readHeader(QTextStream& stream)
{
   CatalogItem temp;
   int filePos=stream.device()->at();
   ConversionStatus status=readEntry(stream);

   if(status==OK || status==RECOVERED_PARSE_ERROR)
   {
      // test if this is the header
      if(!_msgid.first().isEmpty())
      {
         stream.device()->at(filePos);
      }

      return status;
   }

   return PARSE_ERROR;
}

ConversionStatus GettextImportPlugin::readEntry(QTextStream& stream)
{
   enum {Begin,Comment,Msgid,Msgstr} part=Begin;

   QString line;
   bool cancelLoop=false;
   bool error=false;
   bool recoverableError=false;
   _msgstr.clear();
   _msgstr.append("");
   _msgid.clear();
   _msgid.append("");
   _comment="";
   _gettextPluralForm=false;
   _obsolete=false;
   
   QStringList::Iterator msgstrIt=_msgstr.begin();
   
   while(!stream.eof() && !cancelLoop)
   {
       int pos=stream.device()->at();

       line=stream.readLine();
       
       if(line.isNull()) // file end
		   break;

       // remove whitespaces from beginning and end of line
       line=line.stripWhiteSpace();

       if(part==Begin)
       {
           // ignore trailing newlines
           if(line.isEmpty())
              continue;

           if(line.contains(QRegExp("^#~")))
           {
              _obsolete=true;
	      part=Comment;
	      _comment=line;
           }
           else if(line.contains(QRegExp("^#")))
           {
               part=Comment;
               _comment=line;
           }
           else if(line.contains(QRegExp("^msgid\\s*\".*\"$")))
           {
               part=Msgid;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgid\\s*\""),"");
               line.replace(QRegExp("\"$"),"");

               (*(_msgid).begin())=line;
           }
		     // one of the quotation marks is missing
           else if(line.contains(QRegExp("^msgid\\s*\"?.*\"?$")))
           {
               part=Msgid;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgid\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               (*(_msgid).begin())=line;

			   if(!line.isEmpty())
			       recoverableError=true;
           }
           else
           {
              kdDebug(KBABEL) << "no comment or msgid found after a comment: " << line << endl;

               cancelLoop=true;
               error=true;
           }
       }
       else if(part==Comment)
       {
            if(line.isEmpty() && _obsolete ) return OK;
	    if(line.isEmpty() )
	       continue;
            else if(line.contains(QRegExp("^#~")))
            {
               _comment+=("\n"+line);
	       _obsolete=true;
            }
            else if(line.contains(QRegExp("^#")))
            {
               _comment+=("\n"+line);
            }
            else if(line.contains(QRegExp("^msgid\\s*\".*\"$")))
            {
               part=Msgid;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgid\\s*\""),"");
               line.replace(QRegExp("\"$"),"");

               (*(_msgid).begin())=line;
            }
			// one of the quotation marks is missing
			else if(line.contains(QRegExp("^msgid\\s*\"?.*\"?$")))
            {
               part=Msgid;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgid\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               (*(_msgid).begin())=line;
			   
			   if(!line.isEmpty())
			       recoverableError=true;
            }
            else
            {
               kdDebug(KBABEL) << "no comment or msgid found after a comment while parsing: " << _comment << endl;

               error=true;
               cancelLoop=true;
            }
        }
        else if(part==Msgid)
        {
            if(line.isEmpty())
               continue;
            else if(line.contains(QRegExp("^\".*\\n?\"$")))
            {
               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^\""),"");
               line.replace(QRegExp("\"$"),"");

               QStringList::Iterator it;
               if(_gettextPluralForm)
                   it = _msgid.fromLast();
               else
                   it = _msgid.begin();
               
               // add Msgid line to item
               if((*it).isEmpty())
                  (*it)=line;
               else
                  (*it)+=("\n"+line);
            }
            else if(line.contains(QRegExp("^msgid_plural\\s*\".*\"$")))
            {
               part=Msgid;
               _gettextPluralForm = true;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgid_plural\\s*\""),"");
               line.replace(QRegExp("\"$"),"");

               _msgid.append(line);
            }
			// one of the quotation marks is missing
			else if(line.contains(QRegExp("^msgid_plural\\s*\"?.*\"?$")))
            {
               part=Msgid;
               _gettextPluralForm = true;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgid_plural\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               _msgid.append(line);
			   
               if(!line.isEmpty())
			       recoverableError=true;
            }
           else if(!_gettextPluralForm 
                   && line.contains(QRegExp("^msgstr\\s*\".*\\n?\"$")))
            {
               part=Msgstr;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgstr\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               (*msgstrIt)=line;
            }
			else if(!_gettextPluralForm
                    && line.contains(QRegExp("^msgstr\\s*\"?.*\\n?\"?$")))
            {
               part=Msgstr;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgstr\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               (*msgstrIt)=line;

			   if(!line.isEmpty())
			       recoverableError=true;
            }
            else if( _gettextPluralForm 
                    && line.contains(QRegExp("^msgstr\\[0\\]\\s*\".*\\n?\"$")))
            {
               part=Msgstr;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgstr\\[0\\]\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               (*msgstrIt)=line;
            }
			else if( _gettextPluralForm 
                    && line.contains(QRegExp("^msgstr\\[0\\]\\s*\"?.*\\n?\"?$")))
            {
               part=Msgstr;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgstr\\[0\\]\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               (*msgstrIt)=line;

			   if(!line.isEmpty())
			       recoverableError=true;
            }
 
            else if(line.contains(QRegExp("^#")) || line.contains(QRegExp("^msgid")))
            {
               kdDebug(KBABEL) << "no msgstr or msgid found after a msgid while parsing: " << _msgid.first() << endl;

               cancelLoop=true;
               error=true;
            }
            // a line of the msgid with a missing quotation mark
            else if(line.contains(QRegExp("^\"?.+\\n?\"?$")))
            {
               recoverableError=true;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^\""),"");
               line.replace(QRegExp("\"$"),"");

               QStringList::Iterator it;
               if( _gettextPluralForm )
                   it = _msgid.fromLast();
               else
                   it = _msgid.begin();
               
               // add Msgid line to item
               if((*it).isEmpty())
                  (*it)=line;
               else
                  (*it)+=("\n"+line);
            }
            else
            {
               kdDebug(KBABEL) << "no msgstr or msgid found after a msgid while parsing: " << _msgid.first() << endl;

               cancelLoop=true;
               error=true;
            }
        }
        else if(part==Msgstr)
        {
            if(line.isEmpty())
               continue;
            // another line of the msgstr
            else if(line.contains(QRegExp("^\".*\\n?\"$")))
            {
               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^\""),"");
               line.replace(QRegExp("\"$"),"");

               if((*msgstrIt).isEmpty())
                  (*msgstrIt)=line;
               else
                  (*msgstrIt)+=("\n"+line);
            }
            else if( _gettextPluralForm
                    && line.contains(QRegExp("^msgstr\\[[0-9]+\\]\\s*\".*\\n?\"$")))
            {
               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgstr\\[[0-9]+\\]\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               msgstrIt=_msgstr.append(line);
            }
	    else if( _gettextPluralForm 
                    && line.contains(QRegExp("^msgstr\\[[0-9]\\]\\s*\"?.*\\n?\"?$")))
            {
               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^msgstr\\[[0-9]\\]\\s*\"?"),"");
               line.replace(QRegExp("\"$"),"");

               msgstrIt=_msgstr.append(line);

			   if(!line.isEmpty())
			       recoverableError=true;
            }
            else if(line.contains(QRegExp("^\\s*msgid")) || line.contains(QRegExp("^\\s*#")))
            {
               cancelLoop=true;
               stream.device()->at(pos);// reset position in stream to beginning of this line
               break;
            }
            else if(line.contains(QRegExp("^msgstr")))
            {
               kdDebug(KBABEL) << "no msgid or comment found after a msgstr while parsing: " << _msgstr.last() << endl;

               cancelLoop=true;
               error=true;
            }
            // another line of the msgstr with a missing quotation mark
            else if(line.contains(QRegExp("^\"?.+\\n?\"?$")))
            {
               recoverableError=true;

               // remove quotes at beginning and the end of the lines
               line.replace(QRegExp("^\""),"");
               line.replace(QRegExp("\"$"),"");

               if((*msgstrIt).isEmpty())
                  (*msgstrIt)=line;
               else
                  (*msgstrIt)+=("\n"+line);
            }
            else
            {
               kdDebug(KBABEL) << "no msgid or comment found after a msgstr while parsing: " << _msgstr.last() << endl;

               cancelLoop=true;
               error=true;
            }
        }
    }
/*
   if(_gettextPluralForm)
   {
       kdDebug(KBABEL) << "gettext plural form:\n"
                 << "msgid:\n" << _msgid.first() << "\n"
                 << "msgid_plural:\n" << _msgid.last() << "\n" << endl;
       int counter=0;
       for(QStringList::Iterator it = _msgstr.begin(); it != _msgstr.end(); ++it)
       {
           kdDebug(KBABEL) << "msgstr[" << counter << "]:\n" 
                     << (*it) << endl;
           counter++;
       }
   }
  */ 
    if(error)
       return PARSE_ERROR;
	else if(recoverableError)
		return RECOVERED_PARSE_ERROR;
    else
    {
      return OK;
    }
}
