/** ===========================================================
 * @file
 *
 * This file is a part of digiKam project
 * <a href="http://www.digikam.org">http://www.digikam.org</a>
 *
 * @date   2009-06-15
 * @brief  multi-languages string editor
 *
 * @author Copyright (C) 2009-2013 by Gilles Caulier
 *         <a href="mailto:caulier dot gilles at gmail dot com">caulier dot gilles at gmail dot com</a>
 *
 * 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, 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.
 *
 * ============================================================ */

#include "altlangstredit.moc"

// Qt includes

#include <QEvent>
#include <QStyle>
#include <QLabel>
#include <QMap>
#include <QToolButton>
#include <QGridLayout>

// KDE includes

#include <kdialog.h>
#include <kglobal.h>
#include <kiconloader.h>
#include <klocale.h>
#include <ktextedit.h>
#include <kcombobox.h>
#include <kdebug.h>

// Local includes

#include "msgtextedit.h"

namespace KExiv2Iface
{

class AltLangStrEdit::Private
{
public:

    Private()
    {
        valueEdit       = 0;
        titleLabel      = 0;
        delValueButton  = 0;
        languageCB      = 0;
        linesVisible    = 0;
        currentLanguage = "x-default";

        // We cannot use KLocale::allLanguagesList() here because KDE only
        // support 2 characters country codes. XMP require 2+2 characters language+country
        // following ISO 3066 (http://babelwiki.babelzilla.org/index.php?title=Language_codes)

        // The first one from the list is the Default Language code specified by XMP paper
        languageCodeMap.insert( "x-default", i18n("Default Language") );

        // Standard ISO 3066 country codes.

        languageCodeMap.insert( "af-ZA", i18n("Afrikaans (South Africa)") );
        languageCodeMap.insert( "am-ET", i18n("Amharic (Ethiopia)") );
        languageCodeMap.insert( "ar-AE", i18n("Arabic (UAE)") );
        languageCodeMap.insert( "ar-BH", i18n("Arabic (Bahrain)") );
        languageCodeMap.insert( "ar-DZ", i18n("Arabic (Algeria)") );
        languageCodeMap.insert( "ar-EG", i18n("Arabic (Egypt)") );
        languageCodeMap.insert( "ar-IQ", i18n("Arabic (Iraq)") );
        languageCodeMap.insert( "ar-JO", i18n("Arabic (Jordan)") );
        languageCodeMap.insert( "ar-KW", i18n("Arabic (Kuwait)") );
        languageCodeMap.insert( "ar-LB", i18n("Arabic (Lebanon)") );
        languageCodeMap.insert( "ar-LY", i18n("Arabic (Libya)") );
        languageCodeMap.insert( "ar-MA", i18n("Arabic (Morocco)") );
        languageCodeMap.insert( "ar-OM", i18n("Arabic (Oman)") );
        languageCodeMap.insert( "ar-QA", i18n("Arabic (Qatar)") );
        languageCodeMap.insert( "ar-SA", i18n("Arabic (Saudi Arabia)") );
        languageCodeMap.insert( "ar-SY", i18n("Arabic (Syria)") );
        languageCodeMap.insert( "ar-TN", i18n("Arabic (Tunisia)") );
        languageCodeMap.insert( "ar-YE", i18n("Arabic (Yemen)") );
        languageCodeMap.insert( "as-IN", i18n("Assamese (India)") );
        languageCodeMap.insert( "ba-RU", i18n("Bashkir (Russia)") );
        languageCodeMap.insert( "be-BY", i18n("Belarusian (Belarus)") );
        languageCodeMap.insert( "bg-BG", i18n("Bulgarian (Bulgaria)") );
        languageCodeMap.insert( "bn-IN", i18n("Bengali (India)") );
        languageCodeMap.insert( "bo-BT", i18n("Tibetan (Bhutan)") );
        languageCodeMap.insert( "bo-CN", i18n("Tibetan (PRC)") );
        languageCodeMap.insert( "br-FR", i18n("Breton (France)") );
        languageCodeMap.insert( "ca-AD", i18n("Catalan (Andorra)") );
        languageCodeMap.insert( "ca-ES", i18n("Catalan (Spain)") );
        languageCodeMap.insert( "ca-FR", i18n("Catalan (France)") );
        languageCodeMap.insert( "co-FR", i18n("Corsican (France)") );
        languageCodeMap.insert( "cs-CZ", i18n("Czech (Czech Republic)") );
        languageCodeMap.insert( "cy-GB", i18n("Welsh (United Kingdom)") );
        languageCodeMap.insert( "da-DK", i18n("Danish (Denmark)") );
        languageCodeMap.insert( "de-AT", i18n("German (Austria)") );
        languageCodeMap.insert( "de-CH", i18n("German (Switzerland)") );
        languageCodeMap.insert( "de-DE", i18n("German (Germany)") );
        languageCodeMap.insert( "de-LI", i18n("German (Liechtenstein)") );
        languageCodeMap.insert( "de-LU", i18n("German (Luxembourg)") );
        languageCodeMap.insert( "el-GR", i18n("Greek (Greece)") );
        languageCodeMap.insert( "en-AU", i18n("English (Australia)") );
        languageCodeMap.insert( "en-BZ", i18n("English (Belize)") );
        languageCodeMap.insert( "en-CA", i18n("English (Canada)") );
        languageCodeMap.insert( "en-CB", i18n("English (Caribbean)") );
        languageCodeMap.insert( "en-GB", i18n("English (United Kingdom)") );
        languageCodeMap.insert( "en-IE", i18n("English (Ireland)") );
        languageCodeMap.insert( "en-IN", i18n("English (India)") );
        languageCodeMap.insert( "en-JA", i18n("English (Jamaica)") );
        languageCodeMap.insert( "en-MY", i18n("English (Malaysia)") );
        languageCodeMap.insert( "en-NZ", i18n("English (New Zealand)") );
        languageCodeMap.insert( "en-PH", i18n("English (Philippines)") );
        languageCodeMap.insert( "en-SG", i18n("English (Singapore)") );
        languageCodeMap.insert( "en-TT", i18n("English (Trinidad)") );
        languageCodeMap.insert( "en-US", i18n("English (United States)") );
        languageCodeMap.insert( "en-ZA", i18n("English (South Africa)") );
        languageCodeMap.insert( "en-ZW", i18n("English (Zimbabwe)") );
        languageCodeMap.insert( "es-AR", i18n("Spanish (Argentina)") );
        languageCodeMap.insert( "es-BO", i18n("Spanish (Bolivia)") );
        languageCodeMap.insert( "es-CL", i18n("Spanish (Chile)") );
        languageCodeMap.insert( "es-CO", i18n("Spanish (Colombia)") );
        languageCodeMap.insert( "es-CR", i18n("Spanish (Costa Rica)") );
        languageCodeMap.insert( "es-DO", i18n("Spanish (Dominican Republic)") );
        languageCodeMap.insert( "es-EC", i18n("Spanish (Ecuador)") );
        languageCodeMap.insert( "es-ES", i18n("Spanish (Spain)") );
        languageCodeMap.insert( "es-GT", i18n("Spanish (Guatemala)") );
        languageCodeMap.insert( "es-HN", i18n("Spanish (Honduras)") );
        languageCodeMap.insert( "es-MX", i18n("Spanish (Mexico)") );
        languageCodeMap.insert( "es-NI", i18n("Spanish (Nicaragua)") );
        languageCodeMap.insert( "es-PA", i18n("Spanish (Panama)") );
        languageCodeMap.insert( "es-PE", i18n("Spanish (Peru)") );
        languageCodeMap.insert( "es-PR", i18n("Spanish (Puerto Rico)") );
        languageCodeMap.insert( "es-PY", i18n("Spanish (Paraguay)") );
        languageCodeMap.insert( "es-SV", i18n("Spanish (El Salvador)") );
        languageCodeMap.insert( "es-UR", i18n("Spanish (Uruguay)") );
        languageCodeMap.insert( "es-US", i18n("Spanish (United States)") );
        languageCodeMap.insert( "es-VE", i18n("Spanish (Venezuela)") );
        languageCodeMap.insert( "et-EE", i18n("Estonian (Estonia)") );
        languageCodeMap.insert( "eu-ES", i18n("Basque (Basque Country)") );
        languageCodeMap.insert( "fa-IR", i18n("Persian (Iran)") );
        languageCodeMap.insert( "fi-FI", i18n("Finnish (Finland)") );
        languageCodeMap.insert( "fo-FO", i18n("Faeroese (Faero Islands)") );
        languageCodeMap.insert( "fr-BE", i18n("French (Belgium)") );
        languageCodeMap.insert( "fr-CA", i18n("French (Canada)") );
        languageCodeMap.insert( "fr-CH", i18n("French (Switzerland)") );
        languageCodeMap.insert( "fr-FR", i18n("French (France)") );
        languageCodeMap.insert( "fr-LU", i18n("French (Luxembourg)") );
        languageCodeMap.insert( "fr-MC", i18n("French (Monaco)") );
        languageCodeMap.insert( "fy-NL", i18n("Frisian (Netherlands)") );
        languageCodeMap.insert( "ga-IE", i18n("Irish (Ireland)") );
        languageCodeMap.insert( "gl-ES", i18n("Galician (Galicia)") );
        languageCodeMap.insert( "gu-IN", i18n("Gujarati (India)") );
        languageCodeMap.insert( "he-IL", i18n("Hebrew (Israel)") );
        languageCodeMap.insert( "hi-IN", i18n("Hindi (India)") );
        languageCodeMap.insert( "hr-BA", i18n("Croatian (Bosnia and Herzegovina, Latin)") );
        languageCodeMap.insert( "hr-HR", i18n("Croatian (Croatia)") );
        languageCodeMap.insert( "hu-HU", i18n("Hungarian (Hungary)") );
        languageCodeMap.insert( "hy-AM", i18n("Armenian (Armenia)") );
        languageCodeMap.insert( "id-ID", i18n("(Indonesian)") );
        languageCodeMap.insert( "ii-CN", i18n("Yi (PRC)") );
        languageCodeMap.insert( "is-IS", i18n("Icelandic (Iceland)") );
        languageCodeMap.insert( "it-CH", i18n("Italian (Switzerland)") );
        languageCodeMap.insert( "it-IT", i18n("Italian (Italy)") );
        languageCodeMap.insert( "ja-JP", i18n("Japanese (Japan)") );
        languageCodeMap.insert( "ka-GE", i18n("Georgian (Georgia)") );
        languageCodeMap.insert( "kk-KZ", i18n("Kazakh (Kazakhstan)") );
        languageCodeMap.insert( "kl-GL", i18n("Greenlandic (Greenland)") );
        languageCodeMap.insert( "km-KH", i18n("Khmer (Cambodia)") );
        languageCodeMap.insert( "kn-IN", i18n("Kannada (India)") );
        languageCodeMap.insert( "ko-KR", i18n("Korean (South Korea)") );
        languageCodeMap.insert( "ky-KG", i18n("Kyrgyz (Kyrgyzstan)") );
        languageCodeMap.insert( "lb-LU", i18n("Luxembourgish (Luxembourg)") );
        languageCodeMap.insert( "lo-LA", i18n("Lao (Lao PDR)") );
        languageCodeMap.insert( "lt-LT", i18n("Lithuanian (Lithuania)") );
        languageCodeMap.insert( "lv-LV", i18n("Latvian (Latvia)") );
        languageCodeMap.insert( "mi-NZ", i18n("Maori (New Zealand)") );
        languageCodeMap.insert( "mk-MK", i18n("Macedonian (Macedonia)") );
        languageCodeMap.insert( "ml-IN", i18n("Malayalam (India)") );
        languageCodeMap.insert( "mn-CN", i18n("Mongolian (PRC)") );
        languageCodeMap.insert( "mn-MN", i18n("Mongolian (Mongolia)") );
        languageCodeMap.insert( "mr-IN", i18n("Marathi (India)") );
        languageCodeMap.insert( "ms-BN", i18n("Malay (Brunei Darussalam)") );
        languageCodeMap.insert( "ms-MY", i18n("Malay (Malaysia)") );
        languageCodeMap.insert( "mt-MT", i18n("Maltese (Malta)") );
        languageCodeMap.insert( "nb-NO", i18n("Norwegian Bokmål (Norway)") );
        languageCodeMap.insert( "ne-NP", i18n("Nepali (Nepal)") );
        languageCodeMap.insert( "nl-BE", i18n("Dutch (Belgium)") );
        languageCodeMap.insert( "nl-NL", i18n("Dutch (Netherlands)") );
        languageCodeMap.insert( "nn-NO", i18n("Norwegian Nynorsk (Norway)") );
        languageCodeMap.insert( "ns-ZA", i18n("Sesotho sa Leboa (South Africa)") );
        languageCodeMap.insert( "oc-FR", i18n("Occitan (France)") );
        languageCodeMap.insert( "or-IN", i18n("Oriya (India)") );
        languageCodeMap.insert( "pa-IN", i18n("Punjabi (India)") );
        languageCodeMap.insert( "pl-PL", i18n("Polish (Poland)") );
        languageCodeMap.insert( "ps-AF", i18n("Pashto (Afghanistan)") );
        languageCodeMap.insert( "pt-BR", i18n("Portuguese (Brazil)") );
        languageCodeMap.insert( "pt-PT", i18n("Portuguese (Portugal)") );
        languageCodeMap.insert( "rm-CH", i18n("Romansh (Switzerland)") );
        languageCodeMap.insert( "ro-RO", i18n("Romanian (Romania)") );
        languageCodeMap.insert( "ru-RU", i18n("Russian (Russia)") );
        languageCodeMap.insert( "rw-RW", i18n("Kinyarwanda (Rwanda)") );
        languageCodeMap.insert( "sa-IN", i18n("Sanskrit (India)") );
        languageCodeMap.insert( "se-FI", i18n("Sami (Northern, Finland)") );
        languageCodeMap.insert( "se-NO", i18n("Sami (Northern, Norway)") );
        languageCodeMap.insert( "se-SE", i18n("Sami (Northern, Sweden)") );
        languageCodeMap.insert( "si-LK", i18n("Sinhala (Sri Lanka)") );
        languageCodeMap.insert( "sk-SK", i18n("Slovak (Slovakia)") );
        languageCodeMap.insert( "sl-SI", i18n("Slovenian (Slovenia)") );
        languageCodeMap.insert( "sq-AL", i18n("Albanian (Albania)") );
        languageCodeMap.insert( "sv-FI", i18n("Swedish (Finland)") );
        languageCodeMap.insert( "sv-SE", i18n("Swedish (Sweden)") );
        languageCodeMap.insert( "sw-KE", i18n("Swahili (Kenya)") );
        languageCodeMap.insert( "ta-IN", i18n("Tamil (India)") );
        languageCodeMap.insert( "te-IN", i18n("Telugu (India)") );
        languageCodeMap.insert( "th-TH", i18n("Thai (Thailand)") );
        languageCodeMap.insert( "tk-TM", i18n("Turkmen (Turkmenistan)") );
        languageCodeMap.insert( "tn-ZA", i18n("Setswana Tswana (South Africa)") );
        languageCodeMap.insert( "tr-IN", i18n("Urdu (India)") );
        languageCodeMap.insert( "tr-TR", i18n("Turkish (Turkey)") );
        languageCodeMap.insert( "tt-RU", i18n("Tatar (Russia)") );
        languageCodeMap.insert( "ug-CN", i18n("Uighur (PRC)") );
        languageCodeMap.insert( "uk-UA", i18n("Ukrainian (Ukraine)") );
        languageCodeMap.insert( "ur-PK", i18n("Urdu (Pakistan)") );
        languageCodeMap.insert( "vi-VN", i18n("Vietnamese (Vietnam)") );
        languageCodeMap.insert( "wo-SN", i18n("Wolof (Senegal)") );
        languageCodeMap.insert( "xh-ZA", i18n("isiXhosa Xhosa (South Africa)") );
        languageCodeMap.insert( "yo-NG", i18n("Yoruba (Nigeria)") );
        languageCodeMap.insert( "zh-CN", i18n("Chinese (PRC)") );
        languageCodeMap.insert( "zh-HK", i18n("Chinese (Hong Kong SAR, PRC)") );
        languageCodeMap.insert( "zh-MO", i18n("Chinese (Macao SAR)") );
        languageCodeMap.insert( "zh-SG", i18n("Chinese (Singapore)") );
        languageCodeMap.insert( "zh-TW", i18n("Chinese (Taiwan)") );
        languageCodeMap.insert( "zu-ZA", i18n("isiZulu Zulu (South Africa)") );
    }

    ~Private()
    {
        languageCodeMap.clear();
    }

public:

    typedef QMap<QString, QString> LanguageCodeMap;

    LanguageCodeMap                languageCodeMap;

    QString                        currentLanguage;

    uint                           linesVisible;

    QLabel*                        titleLabel;

    QToolButton*                   delValueButton;

    MsgTextEdit*                   valueEdit;

    KComboBox*                     languageCB;

    KExiv2::AltLangMap             values;
};

AltLangStrEdit::AltLangStrEdit(QWidget* parent)
    : QWidget(parent), d(new Private)
{
    QGridLayout* const grid = new QGridLayout(this);
    d->titleLabel           = new QLabel(this);
    d->delValueButton       = new QToolButton(this);
    d->delValueButton->setIcon(SmallIcon("edit-clear"));
    d->delValueButton->setToolTip(i18n("Remove entry for this language"));
    d->delValueButton->setEnabled(false);

    d->languageCB = new KComboBox(this);
    d->languageCB->setSizeAdjustPolicy(KComboBox::AdjustToContents);
    d->languageCB->setWhatsThis(i18n("Select item language here."));

    d->valueEdit  = new MsgTextEdit(this);
    d->valueEdit->setCheckSpellingEnabled(true);

    // --------------------------------------------------------

    grid->setAlignment( Qt::AlignTop );
    grid->addWidget(d->titleLabel,     0, 0, 1, 1);
    grid->addWidget(d->languageCB,     0, 2, 1, 1);
    grid->addWidget(d->delValueButton, 0, 3, 1, 1);
    grid->addWidget(d->valueEdit,      1, 0, 1,-1);
    grid->setColumnStretch(1, 10);
    grid->setMargin(0);
    grid->setSpacing(KDialog::spacingHint());

    loadLangAltListEntries();

    // --------------------------------------------------------

    connect(d->languageCB, SIGNAL(currentIndexChanged(int)),
            this, SLOT(slotSelectionChanged()));

    connect(d->delValueButton, SIGNAL(clicked()),
            this, SLOT(slotDeleteValue()));

    connect(d->valueEdit, SIGNAL(textChanged()),
            this, SLOT(slotTextChanged()));
}

AltLangStrEdit::~AltLangStrEdit()
{
    delete d;
}

QString AltLangStrEdit::currentLanguageCode() const
{
    return d->currentLanguage;
}

void AltLangStrEdit::setCurrentLanguageCode(const QString& lang)
{
    if(d->currentLanguage.isEmpty())
    {
        d->currentLanguage = "x-default";
    }
    else
    {
        d->currentLanguage = lang;
    }
}

QString AltLangStrEdit::languageCode(int index) const
{
    return d->languageCB->itemText(index);
}

void AltLangStrEdit::setTitle(const QString& title)
{
    d->titleLabel->setText(title);
}

void AltLangStrEdit::setClickMessage(const QString& msg)
{
    d->valueEdit->setClickMessage(msg);
}

void AltLangStrEdit::reset()
{
    setValues(KExiv2::AltLangMap());
}

void AltLangStrEdit::slotDeleteValue()
{
    d->values.remove(d->currentLanguage);
    setValues(d->values);
    emit signalValueDeleted(d->currentLanguage);
}

void AltLangStrEdit::slotSelectionChanged()
{
    d->currentLanguage = d->languageCB->currentText();

    // There are bogus signals caused by spell checking, see bug #141663.
    // so we must block signals here.

    d->valueEdit->blockSignals(true);

    QString langISO3066 = d->currentLanguage;
    langISO3066.replace('-', '_');
    d->valueEdit->setSpellCheckingLanguage(langISO3066);

    QString text = d->values.value(d->currentLanguage);
    d->valueEdit->setText(text);
    d->delValueButton->setEnabled(!text.isNull());

    d->valueEdit->blockSignals(false);

    d->languageCB->setToolTip(d->languageCodeMap.value(d->currentLanguage));

    emit signalSelectionChanged(d->currentLanguage);
}

void AltLangStrEdit::setValues(const KExiv2::AltLangMap& values)
{
    d->values    = values;
    loadLangAltListEntries();

    d->valueEdit->blockSignals(true);

    QString text = d->values.value(d->currentLanguage);
    d->valueEdit->setText(text);
    d->delValueButton->setEnabled(!text.isNull());

    d->valueEdit->blockSignals(false);
}

KExiv2::AltLangMap& AltLangStrEdit::values()
{
    return d->values;
}

void AltLangStrEdit::loadLangAltListEntries()
{
    d->languageCB->blockSignals(true);

    d->languageCB->clear();

    // In first we fill already assigned languages.

    QStringList list = d->values.keys();

    if (!list.isEmpty())
    {
        foreach(const QString& item, list)
        {
              d->languageCB->addItem(item);
              d->languageCB->setItemIcon(d->languageCB->count()-1, SmallIcon("dialog-ok"));
        }

        d->languageCB->insertSeparator(d->languageCB->count());
    }

    // ...and now, all the rest...

    for (Private::LanguageCodeMap::Iterator it = d->languageCodeMap.begin();
         it != d->languageCodeMap.end(); ++it)
    {
        if (!list.contains(it.key()))
        {
            d->languageCB->addItem(it.key());
        }
    }

    d->languageCB->setCurrentItem(d->currentLanguage);

    d->languageCB->blockSignals(false);
}

QString AltLangStrEdit::defaultAltLang() const
{
    return d->values.value(QString("x-default"));
}

bool AltLangStrEdit::asDefaultAltLang() const
{
    return !defaultAltLang().isNull();
}

void AltLangStrEdit::slotTextChanged()
{
    QString editedText   = d->valueEdit->toPlainText();
    QString previousText = d->values.value(d->currentLanguage);

    // Special case : if edited and previous strings are empty, do nothing.
    // See bug #152948.
    if (editedText.isEmpty() && previousText.isNull())
    {
        return;
    }

    if (editedText.isEmpty())
    {
        slotDeleteValue();
    }
    else if (previousText.isNull())
    {
        addCurrent();
    }
    else if (editedText != previousText)
    {
        // we cannot trust that the text actually changed
        // (there are bogus signals caused by spell checking, see bug #141663)
        // so we have to check before marking the metadata as modified.
        d->values.insert(d->currentLanguage, editedText);
        emit signalModified(d->currentLanguage, editedText);
    }
}

void AltLangStrEdit::addCurrent()
{
    QString text = d->valueEdit->toPlainText();

    d->values.insert(d->currentLanguage, text);
    loadLangAltListEntries();
    d->delValueButton->setEnabled(true);
    emit signalValueAdded(d->currentLanguage, text);
}

void AltLangStrEdit::setLinesVisible(uint lines)
{
    d->linesVisible = lines;

    if (d->linesVisible == 0)
    {
        d->valueEdit->setFixedHeight(QWIDGETSIZE_MAX); // reset
    }
    else
    {
        d->valueEdit->setFixedHeight(d->valueEdit->fontMetrics().lineSpacing() * d->linesVisible         +
                                     d->valueEdit->contentsMargins().top()                               +
                                     d->valueEdit->contentsMargins().bottom()                            +
                                     1                                                                   +
                                     2*(d->valueEdit->style()->pixelMetric(QStyle::PM_DefaultFrameWidth) +
                                        d->valueEdit->style()->pixelMetric(QStyle::PM_FocusFrameVMargin))
                                    );
    }

    // It's not possible to display scrollbar properlly if size is too small
    if (d->linesVisible < 3)
    {
        d->valueEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    }
}

uint AltLangStrEdit::linesVisible() const
{
    return d->linesVisible;
}

void AltLangStrEdit::changeEvent(QEvent* e)
{
    if (e->type() == QEvent::FontChange)
    {
        setLinesVisible(linesVisible());
    }

    QWidget::changeEvent(e);
}

}  // namespace KExiv2Iface
