#include "syntax.hpp"


// *** Parser (wraps tree-sitter)
Parser::Parser() {
    // Create a parser.
    parser = ts_parser_new();
}

Parser::~Parser() {
    ts_tree_delete(tree);
    // create cursor object
    ts_parser_delete(parser);
}

void Parser::setLang(TSLanguage* lang) {
    // Set the parser's language (JSON in this case).
    ts_parser_set_language(parser, lang);
    this->lang = lang;
    cursor = ts_query_cursor_new();
}

void Parser::setSource(QString source_code) {
    if (parser == NULL) { return; }
    tree = ts_parser_parse_string(
        parser,
        NULL,
        source_code.toStdString().c_str(),
        source_code.length()
    );
}

void Parser::update(QString source_code) {
    // There is a more efficient way to do this
    // rather than parsing the entire file again
    // but do the simplest thing that works first
    if (parser == NULL) { return; }
    if (tree == NULL) { 
        setSource(source_code);
        return;
    };
    tree = ts_parser_parse_string(
        parser,
        tree,
        source_code.toStdString().c_str(),
        source_code.length()
    );
}


// ***Highlighter (implements QSyntaxHighlighter)
#define grey00  0x151515
#define grey01  0x202020
#define grey02  0x303030
#define grey03  0x505050
#define grey04  0xb0b0b0
#define grey05  0xd0d0d0
#define grey06  0xe0e0e0
#define grey07  0xf5f5f5

#define Red      0xcc0000
#define lRed     0xff9999
#define Yellow   0xcc9900
#define lYellow  0xffffcc
#define Green    0x009900
#define lGreen   0xccffcc
#define Cyan     0x00ffcc
#define lCyan    0xccffff
#define Blue     0x3366ff
#define lBlue    0x99ccff
#define Magenta  0xa366ff
#define lMagenta 0xcc99ff

// TODO: create destructor to free all the Tree-sitter stuff
Highlighter::Highlighter(QTextDocument* parent, QString lang) : 
    QSyntaxHighlighter(parent) {
    // TODO: move this config to json
    //tcFormat.setBackground(QBrush(QColor(background)))
    //tcFormat.setFontItalic(True)
    //tcFormat.setFontWeight(100)
    QTextCharFormat numFmt;
    numFmt.setForeground(QBrush(QColor(lRed)));
    styles["number"] = numFmt;
    styles["integer"] = numFmt;
    styles["float"] = numFmt;

    QTextCharFormat keyFmt;
    keyFmt.setForeground(QBrush(QColor(Cyan)));
    styles["keyword"] = keyFmt;
    styles["keyword_identifier"] = keyFmt;
    // everybody

    QTextCharFormat strFmt;
    strFmt.setForeground(QBrush(QColor(Blue)));
    styles["string"] = strFmt;
    // python strings

    QTextCharFormat comFmt;
    comFmt.setForeground(QBrush(QColor(Green)));
    comFmt.setFontItalic(true);
    styles["comment"] = comFmt;
    // c & c++
    
    // preproc
    QTextCharFormat preFmt;
    preFmt.setForeground(QBrush(QColor(Yellow)));
    styles["macros"] = preFmt;

    if (lang == "c") {
        parser.setLang(tree_sitter_c());
    } else if (lang == "cpp") {
        parser.setLang(tree_sitter_cpp());
    } else if (lang == "javascript") {
        parser.setLang(tree_sitter_javascript());
    } else if (lang == "json") {
        parser.setLang(tree_sitter_json());
    } else if (lang == "lua") {
        parser.setLang(tree_sitter_lua());
    } else if (lang == "python") {
        parser.setLang(tree_sitter_python());
    } else {
        ERROR("Unsupported language: " << lang.toStdString());
    }
    curLang = lang;
    parser.setSource(parent->toPlainText());

    // load queries
    QString filename = lang+".scm";
    // TODO: don't hard code this path!
    QString query_str = loadResource("hl/"+filename);
    // create query object
    uint32_t error_offset;
    TSQueryError error_type;
    TSQuery* q = ts_query_new(parser.lang,
            query_str.toStdString().c_str(), query_str.length(), 
            &error_offset, &error_type);
    // handle any errors
    switch (error_type) {
        case TSQueryErrorNone:
            break;
        case TSQueryErrorSyntax:
            ERROR("Syntax error in highlights query: " << filename.toStdString()
                    << " at character " << error_offset);
            break;
        case TSQueryErrorNodeType:
            ERROR("Unknown Node Type in highlights query: " << filename.toStdString()
                    << " at character " << error_offset);
            break;
        case TSQueryErrorField:
            ERROR("Unknown field in highlights query: " << filename.toStdString()
                    << " at character " << error_offset);
            break;
        case TSQueryErrorCapture:
            ERROR("Invalid capture in highlights query: " << filename.toStdString()
                    << " at character " << error_offset);
            break;
        default:
            ERROR("Unknown error in highlights query: " << filename.toStdString());
    }
    hlQuery = q;
}

void Highlighter::setLanguage(const QString lang) {
    curLang = lang;
    parser.setSource(document()->toPlainText());
}

void Highlighter::highlightBlock(const QString&) {
    uint32_t start = currentBlock().position();
    uint32_t length = currentBlock().length();
    uint32_t end = start + length;
    // blocks are source lines (with platform line endings)
    // blocks may contain multiple visual lines due to wrapping.
    uint32_t row = currentBlock().blockNumber();
    uint32_t oldlength = 0;
    UserData* data = (UserData*) currentBlock().userData();
    if (data == NULL) {
        setCurrentBlockUserData(new UserData(length));
    } else {
        oldlength = data->length;
        data->length = length;
    }
    TSInputEdit e = {start, start+oldlength, end, 
        {row, 0}, {row, oldlength}, {row, length}};
    ts_tree_edit(parser.tree, &e);
    parser.update(document()->toPlainText());
    // walk the tree until a node that contains the current TextBlock is reached
    // format the TextBlock until a node outside the current TextBlock is reached
    TSNode root = ts_tree_root_node(parser.tree);
    //TSTreeCursor cursor = ts_tree_cursor_new(root);

    // run the highlights query for the setLanguage
    ts_query_cursor_exec(parser.cursor, hlQuery, root);
    /*
    typedef struct {
      TSNode node;
      uint32_t index;
    } TSQueryCapture;

    typedef struct {
      uint32_t id;
      uint16_t pattern_index;
      uint16_t capture_count;
      const TSQueryCapture *captures;
    } TSQueryMatch;*/

    // walk the query results
    TSQueryMatch match;
    while (ts_query_cursor_next_match(parser.cursor, &match)) {
        for (uint16_t i=0; i<match.capture_count; i++) {
            TSNode node = match.captures[i].node;
            uint32_t capInd = match.captures[i].index;
            uint32_t s = ts_node_start_byte(node);
            uint32_t e = ts_node_end_byte(node);
            // node is past the block or before start, ignore
            if (s >= end) { continue; }
            if (e <= start) { continue; }
            uint32_t l;
            QString t = ts_query_capture_name_for_id(hlQuery, capInd, &l);
            // we don't style this node type
            if (!styles.contains(t)) { continue; } 
            const QTextCharFormat fmt = styles[t];
            if (s < start) { s = start; }
            if (e > end) { e = end; }
            // setFormat acts relative to block start
            setFormat(s - start, e - s, fmt);
        }
    }
}

