/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

#include <string>
using std::string;

#include <QFile>
#include <QTextStream>
 
#include "LanguageHelper.h" 
#include "MacroHelper.h" 
#include "Highlighter.h"



// -----------------------------------------------------------------------
// ------------------- Syntax highlighter code ---------------------------
//        this class is used by the MacroHelper object, defined below
// -----------------------------------------------------------------------

class MacroHighlighter : public Highlighter
{
public:
     MacroHighlighter(QString &pathToFunctionList, QString &pathToMacroDeprecatedFunctionList, QTextDocument *parent = 0);
    ~MacroHighlighter(){}

     virtual void highlightBlock(const QString &text);

private:
    void LoadKeywordsFromFile (QString &fileName, QTextCharFormat &format);

protected:
     QTextCharFormat keywordFormat;
     QTextCharFormat classFormat;
     QTextCharFormat singleLineCommentFormat;
     QTextCharFormat quotationFormat;
     QTextCharFormat functionFormat; 
     QTextCharFormat numberFormat;
     QTextCharFormat builtinFunctionFormat;
     QTextCharFormat deprecatedFunctionFormat;


};


MacroHighlighter::MacroHighlighter(QString &pathToFunctionList, QString &pathToMacroDeprecatedFunctionList, QTextDocument *parent)
     : Highlighter(parent)
{
    HighlightingRule rule;

/*
        From NEdit:

        InlineString:"string inline":"end inline"::MvString1::\n\
        Comment:"#":"$"::MvComment::\n\
        DoubleQuoteString:"""":""""::MvString1::\n\
        SingleQuoteString:"'":"'"::MvString1::\n\
        Keywords:"<(and|by|export|for|function|global|extern|import|in|include|inline|nil|not|object|of|on|or|otherwise|repeat|task|tell|to|until|when|return|if|then|else|switch|case|while|end|do|loop|extern)>":::MvKeyword::\n\
        BuiltinFunctions:"<(|&|\\*|\\+|-|^|abs|accumulate|acos|addmonths|annotation|any|append|area|arguments|ascii|asin|atan|atan2|attributes|average|average_ew|average_ns|averageview|bar|bitmap|budget|bufr_timeseries|carousel|cleanfile|color|colour|colour_polar_field|colour_vector_field|compute|convolution|corr_a|cos|coslat|count|covar|covar_a|create_geo|cross_sect|curve|curveview|datacov_data|datacoverage|datainfo|date|day|definition|describe|det|device|dictionary|dimensions|direction|distance|distribution|divrot|divwind|dow|drawing_priority|dumpgrib|duplicate|ecfs|edit_map|emptyview|eps_metgram_coach|epsmetgram|epsplus|eqpott_m|eqpott_p|exist|exp|fail|fetch|fieldmean_timeseries|fieldpoint_timeseries|file|filter|find|float|frequencies|generate|geo_to_grib|geo_to_matrix|geopoint_timeseries|geosort|getenv|getksec1|getksec2|getksec3|getksec4|getrsec2|getrsec3|getrsec4|gfind|graph|grib_get_double|grib_get_long|grib_get_string|grib_set_double|grib_set_long|grib_to_geo|grib_to_vis5d|gribsetbits|gridvals|hhmm|hhmmss|hour|hovmoeller_area|hovmoeller_expand|hovmoeller_height|hovmoeller_line|hovmoellerview|icon|importview|in|input|inputwindow|int|intbits|integer|integrate|interpolate|inverse|juldate|julday|latitude|legendentry|length|level|line|list|log|log10|longitude|lookup|lowercase|macro_edit|magml|map_definition|mapview|mask|matrix|max|maxvalue|mean|mean_ew|menu|merge|metgram|metgram0|metgram_coach|metplus|min|minute|minvalue|month|name|nearest_gridpoint|nearest_gridpoint_info|nice|nobitmap|now|obsfilter|odb|offset|option_menu|output|overlay_control|page|parse|paxis|pcoast|pcont|percentile|pgraph|pimage|pimport|plot_page|plot_subpage|plot_superpage|plotwindow|pm_bufr_tephi|pm_grib_tephi|pmimage|pobs|polar_field|polar_vector|pott_m|pott_p|pre1995gribtephi|precision|pressure|print|print_menu|printdef|psymb|ptephi|ptext|putenv|putksec1|putksec2|putksec3|putksec4|putrsec2|putrsec3|putrsec4|pwind|random|read|read_g2|reduce|relhum|remove_duplicates|remove_missing_values|repeat|reprojection|retrieve|retrieve_g2|rmask|rms|round|satelliteview|scores|search|second|seqpott_m|seqpott_p|set_date|set_latitude|set_level|set_longitude|set_time|set_value|set_value2|setcurrent|sgn|shell|sin|sinlat|sleep|slider|sort|sort_and_indices|sort_indices|spec_contour|spec_graph|sqrt|stage|stations|stdev|stdev_a|stop|stopwatch_laptime|stopwatch_reset|stopwatch_start|stopwatch_stop|store|streamfn|subpage|subpagedetails|subsample|substring|sum|superpage|tan|tephigramview|textview|thickness|timer|tmpfile|tmpname|togrib|track|trajectory|transpose|type|unipressure|unithickness|univertint|uppercase|value|value2|values|var|var_a|variables|vector|vector_field|velpot|vert_prof|vertint|vertprofview|vis5d|vis5d_object|vis5d_settings|vis5d_trajectory|vis5d_wind_object|vis5d_window|webaccess|write|xs_average|xsectview|xy_vector|year|yymmdd|yyyymmdd|class|dialog|plot|runmode|number|string|mod|neg|setoutput|getoutput|newpage)>":::MvBuiltinFuns::\n\
        Numeric:"<((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f)?>":::MVNumericConst::\n\
        MacroLib:"<mml_\\w*>":::MvMacroLib::\n\
*/


    // Note that the order in which these highlighting rules are given
    // DOES matter! The rules seem to be applied in the order in which
    // they are given.

    //Functions - user defined
    /*functionFormat.setFontWeight(QFont::Bold);
    functionFormat.setForeground(QColor(236,47,33));
    rule.pattern = QRegExp("\\b[A-Za-z0-9_]+(?=\\()");
    rule.format = functionFormat;
    highlightingRules.append(rule);*/


    //Numerics
    //numberFormat.setForeground(QColor(193,63,65));
    numberFormat.setForeground(QColor(0,0,255));
    rule.pattern = QRegExp("\\b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\\.?[0-9]*)|(\\.[0-9]+))((e|E)(\\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f)?\\b");
    rule.format = numberFormat;
    highlightingRules.append(rule);



    //Built in functions - these are stored in a text file, so we read it in and parse it
    builtinFunctionFormat.setFontWeight(QFont::Bold);
    builtinFunctionFormat.setForeground(QColor(255,119,119));


    LoadKeywordsFromFile (pathToFunctionList, builtinFunctionFormat);



    // Deprecated functions - these are stored in a text file, so we read it in and parse it
    // - the font will be a bit darker than that for 'normal' functions, and also underlined
    deprecatedFunctionFormat = builtinFunctionFormat;
    deprecatedFunctionFormat.setForeground(builtinFunctionFormat.foreground().color().darker(115));
    deprecatedFunctionFormat.setUnderlineStyle(QTextCharFormat::SpellCheckUnderline	);
    deprecatedFunctionFormat.setToolTip(tr("Deprecated function"));  // the tool top seems not to work...

    LoadKeywordsFromFile (pathToMacroDeprecatedFunctionList, deprecatedFunctionFormat);




      //Keywords
    keywordFormat.setForeground(Qt::darkBlue);
    keywordFormat.setFontWeight(QFont::Bold);
 
    QStringList keywords;

    keywords << "and" << "by" << "export" << "for" << "function" << "global" << "extern" <<
                "import" << "in" << "include" << "inline" << "nil" <<
                "not" << "object" << "of" << "\\bon\\b" << "\\bor\\b" << "otherwise" << "repeat" << "task" <<
                "tell" << "to" << "until" << "when" << "return" << "if" << "then" << "else" << "switch" << 
                "case" << "while" << "end" << "do" << "loop" << "extern";

    foreach (QString word, keywords)
    {
        QString pattern = "\\b" + word + "\\b";
        rule.pattern = QRegExp(pattern);
        rule.format = keywordFormat;
        highlightingRules.append(rule);
    }




    // strings
    quotationFormat.setForeground(Qt::darkRed);
    rule.pattern = QRegExp("(\"[^\"]*\")|(\'[^\']*\')");
    rule.format = quotationFormat;
    highlightingRules.append(rule);


    //Comments
    singleLineCommentFormat.setFontItalic(true);
    singleLineCommentFormat.setForeground(Qt::darkGreen);
    rule.pattern = QRegExp("#[^\n]*");
    rule.format = singleLineCommentFormat;
    highlightingRules.append(rule);

}



void MacroHighlighter::highlightBlock(const QString &text)
{
    foreach (HighlightingRule rule, highlightingRules) 
    {
        QRegExp expression(rule.pattern);
        int index = text.indexOf(expression);
        while (index >= 0) 
        {
            int length = expression.matchedLength();
            setFormat(index, length, rule.format);
            index = text.indexOf(expression, index + length);
        }
    }
}



// ---------------------------------------------------------------------------
// MacroHighlighter::LoadKeywordsFromFile
// Loads a set of keywords from a given file, assuming one word per line.
// These are added to a new highlighting rule with the given char format.
// ---------------------------------------------------------------------------

void MacroHighlighter::LoadKeywordsFromFile (QString &fileName, QTextCharFormat &format)
{
    QFile file(fileName);

    if (file.open(QFile::ReadOnly | QFile::Text))  // try to open in read-only mode
    {
        HighlightingRule rule;
        QTextStream in(&file);

        string s;
        while (!file.atEnd())
        {
            QString s = file.readLine();
            s="\\b" + s.trimmed() + "\\b";  // note: the Qt docs say that trimmed() should not be necessary, but it is!
            rule.pattern = QRegExp(s);
            rule.format = format;
            highlightingRules.append(rule);
        }
    }
    
    else
    {
        /* QMessageBox::warning(this, tr("Application"),
                             tr("Cannot read file %1:\n%2.")
                             .arg(fileName)
                             .arg(file.errorString())); */
    }
}


// -----------------------------------------------------------------------
// -------------------------- MacroHelper code ---------------------------
// -----------------------------------------------------------------------


// ---------------------------------------------------------------------------
// MacroHelper::MacroHelper
// constructor
// ---------------------------------------------------------------------------

MacroHelper::MacroHelper()
{
    languageName_ = "Macro";
    className_    = "MACRO";
    serviceName_  = "macro";


    QString etcDir = getenv ("METVIEW_DIR_SHARE");

    if (!etcDir.isEmpty())
        etcDir += "/etc/";

    pathToMacroFunctionList_           = etcDir + "macro_built_in_functions.txt";
    pathToMacroDeprecatedFunctionList_ = etcDir + "macro_deprecated_functions.txt";
    pathToMacroTemplateList_           = etcDir + "macro_templates.txt";
    QString xmlParamFilename           = etcDir + "macro_icon_function_help.xml";

    verbFunctionHelp_ = new VerbFunctionHelp(xmlParamFilename);

}


// ---------------------------------------------------------------------------
// MacroHelper::~MacroHelper
// destructor
// ---------------------------------------------------------------------------

MacroHelper::~MacroHelper()
{
    delete verbFunctionHelp_;
}


// ---------------------------------------------------------------------------
// MacroHelper::createHighlighter
// creates a new syntax highlighter object
// ---------------------------------------------------------------------------

Highlighter *MacroHelper::createHighlighter(QTextDocument *parent)
{
    return new MacroHighlighter(pathToMacroFunctionList_, pathToMacroDeprecatedFunctionList_, parent);
}



// ---------------------------------------------------------------------------
// MacroHelper::isHelpAvailableForFunction
// returns a bool depending on whether the given string is the name of a
// function for which we have some help available
// ---------------------------------------------------------------------------

bool MacroHelper::isHelpAvailableForFunction(const QString &function)
{
//    return (function == "pcont");

    return verbFunctionHelp_->isHelpAvailableForFunction(function);
}



// ---------------------------------------------------------------------------
// MacroHelper::pathToFunctionList
// returns the path to where our list of macro functions is. This is generated
// in the constructor.
// ---------------------------------------------------------------------------

QString MacroHelper::pathToFunctionList()
{
    return pathToMacroFunctionList_;
}



// ---------------------------------------------------------------------------
// MacroHelper::pathToTemplateList
// returns the path to where our list of macro templates is. This is generated
// in the constructor.
// ---------------------------------------------------------------------------

QString MacroHelper::pathToTemplateList()
{
    return pathToMacroTemplateList_;
}



// ---------------------------------------------------------------------------
// MacroHelper::isHeaderLine
// decides whether the given line is a Macro header line
// ---------------------------------------------------------------------------

bool MacroHelper::isHeaderLine(const QString &text)
{
    return (text.contains("#") && 
            text.contains("Metview") &&
            text.contains("Macro"));
}

