#include "highlighter.h"
#include "AttributeLexer.h"
#include <iostream>
#include <sstream>

Highlighter::Highlighter(std::istream &file) {
    m_lexer = new XMLLexer(file);
    colormap[CONTENT] = Undefined; // The abstract base class shouldn't have default colors
    colormap[ELEMENT] = Undefined;
    colormap[ATTRIBUTE] = Undefined;
    colormap[ATTRIBURE_CONTENT] = Undefined;
    colormap[BRACKET] = Undefined;
    colormap[nonmatching] = Undefined;
    colormap[COMMENT] = Undefined;
}

Highlighter::~Highlighter() {
    delete m_lexer;
}

void Highlighter::process() {
    while (true) { // Wait until the NoMoreTokens exception is thrown
        try {
            XMLLexer::Token token = m_lexer->nextToken(); // Get the next token
            Token newtoken;
            newtoken.content = token.content;
            switch(token.type) {                    // setup local tokentype
                case XMLLexer::TokenType::CONTENT: 
                    newtoken.type = CONTENT;
                    break;
                case XMLLexer::TokenType::TAG: 
                    newtoken.type = TAG;
                    break;
                case XMLLexer::TokenType::COMMENT: 
                    newtoken.type = COMMENT;
                    break;
                default: 
                    newtoken.type = nonmatching;
                    break;
            }
            newtoken.color = colormap.find(newtoken.type)->second; // get the appropriate color
            m_tokens.push_back(newtoken);
        } catch (XMLLexer::NoMoreTokens &err) {
            break;                                      // We reached the end of the file
        } catch (XMLLexer::NoMatch& err) {      // No match was found, setup a new token with 1 character and tokentype nonmatching + skip this character   
            Token newtoken;
            newtoken.content = m_lexer->peek();
            m_lexer->skip(1);
            newtoken.type = nonmatching;
            m_tokens.push_back(newtoken);
        }
    }   
    auto tokens = std::move(m_tokens);
    m_tokens.clear();
    for(auto &tagtoken: tokens) {
        if(tagtoken.type == TAG && !tagtoken.content.empty()) {         // If the token was a tag, use a second lexer to find attributes etc.
            std::istringstream content(tagtoken.content);
            AttributeLexer attributelexer(content);
            while (true) {
                try {
                    AttributeLexer::Token token = attributelexer.nextToken();
                    Token newtoken;
                    newtoken.content = token.content;
                    switch(token.type) {
                        case AttributeLexer::TokenType::ELEMENT: 
                            newtoken.type = ELEMENT;
                            break;
                        case AttributeLexer::TokenType::BRACKET: 
                            newtoken.type = BRACKET;
                            break;
                        case AttributeLexer::TokenType::ATTRIBUTE: 
                            newtoken.type = ATTRIBUTE;
                            break;
                        case AttributeLexer::TokenType::ATTRIBUTE_CONTENT_DOUBLE_QUOTES: 
                            newtoken.type = ATTRIBURE_CONTENT;
                            break;
                        case AttributeLexer::TokenType::ATTRIBUTE_CONTENT_SINGLE_QUOTES: 
                            newtoken.type = ATTRIBURE_CONTENT;
                            break;
                        default: 
                            newtoken.type = nonmatching;
                            break;
                    }
                    newtoken.color = colormap.find(newtoken.type)->second;
                    m_tokens.push_back(newtoken);
                } catch (AttributeLexer::NoMoreTokens &err) {
                    break;
                } catch (AttributeLexer::NoMatch& err) {
                    Token newtoken;
                    newtoken.content = attributelexer.peek();
                    attributelexer.skip(1);
                    newtoken.type = nonmatching;
                    m_tokens.push_back(newtoken);
                }
            }    
        } else {
            m_tokens.push_back(tagtoken);
        }
    }
}

ConsoleHighlighter::ConsoleHighlighter(std::istream &file): Highlighter(file) {
    colormap[CONTENT] = White;                  // Fill in the colors we want to use for each tokentype
    colormap[ELEMENT] = Blue;                   // Change these for different colors
    colormap[TAG] = Magenta;
    colormap[ATTRIBUTE] = Yellow;
    colormap[ATTRIBURE_CONTENT] = Green;
    colormap[BRACKET] = Blue;
    colormap[nonmatching] = Black;
    colormap[COMMENT] = Black;
    process();
}

void ConsoleHighlighter::highlight(std::ostream &os) {
    for(auto &token: m_tokens) { // Go over all tokens and print them out with the right color
        switch(token.color) {
            case Yellow:
                os << "\033[1;33m" << token.content << "\033[0m";
                break;
            case Black:
                os << "\033[1;30m" << token.content << "\033[0m";
                break;
            case Red:
                os << "\033[1;31m" << token.content << "\033[0m";
                break;
            case Green:
                os << "\033[1;32m" << token.content << "\033[0m";
                break;
            case Blue:
                os << "\033[1;34m" << token.content << "\033[0m";
                break;
            case Magenta:
                os << "\033[1;35m" << token.content << "\033[0m";
                break;
            case Cyan:
                os << "\033[1;36m" << token.content << "\033[0m";
                break;
            case White:
                os << "\033[1;37m" << token.content << "\033[0m";
                break;
            default:
                os << token.content;
        }
    }
    os << std::endl;
}
