/*
    Copyright (C)  2008, 2010  Brad Hards <bradh@frogmouth.net>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser 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 Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "rtfreader.h"
#include "controlword.h"
#include "AbstractRtfOutput.h"

#include "AuthorPcdataDestination.h"
#include "CategoryPcdataDestination.h"
#include "ColorTableDestination.h"
#include "CommentPcdataDestination.h"
#include "CompanyPcdataDestination.h"
#include "DocumentCommentPcdataDestination.h"
#include "DocumentDestination.h"
#include "FontTableDestination.h"
#include "GeneratorPcdataDestination.h"
#include "HLinkBasePcdataDestination.h"
#include "IgnoredDestination.h"
#include "InfoDestination.h"
#include "InfoCreatedTimeDestination.h"
#include "InfoPrintedTimeDestination.h"
#include "InfoRevisedTimeDestination.h"
#include "KeywordsPcdataDestination.h"
#include "ManagerPcdataDestination.h"
#include "OperatorPcdataDestination.h"
#include "PictDestination.h"
#include "SubjectPcdataDestination.h"
#include "StyleSheetDestination.h"
#include "TitlePcdataDestination.h"
#include "UserPropsDestination.h"

#include <QStack>
#include <QTextCursor>
#include <QUrl>

namespace RtfReader
{
  Reader::Reader( QObject *parent ): QObject ( parent ),  m_inputDevice( 0 )
  {
  }

  Reader::~Reader()
  {
  }

  bool Reader::open( const QString &filename )
  {
    m_inputDevice = new QFile( filename, this );

    bool result = m_inputDevice->open( QIODevice::ReadOnly );

    return result;
  }

  void Reader::close()
  {
    if ( ! m_inputDevice )
      return;
    m_inputDevice->close();
    delete m_inputDevice;
    m_inputDevice = 0;
  }

  QString Reader::fileName() const
  {
    if ( m_inputDevice && m_inputDevice->exists() ) {
      return m_inputDevice->fileName();
   } else {
      return QString();
    }
  }

  bool Reader::parseTo( AbstractRtfOutput *output )
  {
    if ( ( ! m_inputDevice ) || ( ! m_inputDevice->isOpen() ) ) {
      return false;
    }

    m_output = output;
    
    parseFile();

    return true;
  }

  void Reader::parseFile()
  {
    m_tokenizer = new Tokenizer ( m_inputDevice );

    if ( parseFileHeader() ) {
      parseDocument();
    }

    delete m_tokenizer;
  }

  bool Reader::parseFileHeader()
  {
    bool result = true;

    Token token = m_tokenizer->fetchToken();
    if (token.type != OpenGroup ) {
      qDebug() << "Not an RTF file";
      result = false;
    }

    token = m_tokenizer->fetchToken();
    if ( token.type != Control ) {
      qDebug() << "Not an RTF file - wrong document type";
      result = false;
    }

    if ( ! headerFormatIsKnown( token.name, token.parameter.toInt() ) ) {
      qDebug() << "Not a valid RTF file - unknown header";
      result = false;
    }

    return result;
  }

  bool Reader::headerFormatIsKnown( const QString &tokenName, int tokenValue )
  {
    if ( tokenName != QString ( "rtf" ) ) {
      qDebug() << "unknown / unexpected header token name:" << tokenName;
      return false;
    }

    if ( tokenValue != 1 ) {
      qDebug() << "unknown / unexpected header token value:" << tokenValue;
      return false;
    }

    return true;
  }

    Destination* Reader::makeDestination( const QString &destinationName )
    {
	if ( destinationName == "colortbl" ) {
	  return new ColorTableDestination( this, m_output, destinationName );
	} else if ( destinationName == "creatim" ) {
	  return new InfoCreatedTimeDestination( this, m_output, destinationName );
	} else if ( destinationName == "printim" ) {
	  return new InfoPrintedTimeDestination( this, m_output, destinationName );
	} else if ( destinationName == "revtim" ) {
	  return new InfoRevisedTimeDestination( this, m_output, destinationName );
	} else if ( destinationName == "author" ) {
	  return new AuthorPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "company" ) {
	  return new CompanyPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "operator" ) {
	  return new OperatorPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "comment" ) {
	  return new CommentPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "doccomm" ) {
	  return new DocumentCommentPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "title" ) {
	  return new TitlePcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "subject" ) {
	  return new SubjectPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "manager" ) {
	  return new ManagerPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "category" ) {
	  return new CategoryPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "keywords" ) {
	  return new KeywordsPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "hlinkbase" ) {
	  return new HLinkBasePcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "generator" ) {
	  return new GeneratorPcdataDestination( this, m_output, destinationName );
	} else if ( destinationName == "pict" ) {
	  return new PictDestination( this, m_output, destinationName );
	} else if ( destinationName == "fonttbl" ) {
	  return new FontTableDestination( this, m_output, destinationName );
	} else if ( destinationName == "stylesheet" ) {
	  return new StyleSheetDestination( this, m_output, destinationName );
	} else if ( destinationName == "rtf" ) {
	  return new DocumentDestination( this, m_output, destinationName );
	} else if ( destinationName == "info" ) {
	  return new InfoDestination( this, m_output, destinationName );
	} else if ( destinationName == "userprops" ) {
	  return new UserPropsDestination( this, m_output, destinationName );
	} else if ( destinationName == "ignorable" ) {
	  return new IgnoredDestination( this, m_output, destinationName );
	}
	qDebug() << "creating plain old Destination for" << destinationName;
	return new Destination( this, m_output, destinationName );
    }
    
    void Reader::changeDestination( const QString &destinationName )
    {
	if ( m_destinationStack.top()->name() == "ignorable" ) {
	    // we don't change destinations inside ignored groups
	    return;
	}
        // qDebug() << m_debugIndent << "about to change destination to: " << destinationName;

	Destination *dest = makeDestination( destinationName );

	m_destinationStack.push( dest );
	m_stateStack.top().didChangeDestination = true;
	QStringList destStackElementNames;
	for (int i = 0; i < m_destinationStack.size(); ++i) {
	    destStackElementNames << m_destinationStack.at(i)->name();
	}
	qDebug() << m_debugIndent << "destinationStack after changeDestination (" << destStackElementNames << ")";
    }

    void Reader::parseDocument()
    {
	class RtfGroupState state;

	// Push an end-of-file marker onto the stack
	state.endOfFile = true;
	m_stateStack.push( state );

	// Set up the outer part of the destination stack
	Destination *dest = makeDestination( "rtf" );
	m_destinationStack.push( dest );
	m_stateStack.top().didChangeDestination = true;

	m_debugIndent = QString( '\t' );
	// Parse RTF document
	bool atEndOfFile = false;
	m_nextSymbolMightBeDestination = false;

	RtfReader::ControlWord controlWord("");

	while ( ! atEndOfFile ) {
	    Token token = m_tokenizer->fetchToken();
	    // token.dump();
	    switch ( token.type ) {
	    case OpenGroup: {
		// Store the current state on the stack
		RtfGroupState state;
		m_stateStack.push( state );
		m_nextSymbolMightBeDestination = true;
		m_output->startGroup();
		// qDebug() << m_debugIndent << "opengroup";
		m_debugIndent.append("\t");
		break;
	    }
	    case CloseGroup: {
		QStringList destStackElementNames;
		for (int i = 0; i < m_destinationStack.size(); ++i) {
		    destStackElementNames << m_destinationStack.at(i)->name();
		}
		// qDebug() << m_debugIndent << "closegroup ( destinationStack:" << destStackElementNames << ")";
		m_debugIndent.remove(0, 1);
		state = m_stateStack.pop();
		if ( state.endOfFile ) {
		    atEndOfFile = true;
		} else {
		    m_output->endGroup();
		}

		if ( state.didChangeDestination ) {
		  m_destinationStack.top()->aboutToEndDestination();

		  m_destinationStack.pop();
		}

		destStackElementNames.clear();
		for (int i = 0; i < m_destinationStack.size(); ++i) {
		    destStackElementNames << m_destinationStack.at(i)->name();
		}
		// qDebug() << m_debugIndent << "destinationStack after CloseGroup: (" << destStackElementNames << ")";
		m_nextSymbolMightBeDestination = true;
		break;
	    }
	    case Control:
		controlWord = ControlWord( token.name );
		if ( ! controlWord.isKnown() ) {
		   qDebug() << "*** Unrecognised control word (not in spec 1.9.1): " << token.name;
		}
		// qDebug() << m_debugIndent << "got controlWord: " << token.name;
		// qDebug() << m_debugIndent << "isDestination:" << controlWord.isDestination();
		// qDebug() << m_debugIndent << "isIgnorable:" << m_nextSymbolIsIgnorable;
		if ( m_nextSymbolMightBeDestination && controlWord.isSupportedDestination() ) {
		    m_nextSymbolMightBeDestination = false;
		    m_nextSymbolIsIgnorable = false;
		    changeDestination( token.name );
		} else if ( m_nextSymbolMightBeDestination && m_nextSymbolIsIgnorable ) {
		    // This is a control word we don't understand
		    m_nextSymbolMightBeDestination = false;
		    m_nextSymbolIsIgnorable = false;
		    qDebug() << "ignorable destination word:" << token.name;
		    changeDestination( "ignorable" );
		} else {
		    m_nextSymbolMightBeDestination = false;
		    if ( token.name ==  "*" ) {
			m_nextSymbolMightBeDestination = true;
			m_nextSymbolIsIgnorable = true;
		    }
		    m_destinationStack.top()->handleControlWord( token.name, token.hasParameter, token.parameter.toInt() );
		}
		break;
	    case Plain:
		m_destinationStack.top()->handlePlainText( token.name );
		break;
	    case Binary:
		qDebug() << "binary data:" << token.name;
	    default:
		qDebug() << "Unexpected token Type";
	    }
	}
    }
}

#include "rtfreader.moc"
