#include "latexstyleparser.h"

LatexStyleParser::LatexStyleParser(QObject *parent,QString baseDirName,QString kpsecmd) :
    QThread(parent)
{
    baseDir=baseDirName;
    stopped=false;
    kpseWhichCmd=kpsecmd;
    mFiles.clear();
    //check if texdef is present
    texdefDir=kpsecmd.left(kpsecmd.length()-9);
    QProcess myProc(0);
    myProc.start(texdefDir+"texdef");
    myProc.waitForFinished();
    texdefMode=(myProc.exitCode()==1);
    texdefMode=false;
}

void LatexStyleParser::stop(){
	stopped=true;
	mFilesAvailable.release();
}

void LatexStyleParser::run(){
	forever {
		//wait for enqueued lines
		mFilesAvailable.acquire();
		if(stopped && mFiles.count()==0) break;
		mFilesLock.lock();
		QString fn=mFiles.dequeue();
		mFilesLock.unlock();
		QString fullName=kpsewhich(fn); // find file
		if(fullName.isEmpty())
		    continue;

		QStringList results;
		if(texdefMode)
		    results=readPackageTexDef(fn); // parse package(s) by texdef
		else
		    results=readPackage(fullName); // parse package(s)
		
		// if included styles call for additional generation, do it.
		QStringList included=results.filter(QRegExp("#include:.+"));
		foreach(QString elem,included){
			elem=elem.mid(9);
			QString fn=findResourceFile("completion/"+elem+".cwl",false,QStringList(baseDir));
			if(fn.isEmpty()){
				elem=kpsewhich(elem+".sty");
				addFile(elem);
			}
		}
		
		// write results
		if(!results.isEmpty()){
		    QFileInfo info(fn);
		    QString baseName=info.completeBaseName();
		    if(!baseName.isEmpty()){
			QFile data(baseDir+"/"+baseName+".cwl");
			if(data.open(QFile::WriteOnly|QFile::Truncate)){
			    QTextStream out(&data);
			    out << "# autogenerated by txs\n";
			    foreach(const QString& elem,results){
				out << elem << "\n";
			    }
			}
			emit scanCompleted(baseName);
		    }
		}
	}
}

void LatexStyleParser::addFile(QString filename){
	mFilesLock.lock();
	mFiles.enqueue(filename);
	mFilesLock.unlock();
	mFilesAvailable.release();
}

QStringList LatexStyleParser::readPackage(QString fn){
    QFile data(fn);
    QStringList results;
    if(data.open(QFile::ReadOnly)){
        QTextStream stream(&data);
        QString line;
        QRegExp rxDef("\\\\def\\s*(\\\\[\\w@]+)\\s*(#\\d+)?");
        QRegExp rxCom("\\\\(newcommand|providecommand)\\s*\\{(\\\\\\w+)\\}\\s*\\[?(\\d+)?\\]?");
        QRegExp rxCom2("\\\\(newcommand|providecommand)\\s*(\\\\\\w+)\\s*\\[?(\\d+)?\\]?");
        QRegExp rxEnv("\\\\newenvironment\\s*\\{(\\w+)\\}\\s*\\[?(\\d+)?\\]?");
        QRegExp rxInput("\\\\input\\s*\\{?([\\w._]+)");
        QRegExp rxRequire("\\\\RequirePackage\\s*\\{(\\S+)\\}");
        QRegExp rxRequireStart("\\\\RequirePackage\\s*\\{(.+)");
        QRegExp rxDecMathSym("\\\\DeclareMathSymbol\\s*\\{\\\\(\\w+)\\}");
        bool inReq=false;
        while(!stream.atEnd()) {
            line = stream.readLine();
            int options=0;
            if(inReq){
                int col=line.indexOf('}');
                if(col>-1){
                    QString zw=line.left(col);
                    foreach(QString elem,zw.split(',')){
                        QString package=elem.remove(' ');
                        if(!package.isEmpty())
                            results << "#include:" + package;
                    }
                    inReq=false;
                } else {
                    foreach(QString elem,line.split(',')){
                        QString package=elem.remove(' ');
                        if(!package.isEmpty())
                            results << "#include:" + package;
                    }
                }
                continue;
            }
            if(rxDef.indexIn(line)>-1){
                QString name=rxDef.cap(1);
                if(name.contains("@"))
                    continue;
                QString optionStr=rxDef.cap(2);
                //qDebug()<< name << ":"<< optionStr;
                options=optionStr.mid(1).toInt(); //returns 0 if conversion fails
                for (int j=0; j<options; j++) {
                    name.append(QString("{arg%1}").arg(j+1));
                }
                name.append("#S");
                if(!results.contains(name))
                    results << name;
                continue;
            }
            if(rxCom.indexIn(line)>-1){
                QString name=rxCom.cap(2);
                if(name.contains("@"))
                    continue;
                QString optionStr=rxCom.cap(3);
                //qDebug()<< name << ":"<< optionStr;
                options=optionStr.toInt(); //returns 0 if conversion fails
                for (int j=0; j<options; j++) {
                    name.append(QString("{arg%1}").arg(j+1));
                }
                name.append("#S");
                if(!results.contains(name))
                    results << name;
                continue;
            }
            if(rxCom2.indexIn(line)>-1){
                QString name=rxCom2.cap(2);
                if(name.contains("@"))
                    continue;
                QString optionStr=rxCom2.cap(3);
                //qDebug()<< name << ":"<< optionStr;
                options=optionStr.toInt(); //returns 0 if conversion fails
                for (int j=0; j<options; j++) {
                    name.append(QString("{arg%1}").arg(j+1));
                }
                name.append("#S");
                if(!results.contains(name))
                    results << name;
                continue;
            }
            if(rxEnv.indexIn(line)>-1){
                QString name=rxEnv.cap(1);
                if(name.contains("@"))
                    continue;
                QString optionStr=rxEnv.cap(2);
                //qDebug()<< name << ":"<< optionStr;
                QString zw="\\begin{"+name+"}#S";
                if(!results.contains(zw))
                    results << zw;
                zw="\\end{"+name+"}#S";
                if(!results.contains(zw))
                    results << zw;
                continue;
            }
            if(rxInput.indexIn(line)>-1){
                QString name=rxInput.cap(1);
                name=kpsewhich(name);
                results << readPackage(name);
                continue;
            }
            if(rxDecMathSym.indexIn(line)>-1){
                QString name="\\"+rxDecMathSym.cap(1);
                if(name.contains("@"))
                    continue;
                name.append("#Sm");
                if(!results.contains(name))
                    results << name;
                continue;
            }
            if(rxRequire.indexIn(line)>-1){
                QString arg = rxRequire.cap(1);
                foreach(QString elem,arg.split(',')){
                    QString package=elem.remove(' ');
                    if(!package.isEmpty())
                        results << "#include:" + package;
                }
                continue;
            }
            if(rxRequireStart.indexIn(line)>-1){
                QString arg = rxRequireStart.cap(1);
                foreach(QString elem,arg.split(',')){
                    QString package=elem.remove(' ');
                    if(!package.isEmpty())
                        results << "#include:" + package;
                }
                inReq=true;
            }
        } // while line
    } // open data
    return results;
}

QString LatexStyleParser::kpsewhich(QString name){
    QString fn=name;
    if(!kpseWhichCmd.isEmpty()){
	QProcess myProc(0);
	myProc.start(kpseWhichCmd,QStringList(fn));
	myProc.waitForFinished();
	if(myProc.exitCode()==0){
	    fn=myProc.readAllStandardOutput();
	    fn=fn.split('\n').first(); // in case more than one results are present
	}else
	    fn.clear();
    }
    return fn;
}

QStringList LatexStyleParser::readPackageTexDef(QString fn){
    if(!fn.endsWith(".sty"))
	return QStringList();

    QString fname=fn.left(fn.length()-4);
    QProcess myProc(0);
    //add exec search path
    if(!texdefDir.isEmpty()){
	QStringList env = QProcess::systemEnvironment();
	if(env.contains("SHELL=/bin/bash")){
	    env.replaceInStrings(QRegExp("^PATH=(.*)", Qt::CaseInsensitive), "PATH=\\1:"+texdefDir);
	    myProc.setEnvironment(env);
	}
    }
    QStringList args;
    QString result;
    args<<"-t"<<"latex" << "-l" << "-p" <<fname;
    myProc.start(texdefDir+"texdef",args);
    args.clear();
    myProc.waitForFinished();
    if(myProc.exitCode()!=0)
    	return QStringList();

    result=myProc.readAllStandardOutput();
    QStringList lines=result.split('\n');

    bool incl=false;
    for(int i=0;i<lines.length();i++){
	if(lines.at(i).startsWith("Defined")){
	    QString name=lines.at(i);
	    name=name.mid(17);
	    name=name.left(name.length()-2);
	    incl=(name==fn);
	    if(!incl)
		args<<"#include:"+name;
	}
	if(incl && lines.at(i).startsWith("\\"))
	    args<<lines.at(i)+"#S";
    }
    // replace tex env def by latex commands
    QStringList zw=args.filter(QRegExp("\\\\end.+"));
    foreach(const QString& elem,zw){
	QString begin=elem;
	begin.remove(1,3);
	int i=args.indexOf(begin);
	if(i!=-1){
	    QString env=begin.mid(1,begin.length()-3);
	    args.replace(i,"\\begin{"+env+"}");
	    i=args.indexOf(elem);
	    args.replace(i,"\\end{"+env+"}");
	}
    }

    return args;
}
