//  XmlUtil.cpp
//
//  Copyright (C) 2013-2016  Jaye Evins <evins@snaught.com>
//
//  This file is part of gLabels-qt.
//
//  gLabels-qt 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 3 of the License, or
//  (at your option) any later version.
//
//  gLabels-qt 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 gLabels-qt.  If not, see <http://www.gnu.org/licenses/>.
//


#include "XmlUtil.hpp"

#include <QTextStream>
#include <QCoreApplication>
#include <QDebug>
#include <QIODevice>


namespace glabels::model
{

        //
        // Static data
        //
        Units XmlUtil::mUnits = Units(Units::PT);


        Units XmlUtil::units()
        {
                return mUnits;
        }


        void XmlUtil::setUnits( Units units )
        {
                mUnits = units;
        }


        QString XmlUtil::getStringAttr( const QDomElement& node,
                                        const QString&     name,
                                        const QString&     default_value )
        {
                return node.attribute( name, default_value );
        }


        double XmlUtil::getDoubleAttr( const QDomElement& node,
                                       const QString&     name,
                                       double             default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        bool ok;
                        double value = valueString.toDouble( &ok );

                        if ( !ok )
                        {
                                qWarning() << "Error: bad double value in attribute "
                                           << node.tagName() << ":" << name << "=" << valueString;
                                return default_value;
                        }

                        return value;
                }

                return default_value;
        }


        bool XmlUtil::getBoolAttr( const QDomElement& node,
                                   const QString&     name,
                                   bool               default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        bool ok;
                        int intValue = valueString.toInt( &ok );

                        if ( (valueString == "True") ||
                             (valueString == "TRUE") ||
                             (valueString == "true") ||
                             (ok && (intValue == 1) ) )
                        {
                                return true;
                        }

                        if ( (valueString == "False") ||
                             (valueString == "FALSE") ||
                             (valueString == "false") ||
                             (ok && (intValue == 0) ) )
                        {
                                return false;
                        }

                        qWarning() << "Error: bad boolean value in attribute "
                                   << node.tagName() << ":" << name << "=" << valueString;
                        return default_value;
                }

                return default_value;
        }


        int XmlUtil::getIntAttr( const QDomElement& node,
                                 const QString&     name,
                                 int                default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        bool ok;
                        int value = valueString.toInt( &ok );

                        if ( !ok )
                        {
                                qWarning() << "Error: bad integer value in attribute "
                                           << node.tagName() << ":" << name << "=" << valueString;
                                return default_value;
                        }

                        return value;
                }

                return default_value;
        }


        uint32_t XmlUtil::getUIntAttr( const QDomElement& node,
                                       const QString&     name,
                                       uint32_t           default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        bool ok;
                        uint32_t value = valueString.toUInt( &ok, 0 );

                        if ( !ok )
                        {
                                qWarning() << "Error: bad unsigned integer value in attribute "
                                           << node.tagName() << ":" << name << "=" << valueString;
                                return default_value;
                        }

                        return value;
                }

                return default_value;
        }


        QString  XmlUtil::getI18nAttr( const QDomElement& node,
                                       const QString&     name,
                                       const QString&     default_value )
        {
                QString i18nString = node.attribute( QString("_").append(name), "" );

                if ( i18nString == "" )
                {
                        return node.attribute( name, default_value );
                }

                return QCoreApplication::translate( "XmlStrings", i18nString.toUtf8().constData() );
        }


        Distance XmlUtil::getLengthAttr( const QDomElement& node,
                                         const QString&     name,
                                         Distance           default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        double value;
                        QString unitsString;
                        QTextStream valueStream( &valueString, QIODevice::ReadOnly );

                        valueStream >> value >> unitsString;

                        if ( !unitsString.isEmpty() && !Units::isIdValid( unitsString ) )
                        {
                                qWarning() << "Error: bad length value in attribute "
                                           << node.tagName() << ":" <<  name << "=" << valueString;
                                return default_value;
                        }

                        return Distance( value, unitsString );
                }

                return default_value;
        }


        QFont::Weight XmlUtil::getWeightAttr( const QDomElement& node,
                                              const QString&     name,
                                              QFont::Weight      default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        if ( valueString == "bold" )
                        {
                                return QFont::Bold;
                        }
                        else if ( valueString == "normal" )
                        {
                                return QFont::Normal;
                        }
                        else
                        {
                                qWarning() << "Error: bad weight value in attribute "
                                           << node.tagName() << ":" <<  name << "=" << valueString;
                                return default_value;
                        }
                }

                return default_value;
        }


        Qt::Alignment XmlUtil::getAlignmentAttr( const QDomElement& node,
                                                 const QString&     name,
                                                 Qt::Alignment      default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        if ( valueString == "right" )
                        {
                                return Qt::AlignRight;
                        }
                        else if ( valueString == "hcenter" )
                        {
                                return Qt::AlignHCenter;
                        }
                        else if ( valueString == "left" )
                        {
                                return Qt::AlignLeft;
                        }
                        else if ( valueString == "bottom" )
                        {
                                return Qt::AlignBottom;
                        }
                        else if ( valueString == "vcenter" )
                        {
                                return Qt::AlignVCenter;
                        }
                        else if ( valueString == "top" )
                        {
                                return Qt::AlignTop;
                        }
                        else
                        {
                                qWarning() << "Error: bad alignment value in attribute "
                                           << node.tagName() << ":" <<  name << "=" << valueString;
                                return default_value;
                        }
                }

                return default_value;
        }


        QTextOption::WrapMode XmlUtil::getWrapModeAttr( const QDomElement&    node,
                                                        const QString&        name,
                                                        QTextOption::WrapMode default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        if ( valueString == "word" )
                        {
                                return QTextOption::WordWrap;
                        }
                        else if ( valueString == "anywhere" )
                        {
                                return QTextOption::WrapAnywhere;
                        }
                        else if ( valueString == "none" )
                        {
                                return QTextOption::NoWrap;
                        }
                        else
                        {
                                qWarning() << "Error: bad wrap mode value in attribute "
                                           << node.tagName() << ":" <<  name << "=" << valueString;
                                return default_value;
                        }
                }

                return default_value;
        }


        Units XmlUtil::getUnitsAttr( const QDomElement& node,
                                     const QString&     name,
                                     Units              default_value )
        {
                QString valueString = node.attribute( name, "" );
                if ( valueString != "" )
                {
                        return Units( valueString );
                }

                return default_value;
        }


        QPainterPath XmlUtil::getPathDataAttr( const QDomElement& node,
                                               const QString&     name,
                                               Units              units )
        {
                QPainterPath d;

                //
                // Simple path data parser
                //
                QStringList tokens = node.attribute( name, "" ).split( " ", Qt::SkipEmptyParts );

                enum { CMD, MX, MY, MDX, MDY, LX, LY, LDX, LDY, HX, HDX, VY, VDY } state = CMD;
                Distance x  = 0;
                Distance y  = 0;
                Distance dx = 0;
                Distance dy = 0;
                QPointF  c;

                for ( int i = 0; i < tokens.size(); i++ )
                {
                        switch (state)
                        {
                        case CMD:
                                switch (tokens[i][0].unicode())
                                {
                                case 'M':
                                        state = MX;
                                        break;
                                case 'm':
                                        state = MDX;
                                        break;
                                case 'L':
                                        state = LX;
                                        break;
                                case 'l':
                                        state = LDX;
                                        break;
                                case 'H':
                                        state = HX;
                                        break;
                                case 'h':
                                        state = HDX;
                                        break;
                                case 'V':
                                        state = VY;
                                        break;
                                case 'v':
                                        state = VDY;
                                        break;
                                case 'Z':
                                case 'z':
                                        d.closeSubpath();
                                        state = CMD;
                                        break;
                                }
                                break;
                        case MX:
                                x = Distance( tokens[i].toDouble(), units );
                                state = MY;
                                break;
                        case MY:
                                y = Distance( tokens[i].toDouble(), units );
                                d.moveTo( x.pt(), y.pt() );
                                state = CMD;
                                break;
                        case MDX:
                                dx = Distance( tokens[i].toDouble(), units );
                                state = MDY;
                                break;
                        case MDY:
                                dy = Distance( tokens[i].toDouble(), units );
                                c = d.currentPosition();
                                d.moveTo( c.x()+x.pt(), c.y()+y.pt() );
                                state = CMD;
                                break;
                        case LX:
                                x = Distance( tokens[i].toDouble(), units );
                                state = LY;
                                break;
                        case LY:
                                y = Distance( tokens[i].toDouble(), units );
                                d.lineTo( x.pt(), y.pt() );
                                state = CMD;
                                break;
                        case LDX:
                                dx = Distance( tokens[i].toDouble(), units );
                                state = LDY;
                                break;
                        case LDY:
                                dy = Distance( tokens[i].toDouble(), units );
                                c = d.currentPosition();
                                d.lineTo( c.x()+dx.pt(), c.y()+dy.pt() );
                                state = CMD;
                                break;
                        case HX:
                                x = Distance( tokens[i].toDouble(), units );
                                c = d.currentPosition();
                                d.lineTo( x.pt(), c.y() );
                                state = CMD;
                                break;
                        case HDX:
                                dx = Distance( tokens[i].toDouble(), units );
                                c = d.currentPosition();
                                d.lineTo( c.x()+dx.pt(), c.y() );
                                state = CMD;
                                break;
                        case VY:
                                y = Distance( tokens[i].toDouble(), units );
                                c = d.currentPosition();
                                d.lineTo( c.x(), y.pt() );
                                state = CMD;
                                break;
                        case VDY:
                                dy = Distance( tokens[i].toDouble(), units );
                                c = d.currentPosition();
                                d.lineTo( c.x(), c.y()+dy.pt() );
                                state = CMD;
                                break;
                        }
                }

                return d;
        }


        void XmlUtil::setStringAttr( QDomElement&   node,
                                     const QString& name,
                                     const QString& value )
        {
                node.setAttribute( name, value );
        }


        void XmlUtil::setDoubleAttr( QDomElement&   node,
                                     const QString& name,
                                     double         value )
        {
                node.setAttribute( name, QString::number(value) );
        }


        void XmlUtil::setBoolAttr( QDomElement&   node,
                                   const QString& name,
                                   bool           value )
        {
                node.setAttribute( name, value ? "true" : "false" );
        }


        void XmlUtil::setIntAttr( QDomElement&   node,
                                  const QString& name,
                                  int            value )
        {
                node.setAttribute( name, QString::number(value) );
        }


        void XmlUtil::setUIntAttr( QDomElement&   node,
                                   const QString& name,
                                   uint32_t       value )
        {
                node.setAttribute( name, "0x" + QString::number(value, 16) );
        }


        void XmlUtil::setLengthAttr( QDomElement&    node,
                                     const QString&  name,
                                     Distance        value )
        {
                node.setAttribute( name, QString::number(value.inUnits(mUnits)) + mUnits.toIdString() );
        }


        void XmlUtil::setWeightAttr( QDomElement&    node,
                                     const QString&  name,
                                     QFont::Weight   value )
        {
                switch (value)
                {
                case QFont::Bold:
                        node.setAttribute( name, "bold" );
                        break;
                default:
                        node.setAttribute( name, "normal" );
                        break;
                }
        }


        void XmlUtil::setAlignmentAttr( QDomElement&    node,
                                        const QString&  name,
                                        Qt::Alignment   value )
        {
                switch (value)
                {
                case Qt::AlignRight:
                        node.setAttribute( name, "right" );
                        break;
                case Qt::AlignHCenter:
                        node.setAttribute( name, "hcenter" );
                        break;
                case Qt::AlignLeft:
                        node.setAttribute( name, "left" );
                        break;
                case Qt::AlignBottom:
                        node.setAttribute( name, "bottom" );
                        break;
                case Qt::AlignVCenter:
                        node.setAttribute( name, "vcenter" );
                        break;
                case Qt::AlignTop:
                        node.setAttribute( name, "top" );
                        break;
                default:
                        node.setAttribute( name, "left" );
                        break;
                }
        }


        void XmlUtil::setWrapModeAttr( QDomElement&          node,
                                       const QString&        name,
                                       QTextOption::WrapMode value )
        {
                switch (value)
                {
                case QTextOption::WordWrap:
                        node.setAttribute( name, "word" );
                        break;
                case QTextOption::WrapAnywhere:
                        node.setAttribute( name, "anywhere" );
                        break;
                case QTextOption::NoWrap:
                case QTextOption::ManualWrap:
                        node.setAttribute( name, "none" );
                        break;
                default:
                        node.setAttribute( name, "word" );
                        break;
                }
        }


        void XmlUtil::setUnitsAttr( QDomElement&   node,
                                    const QString& name,
                                    Units          value )
        {
                node.setAttribute( name, value.toIdString() );
        }


        void XmlUtil::setPathDataAttr( QDomElement&        node,
                                       const QString&      name,
                                       const QPainterPath& path,
                                       Units               units )
        {
                QString pathString;
                for ( int i = 0; i < path.elementCount(); i++ )
                {
                        auto element = path.elementAt( i );

                        // QPainterPath is natively in pts
                        Distance x = Distance::pt( element.x );
                        Distance y = Distance::pt( element.y );

                        // Translate desired units for path data
                        double xValue = x.inUnits( units );
                        double yValue = y.inUnits( units );

                        if ( element.isMoveTo() )
                        {
                                pathString.append( QString( "M %1 %2" ).arg( xValue ).arg( yValue ) );
                        }
                        else if ( element.isLineTo() )
                        {
                                pathString.append( QString( "L %1 %2" ).arg( xValue ).arg( yValue ) );
                        }

                        if ( i < (path.elementCount() - 1) )
                        {
                                pathString.append( " " );
                        }
                }

                node.setAttribute( name, pathString );
        }


}
