/***************************************************************************
 *   Copyright (C) 2006 by Tobias Koenig <tokoe@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.                                   *
 ***************************************************************************/

#include "styleparser.h"

#include <QDateTime>
#include <QFont>
#include <QDomDocument>
#include <QDomElement>
#include <QXmlSimpleReader>

#include <KLocalizedString>

#include "document.h"
#include "styleinformation.h"

using namespace OOO;

StyleParser::StyleParser( const Document *document, const QDomDocument &domDocument, StyleInformation *styleInformation )
  : mDocument( document ), mDomDocument( domDocument ),
    mStyleInformation( styleInformation ), mMasterPageNameSet( false )
{
}

bool StyleParser::parse()
{
  if ( !parseContentFile() )
    return false;

  if ( !parseStyleFile() )
    return false;

  if ( !parseMetaFile() )
    return false;

  return true;
}

bool StyleParser::parseContentFile()
{
  const QDomElement documentElement = mDomDocument.documentElement();
  QDomElement element = documentElement.firstChildElement();
  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "document-common-attrs" ) ) {
      if ( !parseDocumentCommonAttrs( element ) )
        return false;
    } else if ( element.tagName() == QLatin1String( "font-face-decls" ) ) {
      if ( !parseFontFaceDecls( element ) )
        return false;
    } else if ( element.tagName() == QLatin1String( "styles" ) ) {
      if ( !parseStyles( element ) )
        return false;
    } else if ( element.tagName() == QLatin1String( "automatic-styles" ) ) {
      if ( !parseAutomaticStyles( element ) )
        return false;
    }

    element = element.nextSiblingElement();
  }

  return true;
}

bool StyleParser::parseStyleFile()
{
  if ( mDocument->styles().isEmpty() )
    return true;

  QXmlSimpleReader reader;

  QXmlInputSource source;
  source.setData( mDocument->styles() );

  QString errorMsg;
  int errorLine, errorCol;

  QDomDocument document;
  if ( !document.setContent( &source, &reader, &errorMsg, &errorLine, &errorCol ) ) {
    qDebug( "%s at (%d,%d)", qPrintable( errorMsg ), errorLine, errorCol );
    return false;
  }

  const QDomElement documentElement = document.documentElement();
  QDomElement element = documentElement.firstChildElement();
  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "styles" ) ) {
      if ( !parseAutomaticStyles( element ) )
        return false;
    } else if ( element.tagName() == QLatin1String( "automatic-styles" ) ) {
      if ( !parseAutomaticStyles( element ) )
        return false;
    } else if ( element.tagName() == QLatin1String( "master-styles" ) ) {
      if ( !parseMasterStyles( element ) )
        return false;
    }

    element = element.nextSiblingElement();
  }

  return true;
}

bool StyleParser::parseMetaFile()
{
  if ( mDocument->meta().isEmpty() )
    return true;

  QXmlSimpleReader reader;

  QXmlInputSource source;
  source.setData( mDocument->meta() );

  QString errorMsg;
  int errorLine, errorCol;

  QDomDocument document;
  if ( !document.setContent( &source, &reader, &errorMsg, &errorLine, &errorCol ) ) {
    qDebug( "%s at (%d,%d)", qPrintable( errorMsg ), errorLine, errorCol );
    return false;
  }

  const QDomElement documentElement = document.documentElement();
  QDomElement element = documentElement.firstChildElement();
  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "meta" ) ) {
      QDomElement child = element.firstChildElement();
      while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "generator" ) ) {
          mStyleInformation->addMetaInformation( QStringLiteral("producer"), child.text(), i18n( "Producer" ) );
        } else if ( child.tagName() == QLatin1String( "creation-date" ) ) {
          const QDateTime dateTime = QDateTime::fromString( child.text(), Qt::ISODate );
          mStyleInformation->addMetaInformation( QStringLiteral("creationDate"), QLocale().toString( dateTime, QLocale::LongFormat ),
                                                 i18n( "Created" ) );
        } else if ( child.tagName() == QLatin1String( "initial-creator" ) ) {
          mStyleInformation->addMetaInformation( QStringLiteral("creator"), child.text(), i18n( "Creator" ) );
        } else if ( child.tagName() == QLatin1String( "creator" ) ) {
          mStyleInformation->addMetaInformation( QStringLiteral("author"), child.text(), i18n( "Author" ) );
        } else if ( child.tagName() == QLatin1String( "date" ) ) {
          const QDateTime dateTime = QDateTime::fromString( child.text(), Qt::ISODate );
          mStyleInformation->addMetaInformation( QStringLiteral("modificationDate"), QLocale().toString( dateTime, QLocale::LongFormat ),
                                                 i18n( "Modified" ) );
        }

        child = child.nextSiblingElement();
      }
    }

    element = element.nextSiblingElement();
  }

  return true;
}

bool StyleParser::parseDocumentCommonAttrs( QDomElement& )
{
  return true;
}

bool StyleParser::parseFontFaceDecls( QDomElement &parent )
{
  QDomElement element = parent.firstChildElement();
  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "font-face" ) ) {
      FontFormatProperty property;
      property.setFamily( element.attribute( QStringLiteral("font-family") ) );

      mStyleInformation->addFontProperty( element.attribute( QStringLiteral("name") ), property );
    } else {
      qDebug( "unknown tag %s", qPrintable( element.tagName() ) );
    }

    element = element.nextSiblingElement();
  }

  return true;
}

bool StyleParser::parseStyles( QDomElement& )
{
  return true;
}

bool StyleParser::parseMasterStyles( QDomElement &parent )
{
  QDomElement element = parent.firstChildElement();
  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "master-page" ) ) {
      mStyleInformation->addMasterLayout( element.attribute( QStringLiteral("name") ), element.attribute( QStringLiteral("page-layout-name") ) );
      if ( !mMasterPageNameSet ) {
        mStyleInformation->setMasterPageName( element.attribute( QStringLiteral("name") ) );
        mMasterPageNameSet = true;
      }
    } else {
      qDebug( "unknown tag %s", qPrintable( element.tagName() ) );
    }

    element = element.nextSiblingElement();
  }

  return true;
}

bool StyleParser::parseAutomaticStyles( QDomElement &parent )
{
  QDomElement element = parent.firstChildElement();
  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "style" ) ) {
      const StyleFormatProperty property = parseStyleProperty( element );
      mStyleInformation->addStyleProperty( element.attribute( QStringLiteral("name") ), property );
    } else if ( element.tagName() == QLatin1String( "page-layout" ) ) {
      QDomElement child = element.firstChildElement();
      while ( !child.isNull() ) {
        if ( child.tagName() == QLatin1String( "page-layout-properties" ) ) {
          const PageFormatProperty property = parsePageProperty( child );
          mStyleInformation->addPageProperty( element.attribute( QStringLiteral("name") ), property );
        }

        child = child.nextSiblingElement();
      }
    } else if ( element.tagName() == QLatin1String( "list-style" ) ) {
      const ListFormatProperty property = parseListProperty( element );
      mStyleInformation->addListProperty( element.attribute( QStringLiteral("name") ), property );
    } else if ( element.tagName() == QLatin1String( "default-style" ) ) {
      StyleFormatProperty property = parseStyleProperty( element );
      property.setDefaultStyle( true );
      mStyleInformation->addStyleProperty( element.attribute( QStringLiteral("family") ), property );
    } else {
      qDebug( "unknown tag %s", qPrintable( element.tagName() ) );
    }

    element = element.nextSiblingElement();
  }

  return true;
}

StyleFormatProperty StyleParser::parseStyleProperty( QDomElement &parent )
{
  StyleFormatProperty property( mStyleInformation );

  property.setParentStyleName( parent.attribute( QStringLiteral("parent-style-name") ) );
  property.setFamily( parent.attribute( QStringLiteral("family") ) );
  if ( parent.hasAttribute( QStringLiteral("master-page-name") ) ) {
    property.setMasterPageName( parent.attribute( QStringLiteral("master-page-name") ) );
    if ( !mMasterPageNameSet ) {
      mStyleInformation->setMasterPageName( parent.attribute( QStringLiteral("master-page-name") ) );
      mMasterPageNameSet = true;
    }
  }

  QDomElement element = parent.firstChildElement();
  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "paragraph-properties" ) ) {
      const ParagraphFormatProperty paragraphProperty = parseParagraphProperty( element );
      property.setParagraphFormat( paragraphProperty );
    } else if ( element.tagName() == QLatin1String( "text-properties" ) ) {
      const TextFormatProperty textProperty = parseTextProperty( element );
      property.setTextFormat( textProperty );
    } else if ( element.tagName() == QLatin1String( "table-column-properties" ) ) {
      const TableColumnFormatProperty tableColumnProperty = parseTableColumnProperty( element );
      property.setTableColumnFormat( tableColumnProperty );
    } else if ( element.tagName() == QLatin1String( "table-cell-properties" ) ) {
      const TableCellFormatProperty tableCellProperty = parseTableCellProperty( element );
      property.setTableCellFormat( tableCellProperty );
    } else {
      qDebug( "unknown tag %s", qPrintable( element.tagName() ) );
    }

    element = element.nextSiblingElement();
  }

  return property;
}

ParagraphFormatProperty StyleParser::parseParagraphProperty( QDomElement &parent )
{
  ParagraphFormatProperty property;

  property.setPageNumber( parent.attribute( QStringLiteral("page-number") ).toInt() );

  static QMap<QString, ParagraphFormatProperty::WritingMode> map;
  if ( map.isEmpty() ) {
    map.insert( QStringLiteral("lr-tb"), ParagraphFormatProperty::LRTB );
    map.insert( QStringLiteral("rl-tb"), ParagraphFormatProperty::RLTB );
    map.insert( QStringLiteral("tb-rl"), ParagraphFormatProperty::TBRL );
    map.insert( QStringLiteral("tb-lr"), ParagraphFormatProperty::TBLR );
    map.insert( QStringLiteral("lr"), ParagraphFormatProperty::LR );
    map.insert( QStringLiteral("rl"), ParagraphFormatProperty::RL );
    map.insert( QStringLiteral("tb"), ParagraphFormatProperty::TB );
    map.insert( QStringLiteral("page"), ParagraphFormatProperty::PAGE );
  }
  property.setWritingMode( map[ parent.attribute( QStringLiteral("writing-mode") ) ] );

  static QMap<QString, Qt::Alignment> alignMap;
  if ( alignMap.isEmpty() ) {
    alignMap.insert( QStringLiteral("center"), Qt::AlignCenter );
    alignMap.insert( QStringLiteral("left"), Qt::AlignLeft );
    alignMap.insert( QStringLiteral("right"), Qt::AlignRight );
    alignMap.insert( QStringLiteral("justify"), Qt::AlignJustify );
    if ( property.writingModeIsRightToLeft() ) {
      alignMap.insert( QStringLiteral("start"), Qt::AlignRight );
      alignMap.insert( QStringLiteral("end"), Qt::AlignLeft );
    } else {
      // not right to left
      alignMap.insert( QStringLiteral("start"), Qt::AlignLeft );
      alignMap.insert( QStringLiteral("end"), Qt::AlignRight );
    }
  }
  if ( parent.hasAttribute( QStringLiteral("text-align") ) ) {
    property.setTextAlignment( alignMap[ parent.attribute( QStringLiteral("text-align"), QStringLiteral("left") ) ] );
  }

  const QString marginLeft = parent.attribute( QStringLiteral("margin-left") );
  if ( !marginLeft.isEmpty() ) {
    qreal leftMargin = qRound( convertUnit( marginLeft ) );
    property.setLeftMargin( leftMargin );
  }

  const QString colorText = parent.attribute( QStringLiteral("background-color") );
  if ( !colorText.isEmpty() && colorText != QLatin1String( "transparent" ) ) {
    property.setBackgroundColor( QColor( colorText ) );
  }

  return property;
}

TextFormatProperty StyleParser::parseTextProperty( QDomElement &parent )
{
  TextFormatProperty property;

  const QString fontSize = parent.attribute( QStringLiteral("font-size") );
  if ( !fontSize.isEmpty() )
    property.setFontSize( qRound( convertUnit( fontSize ) ) );

  static QMap<QString, QFont::Weight> weightMap;
  if ( weightMap.isEmpty() ) {
    weightMap.insert( QStringLiteral("normal"), QFont::Normal );
    weightMap.insert( QStringLiteral("bold"), QFont::Bold );
  }

  const QString fontWeight = parent.attribute( QStringLiteral("font-weight") );
  if ( !fontWeight.isEmpty() )
    property.setFontWeight( weightMap[ fontWeight ] );

  static QMap<QString, QFont::Style> fontStyleMap;
  if ( fontStyleMap.isEmpty() ) {
    fontStyleMap.insert( QStringLiteral("normal"), QFont::StyleNormal );
    fontStyleMap.insert( QStringLiteral("italic"), QFont::StyleItalic );
    fontStyleMap.insert( QStringLiteral("oblique"), QFont::StyleOblique );
  }

  const QString fontStyle = parent.attribute( QStringLiteral("font-style") );
  if ( !fontStyle.isEmpty() )
    property.setFontStyle( fontStyleMap.value( fontStyle, QFont::StyleNormal ) );

  const QColor color( parent.attribute( QStringLiteral("color") ) );
  if ( color.isValid() ) {
    property.setColor( color );
  }

  const QString colorText = parent.attribute( QStringLiteral("background-color") );
  if ( !colorText.isEmpty() && colorText != QLatin1String( "transparent" ) ) {
    property.setBackgroundColor( QColor( colorText ) );
  }

  return property;
}

PageFormatProperty StyleParser::parsePageProperty( QDomElement &parent )
{
  PageFormatProperty property;

  property.setBottomMargin( convertUnit( parent.attribute( QStringLiteral("margin-bottom") ) ) );
  property.setLeftMargin( convertUnit( parent.attribute( QStringLiteral("margin-left") ) ) );
  property.setTopMargin( convertUnit( parent.attribute( QStringLiteral("margin-top") ) ) );
  property.setRightMargin( convertUnit( parent.attribute( QStringLiteral("margin-right") ) ) );
  property.setWidth( convertUnit( parent.attribute( QStringLiteral("page-width") ) ) );
  property.setHeight( convertUnit( parent.attribute( QStringLiteral("page-height") ) ) );

  return property;
}

ListFormatProperty StyleParser::parseListProperty( QDomElement &parent )
{
  ListFormatProperty property;

  QDomElement element = parent.firstChildElement();
  if ( element.tagName() == QLatin1String( "list-level-style-number" ) )
    property = ListFormatProperty( ListFormatProperty::Number );
  else
    property = ListFormatProperty( ListFormatProperty::Bullet );

  while ( !element.isNull() ) {
    if ( element.tagName() == QLatin1String( "list-level-style-number" ) ) {
      int level = element.attribute( QStringLiteral("level") ).toInt();
      property.addItem( level, 0.0 );
    } else if ( element.tagName() == QLatin1String( "list-level-style-bullet" ) ) {
      int level = element.attribute( QStringLiteral("level") ).toInt();
      property.addItem( level, convertUnit( element.attribute( QStringLiteral("space-before") ) ) );
    }

    element = element.nextSiblingElement();
  }

  return property;
}

TableColumnFormatProperty StyleParser::parseTableColumnProperty( QDomElement &parent )
{
  TableColumnFormatProperty property;

  const double width = convertUnit( parent.attribute( QStringLiteral("column-width") ) );
  property.setWidth( width );

  return property;
}

TableCellFormatProperty StyleParser::parseTableCellProperty( QDomElement &parent )
{
  TableCellFormatProperty property;

  if ( parent.hasAttribute( QStringLiteral("background-color") ) )
    property.setBackgroundColor( QColor( parent.attribute( QStringLiteral("background-color") ) ) );

  property.setPadding( convertUnit( parent.attribute( QStringLiteral("padding") ) ) );

  static QMap<QString, Qt::Alignment> map;
  if ( map.isEmpty() ) {
    map.insert( QStringLiteral("top"), Qt::AlignTop );
    map.insert( QStringLiteral("middle"), Qt::AlignVCenter );
    map.insert( QStringLiteral("bottom"), Qt::AlignBottom );
    map.insert( QStringLiteral("left"), Qt::AlignLeft );
    map.insert( QStringLiteral("right"), Qt::AlignRight );
    map.insert( QStringLiteral("center"), Qt::AlignHCenter );
  }

  if ( parent.hasAttribute( QStringLiteral("align") ) && parent.hasAttribute( QStringLiteral("vertical-align") ) ) {
    property.setAlignment( map[ parent.attribute( QStringLiteral("align") ) ] | map[ parent.attribute( QStringLiteral("vertical-align") ) ] );
  } else if ( parent.hasAttribute( QStringLiteral("align") ) ) {
    property.setAlignment( map[ parent.attribute( QStringLiteral("align") ) ] );
  } else if ( parent.hasAttribute( QStringLiteral("vertical-align") ) ) {
    property.setAlignment( map[ parent.attribute( QStringLiteral("vertical-align") ) ] );
  }

  return property;
}

double StyleParser::convertUnit( const QString &data )
{
  #define MM_TO_POINT(mm) ((mm)*2.83465058)
  #define CM_TO_POINT(cm) ((cm)*28.3465058)
  #define DM_TO_POINT(dm) ((dm)*283.465058)
  #define INCH_TO_POINT(inch) ((inch)*72.0)
  #define PI_TO_POINT(pi) ((pi)*12)
  #define DD_TO_POINT(dd) ((dd)*154.08124)
  #define CC_TO_POINT(cc) ((cc)*12.840103)

  double points = 0;
  if ( data.endsWith( QLatin1String("pt") ) ) {
    points = data.leftRef( data.length() - 2 ).toDouble();
  } else if ( data.endsWith( QLatin1String("cm") ) ) {
    double value = data.leftRef( data.length() - 2 ).toDouble();
    points = CM_TO_POINT( value );
  } else if ( data.endsWith( QLatin1String("mm") ) ) {
    double value = data.leftRef( data.length() - 2 ).toDouble();
    points = MM_TO_POINT( value );
  } else if ( data.endsWith( QLatin1String("dm") ) ) {
    double value = data.leftRef( data.length() - 2 ).toDouble();
    points = DM_TO_POINT( value );
  } else if ( data.endsWith( QLatin1String("in") ) ) {
    double value = data.leftRef( data.length() - 2 ).toDouble();
    points = INCH_TO_POINT( value );
  } else if ( data.endsWith( QLatin1String("inch") ) ) {
    double value = data.leftRef( data.length() - 4 ).toDouble();
    points = INCH_TO_POINT( value );
  } else if ( data.endsWith( QLatin1String("pi") ) ) {
    double value = data.leftRef( data.length() - 4 ).toDouble();
    points = PI_TO_POINT( value );
  } else if ( data.endsWith( QLatin1String("dd") ) ) {
    double value = data.leftRef( data.length() - 4 ).toDouble();
    points = DD_TO_POINT( value );
  } else if ( data.endsWith( QLatin1String("cc") ) ) {
    double value = data.leftRef( data.length() - 4 ).toDouble();
    points = CC_TO_POINT( value );
  } else {
    if ( !data.isEmpty() ) {
      qDebug( "unknown unit for '%s'", qPrintable( data ) );
    }
    points = 12;
  }

  return points;
}
