/***************************************************************************
 *   Copyright (C) 2008 by S. MANKOWSKI / G. DE BURE support@mankowski.fr  *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   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.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>  *
 ***************************************************************************/
/** @file
 * This file is Skrooge plugin to generate categories.
 *
 * @author Stephane MANKOWSKI / Guillaume DE BURE
 */
#include "skgcategoriesplugin.h"

#include <KMessageBox>
#include <KActionCollection>
#include <KAction>
#include <KAboutData>
#include <KStandardDirs>
#include <KGenericFactory>
#include <KSelectAction>
#include <QFileInfo>

#include "skgcategoriespluginwidget.h"
#include "skgtraces.h"
#include "skgmainpanel.h"
#include "skghtmlboardwidget.h"
#include "skgtransactionmng.h"
#include "skgcategoryobject.h"
#include "skgdocumentbank.h"
#include "skgimportexportmanager.h"

/**
 * This plugin factory.
 */
K_PLUGIN_FACTORY(SKGCategoriesPluginFactory, registerPlugin<SKGCategoriesPlugin>();)
/**
 * This plugin export.
 */
K_EXPORT_PLUGIN(SKGCategoriesPluginFactory("skrooge_categories", "skrooge_categories"))

SKGCategoriesPlugin::SKGCategoriesPlugin(QWidget* iWidget, QObject* iParent, const QVariantList& /*iArg*/)
    : SKGInterfacePlugin(iParent), m_currentBankDocument(NULL), m_importStdCatAction(NULL)
{
    Q_UNUSED(iWidget);
    SKGTRACEINFUNC(10);
}

SKGCategoriesPlugin::~SKGCategoriesPlugin()
{
    SKGTRACEINFUNC(10);
    m_currentBankDocument = NULL;
    m_importStdCatAction = NULL;
}

bool SKGCategoriesPlugin::setupActions(SKGDocument* iDocument, const QStringList& iArgument)
{
    SKGTRACEINFUNC(10);
    Q_UNUSED(iArgument);
    m_currentBankDocument = qobject_cast<SKGDocumentBank*>(iDocument);
    if (m_currentBankDocument == NULL) {
        return false;
    }

    setComponentData(KGlobal::mainComponent());
    setXMLFile("../skrooge_categories/skrooge_categories.rc");

    // Import categories
    QStringList overlaycategories;
    overlaycategories.push_back(icon());

    QStringList overlaydelete;
    overlaydelete.push_back("edit-delete");

    KSelectAction* contextMenu = new KSelectAction(KIcon("document-import", NULL, overlaycategories), i18nc("Verb", "Import categories"), this);
    registerGlobalAction("import_categories" , contextMenu);

    m_importStdCatAction = contextMenu->addAction(KIcon("document-import", NULL, overlaycategories), i18nc("Verb", "Import standard categories"));
    m_importStdCatAction->setCheckable(false);
    connect(m_importStdCatAction, SIGNAL(triggered(bool)), SLOT(importStandardCategories()));
    registerGlobalAction("import_standard_categories", m_importStdCatAction);

    QStringList listCategories = KStandardDirs().findAllResources("data", "skrooge/categories/" % KGlobal::locale()->country() % "/*.qif");
    foreach(const QString & cat, listCategories) {
        QString name = QFileInfo(cat).baseName().replace('_', ' ');
        KAction* act = contextMenu->addAction(KIcon("document-import", NULL, overlaycategories), i18nc("Verb", "Import categories [%1]", name));
        act->setCheckable(false);
        act->setData(cat);
        connect(act, SIGNAL(triggered(bool)), SLOT(importCategories()));
        registerGlobalAction("import_categories_" % name, act);
    }

    KAction* deleteUnusedCategoriesAction = new KAction(KIcon(icon(), NULL, overlaydelete), i18nc("Verb", "Delete unused categories"), this);
    connect(deleteUnusedCategoriesAction, SIGNAL(triggered(bool)), SLOT(deleteUnusedCategories()));
    registerGlobalAction("clean_delete_unused_categories", deleteUnusedCategoriesAction);

    // ------------
    KAction* actTmp = new KAction(KIcon(icon()), i18nc("Verb", "Open similar categories..."), this);
    actTmp->setData(QString("skg://skrooge_categories_plugin/?title_icon=" % icon() % "&title=" % SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Similar categories")) %
                            "&whereClause=" % SKGServices::encodeForUrl("id IN (SELECT category.id FROM category, ("
                                    "SELECT * FROM category C WHERE EXISTS (SELECT 1 FROM category p2 WHERE p2.id<>C.id AND upper(p2.t_fullname)=upper(C.t_fullname) AND p2.t_fullname<>C.t_fullname)) "
                                    "AS C WHERE category.t_fullname=C.t_fullname OR C.t_fullname LIKE category.t_fullname||' > %')")));
    connect(actTmp, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
    registerGlobalAction("view_open_similar_categories", actTmp);
    /*TODO
    WITH RECURSIVE
    cat(t_fullname,id, level) AS (
    SELECT t_fullname, id, 0 FROM v_category_display WHERE EXISTS (SELECT 1 FROM category p2 WHERE p2.id<>v_category_display.id AND upper(p2.t_fullname)=upper(v_category_display.t_fullname) AND p2.t_fullname<>v_category_display.t_fullname)
    UNION ALL
    SELECT v_category_display.name, cat.level+1
    FROM v_category_display JOIN cat ON v_category_display.rd_category_id=under_alice.id
    ORDER BY 2
    )
    SELECT substr('..........',1,level*3) || name FROM cat;
    */
    return true;
}

int SKGCategoriesPlugin::getNbDashboardWidgets()
{
    SKGTRACEINFUNC(1);
    return 3;
}

QString SKGCategoriesPlugin::getDashboardWidgetTitle(int iIndex)
{
    SKGTRACEINFUNC(1);
    if (iIndex == 0) {
        return i18nc("Report header",  "5 main categories of expenditure");
    } else if (iIndex == 1) {
        return i18nc("Report header",  "5 main variations");
    }
    return i18nc("Report header",  "Budget");
}

SKGBoardWidget* SKGCategoriesPlugin::getDashboardWidget(int iIndex)
{
    SKGTRACEINFUNC(1);
    if (iIndex == 0)  {
        SKGHtmlBoardWidget* w = new SKGHtmlBoardWidget(m_currentBankDocument,
                getDashboardWidgetTitle(iIndex),
                KStandardDirs().findResource("data", "skrooge/html/default/categories_period_table.html"),
                QStringList() << "v_suboperation_consolidated", true);

        QStringList overlayopen;
        overlayopen.push_back("skg_open");
        KAction* open = new KAction(KIcon("view-investment", NULL, overlayopen), i18nc("Verb", "Open report..."), w);
        connect(open, SIGNAL(triggered(bool)), SKGMainPanel::getMainPanel(), SLOT(openPage()));
        QString u = QString("skg://skrooge_report_plugin/?grouped=Y&transfers=Y&tracked=Y&expenses=Y&incomes=N&lines2=t_REALCATEGORY&currentPage=-1&mode=0&interval=2&period=2&nbLevelLines=1") %
                    "&tableAndGraphState.graphMode=2&tableAndGraphState.allPositive=N&tableAndGraphState.sortColumn=1&tableAndGraphState.show=graph;table&columns=" % SKGServices::encodeForUrl("#NOTHING#");
        open->setData(u);

        QAction* sep = new QAction(this);
        sep->setSeparator(true);
        w->insertAction(1, sep);

        w->insertAction(1, open);
        return w;
    } else if (iIndex == 1) return new SKGHtmlBoardWidget(m_currentBankDocument,
                                       getDashboardWidgetTitle(iIndex),
                                       KStandardDirs().findResource("data", "skrooge/html/default/categories_variations.html"),
                                       QStringList() << "v_suboperation_consolidated", true);
    return new SKGHtmlBoardWidget(m_currentBankDocument,
                                  getDashboardWidgetTitle(iIndex),
                                  KStandardDirs().findResource("data", "skrooge/html/default/budget_table.html"),
                                  QStringList() << "v_budget", true);
}

void SKGCategoriesPlugin::refresh()
{
    SKGTRACEINFUNC(10);
    if (m_currentBankDocument) {
        bool test = (m_currentBankDocument->getDatabase() != NULL);
        if (m_importStdCatAction) {
            m_importStdCatAction->setEnabled(test);
        }

        // Automatic categories creation
        if (m_currentBankDocument->getDatabase() != NULL) {
            QString doc_id = m_currentBankDocument->getUniqueIdentifier();
            if (m_docUniqueIdentifier != doc_id) {
                m_docUniqueIdentifier = doc_id;

                bool exist = false;
                SKGError err = m_currentBankDocument->existObjects("category", "", exist);
                if (!err && !exist) {
                    importStandardCategories();

                    // The file is considered has not modified
                    m_currentBankDocument->setFileNotModified();
                }
            }
        }
    }
}

void SKGCategoriesPlugin::importCategories()
{
    SKGTRACEINFUNC(10);
    SKGError err;
    KAction* act = qobject_cast< KAction* >(sender());
    if (act) {
        QString fileName = act->data().toString();
        QString name = QFileInfo(fileName).baseName().replace('_', ' ');
        {
            SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Verb", "Import categories [%1]", name), err);

            SKGImportExportManager imp(m_currentBankDocument, KUrl(fileName));
            err = imp.importFile();
            IFOKDO(err, m_currentBankDocument->removeMessages(m_currentBankDocument->getCurrentTransaction()))
        }


        // status
        IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Categories imported.")))
        else {
            err.addError(ERR_FAIL, i18nc("Error message", "Importing categories failed."));
        }

        // Display error
        SKGMainPanel::displayErrorMessage(err);
    }
}

void SKGCategoriesPlugin::importStandardCategories()
{
    SKGTRACEINFUNC(10);
    SKGError err;
    {
        QString cats = i18nc("List of categories.It is not needed to translate each item. You can set the list you want. ';' must be used to separate categories. ' > ' must be used to separate categorie and sub caterogie (no limit of level).",
                             "Alimony;Auto;Auto > Fuel;Auto > Insurance;Auto > Lease;Auto > Loan;Auto > Registration;Auto > Service;Bank Charges;Bank Charges > Interest Paid;Bank Charges > Service Charge;Bills;Bills > Electricity;"
                             "Bills > Fuel Oil;Bills > Local Taxes;Bills > Mortgage;Bills > Natural Gas;Bills > Rent;Bills > TV;Bills > Telephone;Bills > Water & Sewage;Bonus;Business;Business > Auto;Business > Capital Goods;Business > Legal Expenses;Business > Office Rent;"
                             "Business > Office Supplies;Business > Other;Business > Revenue;Business > Taxes;Business > Travel;Business > Utilities;Business > Wages & Salary;Car;Car > Fuel;Car > Insurance;Car > Lease;Car > Loan;Car > Registration;Car > Service;"
                             "Cash Withdrawal;Charity;Charity > Donations;Child Care;Child Support;Clothing;Disability;Div Income;Div Income > Ord dividend;Div Income > Stock dividend;Education;Education > Board;Education > Books;Education > Fees;Education > Loans;"
                             "Education > Tuition;Employment;Employment > Benefits;Employment > Foreign;Employment > Lump sums;Employment > Other employ;Employment > Salary & wages;Food;Food > Dining Out;Food > Groceries;Gardening;"
                             "Gift Received;Gifts;Healthcare;Healthcare > Dental;Healthcare > Doctor;Healthcare > Hospital;Healthcare > Optician;Healthcare > Prescriptions;Holidays;Holidays > Accomodation;Holidays > Travel;Household;"
                             "Household > Furnishings;Household > Repairs;Insurance;Insurance > Auto;Insurance > Disability;Insurance > Home and Contents;Insurance > Life;Insurance > Medical;Int Inc;Int Inc > Bank Interest;Int Inc > Gross;Int Inc > Net;"
                             "Int Inc > Other savings;Invest. income;Invest. income > 1st option;Invest. income > Dividend;Invest. income > Foreign;Invest. income > Other savings;Invest. income > Other trusts;Invest. income > Other trusts#Capital;"
                             "Invest. income > Other trusts#Dist. rec'd;Invest. income > Other trusts#Estate;Investment Income;Investment Income > Dividends;Investment Income > Interest;Investment Income > Long-Term Capital Gains;"
                             "Investment Income > Short-Term Capital Gains;Investment Income > Tax-Exempt Interest;Job Expense;Job Expense > Non-Reimbursed;Job Expense > Reimbursed;Legal Fees;Leisure;Leisure > Books & Magazines;Leisure > Entertaining;"
                             "Leisure > Films & Video Rentals;Leisure > Hobbies;Leisure > Sporting Events;Leisure > Sports Goods;Leisure > Tapes & CDs;Leisure > Theatre & Concerts etc;Leisure > Toys & Games;Loan;Loan > Loan Interest;Long-Term Capital gains;Mortgage;Mortgage > Interest;Mortgage > PMI;Mortgage > Principle;Motor;Motor > Fuel;Motor > Loan;Motor > Service;Other Expense;Other Expense > Unknown;Other Income;Other Income > Child Support;"
                             "Other Income > Employee Share Option;Other Income > Gifts Received;Other Income > Loan Principal Received;Other Income > Lottery or Premium Bond Prizes;Other Income > Student loan;Other Income > Tax Refund;"
                             "Other Income > Unemployment Benefit;Pension;Pension > Employer;Personal Care;Pet Care;Pet Care > Food;Pet Care > Supplies;Pet Care > Vet's Bills;Recreation;Retirement Accounts;Retirement Accounts > 401(k)403(b) Plan Contributions;"
                             "Retirement Accounts > 529 Plan Contributions;Retirement Accounts > IRA Contributions;Retirement Income;Retirement Income > 401(k);Retirement Income > 401(k) > 403(b) Distributions;Retirement Income > IRA Distributions;"
                             "Retirement Income > Pensions & Annuities;Retirement Income > State Pension Benefits;Short-Term Capital gains;Social Security Benefits;Taxes;Taxes > AMT;Taxes > Federal Tax;Taxes > Federal Taxes;Taxes > Local Tax;Taxes > Local Taxes;"
                             "Taxes > Other Invest;Taxes > Other Tax;Taxes > Property Taxes;Taxes > Social Security;Taxes > State Tax;Taxes > State Taxes;Travel;Travel > Accomodations;Travel > Car Rental;Travel > Fares;Utilities;Utilities > Electricity;"
                             "Utilities > Garbage & Recycling;Utilities > Gas;Utilities > Sewer;Utilities > Telephone;Utilities > Water;Wages & Salary;Wages & Salary > Benefits;Wages & Salary > Bonus;Wages & Salary > Commission;"
                             "Wages & Salary > Employer Pension Contributions;Wages & Salary > Gross Pay;Wages & Salary > Net Pay;Wages & Salary > Overtime;Wages & Salary > Workman's Comp");

        SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Import standard categories"), err);

        foreach(const QString & item, SKGServices::splitCSVLine(cats, ';')) {
            QString line = item.trimmed();
            if (!line.isEmpty()) {
                SKGCategoryObject cat;
                err = SKGCategoryObject::createPathCategory(m_currentBankDocument, line, cat);
            }
        }
    }


    // status
    IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Categories imported.")))
    else {
        err.addError(ERR_FAIL, i18nc("Error message", "Importing categories failed."));
    }

    // Display error
    SKGMainPanel::displayErrorMessage(err);
}

SKGTabPage* SKGCategoriesPlugin::getWidget()
{
    SKGTRACEINFUNC(10);
    return new SKGCategoriesPluginWidget(m_currentBankDocument);
}

QString SKGCategoriesPlugin::title() const
{
    return i18nc("Noun, categories of items", "Categories");
}

QString SKGCategoriesPlugin::icon() const
{
    return "skrooge_category";
}

QString SKGCategoriesPlugin::toolTip() const
{
    return i18nc("A tool tip", "Categories management");
}

QStringList SKGCategoriesPlugin::tips() const
{
    QStringList output;
    output.push_back(i18nc("Description of a tips", "<p>... categories can be reorganized by drag & drop.</p>"));
    output.push_back(i18nc("Description of a tips", "<p>... if you delete a category, all operations affected by this category will be associated to its parent category.</p>"));
    return output;
}

int SKGCategoriesPlugin::getOrder() const
{
    return 30;
}

bool SKGCategoriesPlugin::isInPagesChooser() const
{
    return true;
}

SKGAdviceList SKGCategoriesPlugin::advice(const QStringList& iIgnoredAdvice)
{
    SKGTRACEINFUNC(10);
    SKGAdviceList output;
    // Check unused categies
    if (!iIgnoredAdvice.contains("skgcategoriesplugin_unused")) {
        bool exist = false;
        m_currentBankDocument->existObjects("v_category_used2", "t_ISUSEDCASCADE='N'", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgcategoriesplugin_unused");
            ad.setPriority(5);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Many unused categories"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "You can improve performances by removing categories that have no operations."));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = "skg://clean_delete_unused_categories";
                a.IsRecommended = true;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check operations not validated
    if (!iIgnoredAdvice.contains("skgmonthlyplugin_maincategoriesvariation")) {
        QString month = QDate::currentDate().toString("yyyy-MM");
        QDate datepreviousmonth = QDate::currentDate().addDays(-QDate::currentDate().day());
        QString previousmonth = datepreviousmonth.toString("yyyy-MM");

        QStringList listCategories;
        QStringList listVariations = qobject_cast< SKGDocumentBank* >(m_currentBankDocument)->get5MainCategoriesVariationList(month, previousmonth, true, &listCategories);

        int nb = listVariations.count();
        for (int i = 0; i < nb; ++i) {
            SKGAdvice ad;
            ad.setUUID("skgmonthlyplugin_maincategoriesvariation|" % listCategories.at(i));
            ad.setPriority(7);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Important variation for '%1'", listCategories.at(i)));
            ad.setLongMessage(listVariations.at(i));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = i18nc("Advice on making the best (action)", "Open sub operations with category containing '%1'", listCategories.at(i));
                a.IconName = "skg_open";
                a.IsRecommended = false;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }

    // Check categories with different case
    if (!iIgnoredAdvice.contains("skgcategoriesplugin_case")) {
        bool exist = false;
        m_currentBankDocument->existObjects("category", "EXISTS (SELECT 1 FROM category p2 WHERE p2.id<>category.id AND upper(p2.t_fullname)=upper(category.t_fullname) AND p2.t_fullname<>category.t_fullname)", exist);
        if (exist) {
            SKGAdvice ad;
            ad.setUUID("skgcategoriesplugin_case");
            ad.setPriority(3);
            ad.setShortMessage(i18nc("Advice on making the best (short)", "Some categories seem to be identical"));
            ad.setLongMessage(i18nc("Advice on making the best (long)", "Some categories seem to be identical but with different syntax. They could be merged."));
            QList<SKGAdvice::SKGAdviceAction> autoCorrections;
            {
                SKGAdvice::SKGAdviceAction a;
                a.Title = "skg://view_open_similar_categories";
                a.IsRecommended = false;
                autoCorrections.push_back(a);
            }
            ad.setAutoCorrections(autoCorrections);
            output.push_back(ad);
        }
    }
    return output;
}

SKGError SKGCategoriesPlugin::executeAdviceCorrection(const QString& iAdviceIdentifier, int iSolution)
{
    if (m_currentBankDocument && iAdviceIdentifier.startsWith(QLatin1String("skgmonthlyplugin_maincategoriesvariation|"))) {
        // Get parameters
        QString category = iAdviceIdentifier.right(iAdviceIdentifier.length() - 41);
        QString month = QDate::currentDate().toString("yyyy-MM");

        // Call operation plugin
        SKGMainPanel::getMainPanel()->openPage("skg://skrooge_operation_plugin/SKGOPERATION_CONSOLIDATED_DEFAULT_PARAMETERS/?currentPage=-1&title_icon=skrooge_category&operationTable=v_suboperation_consolidated&title=" %
                                               SKGServices::encodeForUrl(i18nc("Noun, a list of items", "Sub operations with category containing '%1'",  category)) % "&operationWhereClause=" % SKGServices::encodeForUrl("d_DATEMONTH='" % month % "' AND t_REALCATEGORY='" % SKGServices::stringToSqlString(category) % '\''));
        return SKGError();
    }
    return SKGInterfacePlugin::executeAdviceCorrection(iAdviceIdentifier, iSolution);
}

void SKGCategoriesPlugin::deleteUnusedCategories() const
{
    SKGError err;
    _SKGTRACEINFUNCRC(10, err);
    if (m_currentBankDocument) {
        SKGBEGINTRANSACTION(*m_currentBankDocument, i18nc("Noun, name of the user action", "Delete unused categories")  , err);

        QStringList categoriesUsed;
        err = m_currentBankDocument->getDistinctValues("category", "t_fullname", "t_fullname in ("
                "SELECT DISTINCT(category.t_fullname) FROM category, suboperation WHERE suboperation.r_category_id=category.id UNION ALL "
                "SELECT DISTINCT(category.t_fullname) FROM category, budget WHERE budget.rc_category_id=category.id UNION ALL "
                "SELECT DISTINCT(category.t_fullname) FROM category, budgetrule WHERE budgetrule.rc_category_id=category.id UNION ALL "
                "SELECT DISTINCT(category.t_fullname) FROM category, budgetrule WHERE budgetrule.rc_category_id_target=category.id)", categoriesUsed);

        for (int i = 0; i < categoriesUsed.count(); ++i) {  // Warning categoriesUsed is modified in the loop
            QString cat = categoriesUsed.at(i);
            categoriesUsed[i] = SKGServices::stringToSqlString(cat);
            int pos = cat.lastIndexOf(OBJECTSEPARATOR);
            if (pos != -1) {
                categoriesUsed.push_back(cat.left(pos));
            }
        }

        IFOK(err) {
            QString sql;
            if (categoriesUsed.count()) {
                sql = "DELETE FROM category WHERE t_fullname NOT IN ('" % categoriesUsed.join("','") % "')";
            } else {
                sql = "DELETE FROM category";
            }
            err = m_currentBankDocument->executeSqliteOrder(sql);
        }
    }

    // status bar
    IFOKDO(err, SKGError(0, i18nc("Successful message after an user action", "Unused categories deleted")))
    else {
        err.addError(ERR_FAIL, i18nc("Error message", "Unused categories deletion failed"));
    }

    // Display error
    SKGMainPanel::displayErrorMessage(err);
}
#include "skgcategoriesplugin.moc"
