/*
 * Copyright (c) 2013 Mark Liversedge (liversedge@gmail.com)
 *
 * 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, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "GoldenCheetah.h"
#include "Settings.h"
#include "Colors.h"
#include "GcUpgrade.h"
#include "Athlete.h"
#include "VideoWindow.h"
#include "ErgFile.h"
#include "RideFile.h"
#include "JsonRideFile.h"
#include "Context.h"
#include "DataProcessor.h"

#include <QDebug>
#include <QMessageBox>
#include <QFileDialog>
#include <QWebFrame>
#include <QScrollBar>

bool
GcUpgrade::upgradeConfirmedByUser(const QDir &home)
{

    // since the upgrade can fail / multiple times, don't rely on the "lastUpdated" version,
    // but track the "upgrade success" separately in the settings on user level
    bool folderUpgradeSuccess =  appsettings->cvalue(home.dirName(), GC_UPGRADE_FOLDER_SUCCESS, false).toBool();

    //  reset upgrade flag - in case flag is set "true" and subfolder "/activities" and /config do NOT exist
    //  or - if they exists - do not contain any files, then something is wrong with the upgrade flag -
    //  (potentially due to a not fitting data restore)
    //  so let's reset the flag to "false" and run the upgrade again - this can never go wrong (if their is
    //  nothing to be upgrade - upgrade a success anyway

    AthleteDirectoryStructure newHome(home);
    if (folderUpgradeSuccess && !newHome.upgradedDirectoriesHaveData()) {
       folderUpgradeSuccess = false;
       appsettings->setCValue(home.dirName(), GC_UPGRADE_FOLDER_SUCCESS, false);
    }

    if (!folderUpgradeSuccess) {

        GcUpgradeExecuteDialog msgBox(home);
        if (msgBox.exec() == QDialog::Accepted) return true;

        // if not accepted
        return false;

    }

    return true; // if there is no upgrade needed, just proceed
}

int 
GcUpgrade::upgrade(const QDir &home)
{ 

    // what was the last version? -- do we need to upgrade?
    int last = appsettings->cvalue(home.dirName(), GC_VERSION_USED, 0).toInt();

    // Upgrade processing was introduced in Version 3 -- below must be performed
    // for athlete directories from prior to Version 3
    // and can essentially be used as a template for all major release
    // upgrades as it deletes old stuff and sets clean

    //----------------------------------------------------------------------
    // 3.0 upgrade processing
    //----------------------------------------------------------------------
    if (!last || last < VERSION3_BUILD) {

        // For now we always do the same thing
        // when we have some maturity with versions we may
        // choose to do different things
        if (last < VERSION3_BUILD) {

            // 1. Delete old files
            QStringList oldfiles;
            oldfiles << "*.cpi";
            oldfiles << "*.bak";
            foreach (QString oldfile, home.entryList(oldfiles, QDir::Files)) {
                QFile old(QString("%1/%2").arg(home.canonicalPath()).arg(oldfile));
                old.remove();

            }

            // 2. Remove old CLucece 'index'
            QFile index(QString("%1/index").arg(home.canonicalPath()));
            if (index.exists()) {
                removeIndex(index);
            }

            // 3. Remove metricDBv3 - force rebuild including the search index
            QFile db(QString("%1/metricDBv3").arg(home.canonicalPath()));
            if (db.exists()) db.remove();

            // 4. Set default weight to 75kg if currently zero
            double weight_ = appsettings->cvalue(home.dirName(), GC_WEIGHT, "75.0").toString().toDouble();
            if (weight_ <= 0.00) appsettings->setCValue(home.dirName(), GC_WEIGHT, "75.0");

            // 5. startup with common sidebars shown (less ugly)
            appsettings->setCValue(home.dirName(), "splitter/LTM/hide", true);
            appsettings->setCValue(home.dirName(), "splitter/LTM/hide/0", false);
            appsettings->setCValue(home.dirName(), "splitter/LTM/hide/1", false);
            appsettings->setCValue(home.dirName(), "splitter/LTM/hide/2", false);
            appsettings->setCValue(home.dirName(), "splitter/LTM/hide/3", true);
            appsettings->setCValue(home.dirName(), "splitter/analysis/hide", true);
            appsettings->setCValue(home.dirName(), "splitter/analysis/hide/0", false);
            appsettings->setCValue(home.dirName(), "splitter/analysis/hide/1", true);
            appsettings->setCValue(home.dirName(), "splitter/analysis/hide/2", false);
            appsettings->setCValue(home.dirName(), "splitter/analysis/hide/3", true);
            appsettings->setCValue(home.dirName(), "splitter/diary/hide", true);
            appsettings->setCValue(home.dirName(), "splitter/diary/hide/0", false);
            appsettings->setCValue(home.dirName(), "splitter/diary/hide/1", false);
            appsettings->setCValue(home.dirName(), "splitter/diary/hide/2", true);
            appsettings->setCValue(home.dirName(), "splitter/train/hide", true);
            appsettings->setCValue(home.dirName(), "splitter/train/hide/0", false);
            appsettings->setCValue(home.dirName(), "splitter/train/hide/1", false);
            appsettings->setCValue(home.dirName(), "splitter/train/hide/2", false);
            appsettings->setCValue(home.dirName(), "splitter/train/hide/3", false);

            // 6. Delete any old measures.xml -- its for withings only
            QFile msxml(QString("%1/measures.xml").arg(home.canonicalPath()));
            if (msxml.exists()) msxml.remove();

            // FINALLY -- Set latest version - so only tries to upgrade once
            appsettings->setCValue(home.dirName(), GC_VERSION_USED, VERSION_LATEST);
        }
    }

    // Versions after 3 should add their upgrade processing at this point
    // DO NOT CHANGE THE VERSION 3 UPGRADE PROCESS ABOVE, ADD TO IT BELOW

    //----------------------------------------------------------------------
    // 3.0 SP2 upgrade processing
    //----------------------------------------------------------------------
    if (last < VERSION3_SP2) {

        // 2. Remove old CLucece 'index'
        QFile index(QString("%1/index").arg(home.canonicalPath()));
        if (index.exists()) {
            removeIndex(index);
        }

        // 3. Remove metricDBv3 - force rebuild including the search index
        QFile db(QString("%1/metricDBv3").arg(home.canonicalPath()));
        if (db.exists()) db.remove();
    }

    //----------------------------------------------------------------------
    // 3.1 upgrade processing
    //----------------------------------------------------------------------

    if (last < VERSION31_BUILD) {

        // We sought to reset the user defaults in v3.1 to
        // move away from the ugly default used since GC first
        // released. This is the first time we actively applied
        // a new theme and color setting for users.

        // For a full breakdown of all activities applied in VERSION 3.1
        // they are listed in detail on the associated gitub issue:
        // see https://github.com/GoldenCheetah/GoldenCheetah/issues/883

        // 1. Delete all backup, CPX, Metrics and Lucene Index
        QStringList oldfiles;
        oldfiles << "*.cpi";
        oldfiles << "*.bak";
        foreach (QString oldfile, home.entryList(oldfiles, QDir::Files)) {
            QFile old(QString("%1/%2").arg(home.canonicalPath()).arg(oldfile));
            old.remove();
        }

        QFile index(QString("%1/index").arg(home.canonicalPath()));
        if (index.exists()) {
            removeIndex(index);
        }

        QFile db(QString("%1/metricDBv3").arg(home.canonicalPath()));
        if (db.exists()) db.remove();


        // 2. Remove any old charts.xml (it will be WRONG!)
        QFile charts(QString("%1/charts.xml").arg(home.canonicalPath()));
        if (charts.exists()) charts.remove();

        // 3. Reset colour defaults **
        GCColor::applyTheme(0); // set to default theme

        // 4. Theme and Chrome Color
        QString theme = "Flat";
        QColor chromeColor = QColor(0xec,0xec,0xec);
#ifdef Q_OS_MAC
        // Yosemite or earlier 
        if (QSysInfo::MacintoshVersion >= 12) {

            chromeColor = QColor(235,235,235);
        } else {

            // prior to Yosemite .. metallic
            theme = "Mac";
            chromeColor = QColor(215,215,215);
        }
#endif
        QString colorstring = QString("%1:%2:%3").arg(chromeColor.red())
                                                 .arg(chromeColor.green())
                                                 .arg(chromeColor.blue());
        appsettings->setValue("CCHROME", colorstring);
        GCColor::setColor(CCHROME, chromeColor);

        // 5. Metrics and Notes keywords
        QString filename = home.canonicalPath()+"/metadata.xml";
        if (QFile(filename).exists()) {

            QList<KeywordDefinition> keywordDefinitions;
            QList<FieldDefinition>   fieldDefinitions;
            QString colorfield;
            QList<DefaultDefinition> defaultDefinitions;

            // read em in
            RideMetadata::readXML(filename, keywordDefinitions, fieldDefinitions, colorfield, defaultDefinitions);

            bool updated=false;

            //
            // ADD METRICS TO METADATA TAB
            //
            int pos = -1;
            int indexTSS=-1, indexAnTISS=-1, indexAeTISS=-1;
            for(int i=0; i < fieldDefinitions.count(); i++) {

                // current ...
                FieldDefinition f = fieldDefinitions[i];

                if (f.tab == tr("Metric") && pos < 0) pos = i;
                if (f.name == "TSS") indexTSS=i;
                if (f.name == tr("Aerobic TISS")) indexAeTISS=i;
                if (f.name == tr("Anaerobic TISS")) indexAnTISS=i;
            }

            // ok, we need to add them to the metadata
            if (indexTSS < 0 || indexAnTISS < 0 || indexAeTISS < 0) {

                // lets add all at the same place
                if (indexTSS >= 0) pos = indexTSS;
                else if (indexAnTISS >= 0) pos = indexAnTISS;
                else if (indexAeTISS >= 0) pos = indexAeTISS;

                // ok, one at a time, using this as a template
                FieldDefinition add;
                add.tab = pos >= 0 ? fieldDefinitions[pos].tab : tr("Metric");
                add.diary = false;
                add.type = 4; // double

                // now set pos to non-negative if needed
                if (pos < 0) pos = 1;

                // add them
                if (indexAnTISS < 0) { 
                    add.name = tr("Anaerobic TISS");
                    fieldDefinitions.insert(pos, add);
                }
                if (indexAeTISS < 0) { 
                    add.name = tr("Aerobic TISS");
                    fieldDefinitions.insert(pos, add);
                }
                if (indexTSS < 0) { 
                    add.name = tr("TSS");
                    fieldDefinitions.insert(pos, add);
                }
                updated = true;
            }

            //
            // DEPRECATE 'default' color keyword and if needed
            // ADD 'Reverse' color keyword
            //
            int defaultIndex = -1, reverseIndex = -1;
            for(int i=0; i<keywordDefinitions.count(); i++) {
                if (keywordDefinitions[i].name == "Default") defaultIndex = i;
                if (keywordDefinitions[i].name == "Reverse") reverseIndex = i;
            }

            // no more default
            if (defaultIndex >= 0) {
                updated = true;
                keywordDefinitions.removeAt(defaultIndex);
            }

            // no reverse ?
            if (reverseIndex < 0) {
                updated = true;
                KeywordDefinition add;
                add.name = "Reverse";
                add.color = QColor(Qt::black);
                keywordDefinitions << add;
            }

            if (updated) {
                // write a new updated version
                RideMetadata::serialize(filename, keywordDefinitions, fieldDefinitions, colorfield, defaultDefinitions);
            }
        }

        // ** NOTE:
        // ** Suggestions to update CP/W'/Zones have been ignored due to the
        // ** high risk of breaking user setups -- this is due to the complexity
        // ** and multiple ways the user can manage their zones.

        // BELOW ARE PROBLEMATIC TOO:
        // ** there are no functions to read/write the layout.xml
        // ** files without refactoring HomeWindow to do so -- which
        // ** is a risky change and instead we will need the user
        // ** to reset their layout to get the latest chart setup:
        // Add a W'bal chart to the ride view
        // Add a CP History chart to the trend view
        // Add a Library chart to the trend view

        // PM deprecation has been handled by returning an LTM chart with
        // PMC curves when an PM chart is still in the layout.


    }

    //----------------------------------------------------------------------
    // 3.2 (formerly 3.11) upgrade processing
    //----------------------------------------------------------------------

    if (last < VERSION311_BUILD) {

        // add here the standard upgrade tasks

    }

    //----------------------------------------------------------------------
    // 3.2 new subfolder introduction and upgrade processing
    //----------------------------------------------------------------------


    // now the special "folder structure" upgrade - which is tracked separately on success

    bool folderUpgradeSuccess =  appsettings->cvalue(home.dirName(), GC_UPGRADE_FOLDER_SUCCESS, false).toBool();

    // now let's check if upgrade is necessary and do the job
    if (!folderUpgradeSuccess) {

        // initials logs,...
        errorCount = 0;
        upgradeLog = new GcUpgradeLogDialog(home);
        upgradeLog->show();

        // 1. create the new subDirs structure
        upgradeLog->append(tr("Start creating of: Directories... "),3);

        AthleteDirectoryStructure newHome(home);
        newHome.createAllSubdirs();

        if (!newHome.subDirsExist()) {
           upgradeLog->append(QString(tr("Error: Creation of subdirectories failed")),2);
           errorCount++;
        } else {
           upgradeLog->append(QString(tr("Creation of subdirectories successful")),2);
        }

        // 2. Delete all backup, CPX, Metrics and Lucene Index
        QStringList oldfiles;
        oldfiles << "*.cpi";
        oldfiles << "*.cpx";
        foreach (QString oldfile, home.entryList(oldfiles, QDir::Files)) {
            QFile old(QString("%1/%2").arg(home.canonicalPath()).arg(oldfile));
            old.remove();
        }

        QFile index(QString("%1/index").arg(home.canonicalPath()));
        if (index.exists()) {
            removeIndex(index);
        }

        QFile db(QString("%1/metricDBv3").arg(home.canonicalPath()));
        if (db.exists()) db.remove();

        //3. move the existing files to the new SubDirs
        //3.1 move config

        QStringList configFiles;
        configFiles << "*.xml";
        configFiles << "*.zones";
        configFiles << "avatar.*";
        upgradeLog->append(tr("Start copying of: Configuration files... "),3);

        int ok = 0; int fail = 0;
        foreach (QString configFile, newHome.root().entryList(configFiles, QDir::Files)) {
            bool success = moveFile(QString("%1/%2").arg(newHome.root().canonicalPath()).arg(configFile),
                         QString("%1/%2").arg(newHome.config().canonicalPath()).arg(configFile));
            if (success) {
                ok++;
            } else {
                fail++;
             }
        }
        errorCount += fail;
        upgradeLog->append(QString(tr("%1 configuration files moved to subdirectory: %2 - %3 failed" ))
                  .arg(QString::number(ok)).arg(newHome.config().dirName()).arg(QString::number(fail)),2);

        // 3.2 move the calendar
        QStringList calFiles;
        calFiles << "*.ics";
        upgradeLog->append(tr("Start copying of: Calendar files..."),3);

        ok = 0; fail = 0;
        foreach (QString calFile, newHome.root().entryList(calFiles, QDir::Files)) {
            bool success = moveFile(QString("%1/%2").arg(newHome.root().canonicalPath()).arg(calFile),
                     QString("%1/%2").arg(newHome.calendar().canonicalPath()).arg(calFile));
            if (success) {
                ok++;
            } else {
                fail++;
            }
        }
        errorCount += fail;
        upgradeLog->append(QString((tr("%1 calendar files moved to subdirectory: %2 - %3 failed"))
                  .arg(QString::number(ok)).arg(newHome.calendar().dirName()).arg(QString::number(fail))),2);

        // 3.3 move the logs
        QStringList logFiles;
        logFiles << "*.log";
        upgradeLog->append(tr("Start copying of: Log files..."),3);
        ok = 0; fail = 0;
        foreach (QString logFile, newHome.root().entryList(logFiles, QDir::Files)) {
            bool success = moveFile(QString("%1/%2").arg(newHome.root().canonicalPath()).arg(logFile),
                     QString("%1/%2").arg(newHome.logs().canonicalPath()).arg(logFile));
            if (success) {
                ok++;
            } else {
                fail++;

            }
        }
        errorCount += fail;
        upgradeLog->append(QString((tr("%1 log files moved to subdirectory: %2 - %3 failed"))
                  .arg(QString::number(ok)).arg(newHome.logs().dirName()).arg(QString::number(fail))),2);


        // 3.4 move the .JSON and .GC first
        QStringList jsonFiles;
        jsonFiles << "*.json";
        jsonFiles << "*.gc";
        upgradeLog->append(tr("Start copying of: Activity files (.JSON / .GC)..."),3);
        ok = 0; fail = 0;
        foreach (QString jsonFile, newHome.root().entryList(jsonFiles, QDir::Files)) {
            bool success = moveFile(QString("%1/%2").arg(newHome.root().canonicalPath()).arg(jsonFile),
                     QString("%1/%2").arg(newHome.activities().canonicalPath()).arg(jsonFile));
            if (success) {
                ok++;
            } else {
                fail++;
            }
        }
        errorCount += fail;
        upgradeLog->append(QString(tr("%1 activity (.JSON, .GC) files moved to subdirectory: %2 - %3 failed" ))
                  .arg(QString::number(ok)).arg(newHome.activities().dirName()).arg(QString::number(fail)),2);


        // 3.6 keep the .BAK files and store in /fileBackup()
        QStringList bakFiles;
        bakFiles << "*.bak";
        upgradeLog->append(tr("Start copying of: Activity files (.BAK)..."),3);
        ok = 0; fail = 0;
        foreach (QString bakFile, newHome.root().entryList(bakFiles, QDir::Files)) {
            bool success = moveFile(QString("%1/%2").arg(newHome.root().canonicalPath()).arg(bakFile),
                     QString("%1/%2").arg(newHome.fileBackup().canonicalPath()).arg(bakFile));
            if (success) {
                ok++;
            } else {
                fail++;
            }
        }
        errorCount += fail;
        upgradeLog->append(QString(tr("%1 activity backup (.BAK) files moved to subdirectory: %2 - %3 failed" ))
                  .arg(QString::number(ok)).arg(newHome.fileBackup().dirName()).arg(QString::number(fail)),2);

        // 3.6 now sort the rest of the files (extension checks are re-use)
        MediaHelper mediaFile;
        ok = 0; fail = 0;
        upgradeLog->append(tr("Start copying of: Media and Workout files... "),3);
        foreach (QString otherFile, newHome.root().entryList(QDir::Files)) {
            // check for workout and media files
            if (mediaFile.isMedia(otherFile) || ErgFile::isWorkout(otherFile)) {
                QDir r;
                bool success = moveFile(QString("%1/%2").arg(newHome.root().canonicalPath()).arg(otherFile),
                         QString("%1/%2").arg(newHome.workouts().canonicalPath()).arg(otherFile));
                if (success) {
                    ok++;
                } else {
                    fail++;
                }
            }
        }
        errorCount += fail;
        upgradeLog->append(QString(tr("%1 media and workout files moved to subdirectory: \%2 - %3 failed"))
                  .arg(QString::number(ok)).arg(newHome.workouts().dirName()).arg(QString::number(fail)),2);

        // the conversion of all activities to .json is done in "lateUpgrade" - since the prerequisites
        // on the "context" setup are not fulfilled at this early stage


    }

    // other 3.2 upgrade tasks, mostly cosmetic
    if (last < VERSION32_BUILD) {

        // trend plot matches ride plot, as newly introduced
        // just do for first time we run 3.2 and set to ride plot
        QColor color = GCColor::getColor(CRIDEPLOTBACKGROUND);
        GCColor::setColor(CTRENDPLOTBACKGROUND, color);

        // and update config
        QString colorstring = QString("%1:%2:%3").arg(color.red())
                                                 .arg(color.green())
                                                 .arg(color.blue());
        appsettings->setValue("COLORTRENDPLOTBACKGROUND", colorstring);

        // and on non-Mac platforms we want flat look and feel
        // by default now, the metal look is de-rigeur
#ifndef Q_OS_MAC
        color = QColor(0xe5,0xe5,0xe5);
        colorstring = QString("%1:%2:%3").arg(color.red())
                                         .arg(color.green())
                                         .arg(color.blue());
        appsettings->setValue("CCHROME", colorstring);
        appsettings->setValue(GC_CHROME, "Flat");
#endif

    }
    return 0;
}


int
GcUpgrade::upgradeLate(Context *context)
{
    // check the special "folder structure" upgrade - which is tracked separately on success
    bool folderUpgradeSuccess =  appsettings->cvalue(context->athlete->home->root().dirName(), GC_UPGRADE_FOLDER_SUCCESS, false).toBool();
    if (!folderUpgradeSuccess) {

        // switch off automatic Fix tools (
        DataProcessorFactory::instance().setAutoProcessRule(false);

        // 1. convert the rest of activities to .json and move the converted ones to /imports
        // prepare the suffixes for check of the files
        QStringList suffixList = RideFileFactory::instance().suffixes();
        QRegExp suffixes(QString("^(%1)$").arg(suffixList.join("|")));
        suffixes.setCaseSensitivity(Qt::CaseInsensitive);

        int ok = 0; int fail = 0; int okConvert = 0; int failConvert = 0;
        upgradeLog->append(tr("Start conversion of native activity files to GoldenCheetah .JSON format..."),3 );

        // GC file Name format
        QRegExp rx ("^((\\d\\d\\d\\d)_(\\d\\d)_(\\d\\d)_(\\d\\d)_(\\d\\d)_(\\d\\d))\\.(.+)$");
        bool fileNameValid;

        foreach (QString activitiesFileName, context->athlete->home->root().entryList(QDir::Files)) {

            QString fullFileName = context->athlete->home->root().canonicalPath() + "/" + activitiesFileName;
            QFileInfo fullFileInfo(fullFileName);

            if (fullFileInfo.exists() && fullFileInfo.isFile() && fullFileInfo.isReadable()) {

                if (suffixes.exactMatch(fullFileInfo.suffix()))   {
                    // We have an activity file which is NOT JSON or GC (since they have been moved already) - let's convert
                    QStringList errors;
                    QFile currentFile(fullFileName);

                    // Check if the filename is formattted according the GC filename format (since this format is used to determine
                    // the ride start date and time
                    fileNameValid = true;
                    if (!rx.exactMatch(fullFileInfo.fileName())) {
                        fileNameValid = false;
                    } else {
                        // format is fine, check if the file name really contains a valid date/time info
                        QDate date(rx.cap(2).toInt(), rx.cap(3).toInt(),rx.cap(4).toInt());
                        QTime time(rx.cap(5).toInt(), rx.cap(6).toInt(),rx.cap(7).toInt());

                        if (!(date.isValid() && time.isValid())) {
                            fileNameValid = false;
                        }
                    }

                    // only process if the file name contains a valid date/time - otherwise user needs to check
                    if (fileNameValid) {

                        RideFile *ride = RideFileFactory::instance().openRideFile(context, currentFile, errors);

                        // did it parse ok ? (all files here were alrady parsed when importing)
                        if (ride) {

                            // serialize
                            QString targetFileName = activitiesFileName;
                            int dot = targetFileName.lastIndexOf(".");
                            assert(dot >= 0);
                            targetFileName.truncate(dot);
                            targetFileName.append(".json");
                            // add Source File Tag + New File Name
                            ride->setTag("Source Filename", activitiesFileName);
                            ride->setTag("Filename", targetFileName);
                            JsonFileReader reader;
                            QFile target(context->athlete->home->activities().canonicalPath() + "/" + targetFileName);
                            reader.writeRideFile(context, ride, target);
                            okConvert++;
                            upgradeLog->append(tr("-> Information: Activity %1 - Successfully converted to .JSON").arg(activitiesFileName));

                            // copy source file to the /imports folder (only if conversion was successful)
                            bool success = moveFile(QString("%1/%2").arg(context->athlete->home->root().canonicalPath()).arg(activitiesFileName),
                                                    QString("%1/%2").arg(context->athlete->home->imports().canonicalPath()).arg(activitiesFileName));
                            if (success) {
                                ok++;
                            } else {
                                fail++;
                            }
                        } else {
                            failConvert++;
                            upgradeLog->append(tr("-> Error: Activity %1 - Conversion errors: ").arg(activitiesFileName),2);
                            foreach (QString error, errors) {
                                upgradeLog->append(tr("......... message(s) of .JSON conversion): ") + error);
                                upgradeLog->append("<br>");
                            }
                        }

                        // clear
                        delete ride;

                    } else {
                        failConvert++;
                        upgradeLog->append(tr("-> Error: Activity %1 - Invalid File Name (expected format 'YYYY_MM_DD_HH_MM_SS.%2')").arg(activitiesFileName).arg(fullFileInfo.suffix()),2);
                    }
                }

            } else {
                failConvert++;
                upgradeLog->append(tr("-> Error: Activity %1 - Problem reading file").arg(activitiesFileName),2);
            }
        }

        errorCount+=fail;
        errorCount+=failConvert;

        upgradeLog->append(QString(tr("%1 activity files converted to .JSON and stored in subdirectory: %2 - %3 failed" ))
                  .arg(QString::number(okConvert)).arg(context->athlete->home->activities().dirName()).arg(QString::number(failConvert)),2);

        upgradeLog->append(QString(tr("%1 converted activity source files moved to subdirectory: %2 - %3 failed" ))
                  .arg(QString::number(ok)).arg(context->athlete->home->imports().dirName()).arg(QString::number(fail)),2);

        // Total Number of errors
        if (errorCount == 0) {
           upgradeLog->append(QString(tr("Summary: No errors detected - upgrade successful" )),3);
           appsettings->setCValue(context->athlete->home->root().dirName(), GC_UPGRADE_FOLDER_SUCCESS, true);
        } else {
            upgradeLog->append(QString(tr("Summary: %1 errors detected - please check log details before proceeding" ))
                  .arg(QString::number(errorCount)),3);

            upgradeLog->append(QString(tr("<center><br>After choosing 'Proceed to Athlete', the system will open the athlete window using the "
                                          "converted data. Depending on the errors this might lead to follow-up errors and incomplete "
                                          "athlete data. You may either fix the error(s) in your directory directly, or go back "
                                          "to your last backup and correct the error(s) in the source data. <br>The upgrade process "
                                          "will be done again each time you open the athlete, until the conversion was "
                                          "successful - and had no more errors.</center>")),2);

            upgradeLog->append(QString(tr("<center><br>Latest information about possible upgrade problems and concepts to resolve them are available in the<br>"
                                         "<a href= \"https://github.com/GoldenCheetah/GoldenCheetah/wiki/Upgrade_v3.2_Troubleshooting_Guide\" target=\"_blank\">"
                                         "Upgrade v3.2 Troubleshooting Guide<a>")),1);

            // document that upgrade failed at least one time
            appsettings->setCValue(context->athlete->home->root().dirName(), GC_UPGRADE_FOLDER_SUCCESS, false);

        }

        // switch automatic Fix tools on again
        DataProcessorFactory::instance().setAutoProcessRule(true);

        // show upgrade log
        upgradeLog->enableButtons();
        upgradeLog->exec();

        // user can only select "Accept" to end with the upgrade step
        return 0;

    }

    return 0;
}

bool
GcUpgrade::moveFile(const QString &source, const QString &target) {

    QFile r(source);

    // first try it with a rename
    if (r.rename(target)) return true; // job is done

    // now the harder variant (copy & delete)
    if (r.copy(target))
    {
        // try to remove - but if this fails, no problem, file has been copied at least
        if (!r.remove()) {
            // log, even though it's not very critical it needs to be cleaned up - so report as error
            upgradeLog->append(QString(tr("-> Error: Deletion of copied file '%1' failed" )).arg(source),2);
            return false;
        } else {
            // copy & remove worked fine - we are done
            return true;
        }
    }

    // copying failed - so give up and report
    upgradeLog->append((tr("-> Error moving file : ") + source),2);
    return false;

}


class FileUtil
{
    public:
        static bool removeDir(const QString &dirName) {

            bool result = true;
            QDir dir(dirName);
 
            if (dir.exists(dirName)) {
                foreach(QFileInfo info, dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden  | QDir::AllDirs | QDir::Files, QDir::DirsFirst)) {
                    if (info.isDir()) {
                        result = FileUtil::removeDir(info.absoluteFilePath());

                    } else {

                        result = QFile::remove(info.absoluteFilePath());
                    }
 
                    if (!result) { return result; }

                }
                result = dir.rmdir(dirName);
            }
 
        return result;
    }

};


void
GcUpgrade::removeIndex(QFile &index)
{
    FileUtil::removeDir(index.fileName());
}


GcUpgradeExecuteDialog::GcUpgradeExecuteDialog(QDir athleteHomeDir) : QDialog(NULL, Qt::Dialog)
{

    const QString athlete = athleteHomeDir.dirName();

    setWindowTitle(QString(tr("Athlete %1").arg(athlete)));
    this->setMinimumWidth(550);

    QVBoxLayout *layout = new QVBoxLayout(this);

    QHBoxLayout *toprow = new QHBoxLayout;

    QPushButton *critical = new QPushButton(style()->standardIcon(QStyle::SP_MessageBoxWarning), "", this);
    critical->setFixedSize(80,80);
    critical->setFlat(true);
    critical->setIconSize(QSize(80,80));
    critical->setAutoFillBackground(false);
    critical->setFocusPolicy(Qt::NoFocus);

    QLabel *header = new QLabel(this);
    header->setWordWrap(true);
    header->setTextFormat(Qt::RichText);
    header->setText(QString(tr("<center><h2>Upgrade of Athlete:<br>%1<br></h2></center>")).arg(athlete));

    QLabel *text = new QLabel(this);
    text->setWordWrap(true);
    text->setTextFormat(Qt::RichText);
    text->setText(tr("<center><b>Backup your 'Athlete' data first!<br>"
                     "<b>Please read carefully before proceeding!</b></center> <br> <br>"
                     "With Version 3.2 the 'Athlete' directory has been refactored "
                     "by adding a set of subdirectories which hold the different types "
                     "of GoldenCheetah files.<br><br>"
                     "The new structure is:<br>"
                     "-> Activity files: <samp>/activities</samp><br>"
                     "-> Configuration files: <samp>/config</samp><br>"
                     "-> Download files: <samp>/downloads</samp><br>"
                     "-> Import files: <samp>/imports</samp><br>"
                     "-> Backups of Activity files: <samp>/bak</samp><br>"
                     "-> Workout related files: <samp>/workouts</samp><br>"
                     "-> Cache files: <samp>/cache</samp><br>"
                     "-> Calendar files: <samp>/calendar</samp><br>"
                     "-> Log files: <samp>/logs</samp><br>"
                     "-> Temp files: <samp>/temp</samp><br>"
                     "-> Temp for Activities: <samp>/tempActivities</samp><br>"
                     "-> Train View recordings: <samp>/recordings</samp><br>"
                     "-> Quarantined files: <samp>/quarantine</samp><br><br>"

                     "The upgrade process will create the new directory structure and move "
                     "the existing files to the new directories as needed. During the upgrade "
                     "all activity files will be converted to GoldenCheetah's native "
                     "file format .JSON and moved to the <br><samp>/activities</samp> folder. The source files "
                     "are moved to the <samp>/imports</samp> folder.<br><br>"
                     "Starting with version 3.2 all downloads from devices or imported "
                     "activity files will be converted to GoldenCheetah's file "
                     "format during import/download. The original files will be stored - depending on the source - "
                     "in <samp>/downloads</samp> or <br><samp>/imports</samp> folder.<br><br>"
                     "<center><b>Please make sure that you have done a backup of your athlete data "
                     "before proceeding with the upgrade. We can't take responsibility for "
                     "any loss of data during the process. </b> </center> <br>"
                     ));
    scrollText = new QScrollArea();
    scrollText->setWidget(text);

    QLabel *footer1 = new QLabel(this);
    footer1->setWordWrap(true);
    footer1->setTextFormat(Qt::RichText);
    footer1->setText(QString(tr("<center>Please backup the athlete directory:</center>")));

    QLabel *footer2 = new QLabel(this);
    footer2->setWordWrap(true);
    footer2->setTextFormat(Qt::RichText);
    footer2->setTextInteractionFlags(Qt::TextSelectableByMouse);
    footer2->setText(QString("<center><b>%1</b></center>").arg(athleteHomeDir.absolutePath()));

    toprow->addWidget(critical);
    toprow->addWidget(header);
    layout->addLayout(toprow);
    layout->addWidget(scrollText);
    layout->addWidget(footer1);
    layout->addWidget(footer2);


    QHBoxLayout *lastRow = new QHBoxLayout;

    proceedButton = new QPushButton(tr("Accept conditions and proceed with Upgrade"), this);
    proceedButton->setEnabled(true);
    connect(proceedButton, SIGNAL(clicked()), this, SLOT(accept()));
    abortButton = new QPushButton(tr("Abort Upgrade"), this);
    abortButton->setDefault(true);
    connect(abortButton, SIGNAL(clicked()), this, SLOT(reject()));


    lastRow->addWidget(abortButton);
    lastRow->addWidget(proceedButton);
    lastRow->addStretch();
    layout->addLayout(lastRow);

}



GcUpgradeLogDialog::GcUpgradeLogDialog(QDir homeDir) : QDialog(NULL, Qt::Dialog), home(homeDir)
{
    setAttribute(Qt::WA_DeleteOnClose, true);
    setWindowTitle(QString(tr("Athlete %1").arg(home.root().dirName())));

    QVBoxLayout *layout = new QVBoxLayout(this);

    QHBoxLayout *toprow = new QHBoxLayout;

    QPushButton *information = new QPushButton(style()->standardIcon(QStyle::SP_MessageBoxInformation), "", this);
    information->setFixedSize(60,60);
    information->setFlat(true);
    information->setIconSize(QSize(60,60));
    information->setAutoFillBackground(false);
    information->setFocusPolicy(Qt::NoFocus);

    QLabel *header = new QLabel(this);
    header->setWordWrap(true);
    header->setTextFormat(Qt::RichText);
    header->setText(tr("<h1>Upgrade log: GoldenCheetah v3.2</h1>"));

    toprow->addWidget(information);
    toprow->addWidget(header);
    layout->addLayout(toprow);

    report = new QWebView(this);
    report->setContentsMargins(0,0,0,0);
    report->page()->view()->setContentsMargins(0,0,0,0);
    report->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    report->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
    report->setContextMenuPolicy(Qt::NoContextMenu);
    report->setAcceptDrops(false);
    connect( report, SIGNAL( linkClicked( QUrl ) ), this, SLOT( linkClickedSlot( QUrl ) ) );

    QFont defaultFont; // mainwindow sets up the defaults.. we need to apply
    report->settings()->setFontSize(QWebSettings::DefaultFontSize, defaultFont.pointSize()+1);
    report->settings()->setFontFamily(QWebSettings::StandardFont, defaultFont.family());

    layout->addWidget(report);

    QHBoxLayout *lastRow = new QHBoxLayout;

    // create the buttons for the whole Dialog - but only make visible what is needed at the point in time
    proceedButton = new QPushButton(tr("Proceed to Athlete"), this);
    connect(proceedButton, SIGNAL(clicked()), this, SLOT(accept()));
    saveAsButton = new QPushButton(tr("Save Upgrade Report..."), this);
    saveAsButton->setDefault(true);
    connect(saveAsButton, SIGNAL(clicked()), this, SLOT(saveAs()));

    // during logging, disable the buttons, so user can't escape
    proceedButton->setDisabled(true);
    saveAsButton->setDisabled(true);


    lastRow->addWidget(saveAsButton);
    lastRow->addWidget(proceedButton);
    lastRow->addStretch();
    layout->addLayout(lastRow);

    // the cyclist...
    reportText += QString("<center><h1>Cyclist: \"%1\"</h1></center><br>").arg(home.root().dirName());
    report->page()->mainFrame()->setHtml(reportText);

}

void
GcUpgradeLogDialog::saveAs()
{
    QString fileName = QFileDialog::getSaveFileName( this, tr("Save Log"), QDir::homePath(), tr("Text File (*.txt)"));

    // now write to it
    QFile file(fileName);
    file.resize(0);
    QTextStream out(&file);
    out.setCodec("UTF-8");

    if (file.open(QIODevice::WriteOnly)) {

        // write the texts
        out << report->page()->mainFrame()->toPlainText();

    }
    file.close();
}

void
GcUpgradeLogDialog::linkClickedSlot( QUrl url )
{
    QDesktopServices::openUrl( url );

}


void
GcUpgradeLogDialog::append(QString text, int level) {

        switch (level) {
        case 3:
            reportText += QString("<h2>%1</h2>").arg(text);
            break;
        case 2:
            reportText += QString("<h3>%1</h3>").arg(text);
            break;
        case 1:
            reportText += QString("<h1>%1</h1>").arg(text);
            break;
        default: // any other
            reportText += QString("%1 <br>").arg(text);
        }
        report->page()->mainFrame()->setHtml(reportText);
        report->page()->mainFrame()->setScrollBarValue(Qt::Vertical, report->page()->mainFrame()->scrollBarMaximum(Qt::Vertical));
        this->repaint();
        QApplication::processEvents();

}

void
GcUpgradeLogDialog::enableButtons() {
    saveAsButton->setDisabled(false);
    proceedButton->setDisabled(false);

}
