/*
 * Copyright (c) 2018 Marco Maccaferri and others.
 * All rights reserved.
 *
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */

package com.maccasoft.tools.editor;

public class Z80TokenMarker extends TokenMarker {

    static {
        keywords.put("ADC", TokenId.Instruction);
        keywords.put("ADD", TokenId.Instruction);
        keywords.put("AND", TokenId.Instruction);
        keywords.put("BIT", TokenId.Instruction);
        keywords.put("CALL", TokenId.Instruction);
        keywords.put("CCF", TokenId.Instruction);
        keywords.put("CP", TokenId.Instruction);
        keywords.put("CPD", TokenId.Instruction);
        keywords.put("CPDR", TokenId.Instruction);
        keywords.put("CPI", TokenId.Instruction);
        keywords.put("CPIR", TokenId.Instruction);
        keywords.put("CPL", TokenId.Instruction);
        keywords.put("DAA", TokenId.Instruction);
        keywords.put("DEC", TokenId.Instruction);
        keywords.put("DI", TokenId.Instruction);
        keywords.put("DJNZ", TokenId.Instruction);
        keywords.put("EI", TokenId.Instruction);
        keywords.put("EX", TokenId.Instruction);
        keywords.put("EXX", TokenId.Instruction);
        keywords.put("HALT", TokenId.Instruction);
        keywords.put("IM", TokenId.Instruction);
        keywords.put("INC", TokenId.Instruction);
        keywords.put("IN", TokenId.Instruction);
        keywords.put("INI", TokenId.Instruction);
        keywords.put("IND", TokenId.Instruction);
        keywords.put("INIR", TokenId.Instruction);
        keywords.put("INDR", TokenId.Instruction);
        keywords.put("JR", TokenId.Instruction);
        keywords.put("JP", TokenId.Instruction);
        keywords.put("LD", TokenId.Instruction);
        keywords.put("LDI", TokenId.Instruction);
        keywords.put("LDIR", TokenId.Instruction);
        keywords.put("LDD", TokenId.Instruction);
        keywords.put("LDDR", TokenId.Instruction);
        keywords.put("NEG", TokenId.Instruction);
        keywords.put("NOP", TokenId.Instruction);
        keywords.put("OR", TokenId.Instruction);
        keywords.put("OUT", TokenId.Instruction);
        keywords.put("OUTI", TokenId.Instruction);
        keywords.put("OUTD", TokenId.Instruction);
        keywords.put("OTIR", TokenId.Instruction);
        keywords.put("OTDR", TokenId.Instruction);
        keywords.put("POP", TokenId.Instruction);
        keywords.put("PUSH", TokenId.Instruction);
        keywords.put("RES", TokenId.Instruction);
        keywords.put("RET", TokenId.Instruction);
        keywords.put("RETI", TokenId.Instruction);
        keywords.put("RETN", TokenId.Instruction);
        keywords.put("RL", TokenId.Instruction);
        keywords.put("RLA", TokenId.Instruction);
        keywords.put("RLC", TokenId.Instruction);
        keywords.put("RLCA", TokenId.Instruction);
        keywords.put("RLD", TokenId.Instruction);
        keywords.put("RR", TokenId.Instruction);
        keywords.put("RRA", TokenId.Instruction);
        keywords.put("RRC", TokenId.Instruction);
        keywords.put("RRCA", TokenId.Instruction);
        keywords.put("RRD", TokenId.Instruction);
        keywords.put("RST", TokenId.Instruction);
        keywords.put("SBC", TokenId.Instruction);
        keywords.put("SCF", TokenId.Instruction);
        keywords.put("SET", TokenId.Instruction);
        keywords.put("SLA", TokenId.Instruction);
        keywords.put("SLL", TokenId.Instruction);
        keywords.put("SRA", TokenId.Instruction);
        keywords.put("SRL", TokenId.Instruction);
        keywords.put("SUB", TokenId.Instruction);
        keywords.put("XOR", TokenId.Instruction);

        keywords.put("DB", TokenId.Directive2);
        keywords.put("DD", TokenId.Directive2);
        keywords.put("DS", TokenId.Directive2);
        keywords.put("DW", TokenId.Directive2);
        keywords.put("BYTE", TokenId.Directive2);
        keywords.put("TEXT", TokenId.Directive2);

        keywords.put("INCLUDE", TokenId.Directive1);
        keywords.put("EQU", TokenId.Directive1);
        keywords.put("ORG", TokenId.Directive2);
        keywords.put("ENDM", TokenId.Directive1);
        keywords.put("ENDP", TokenId.Directive1);
        keywords.put("ENDS", TokenId.Directive1);
        keywords.put("END", TokenId.Directive1);
        keywords.put("ENDIF", TokenId.Directive1);
        keywords.put("ELSE", TokenId.Directive1);
        keywords.put("ERROR", TokenId.Directive1);
        keywords.put("WARNING", TokenId.Directive1);
        keywords.put("FILL", TokenId.Directive1);

        keywords.put(".DB", TokenId.Directive2);
        keywords.put(".DD", TokenId.Directive2);
        keywords.put(".DS", TokenId.Directive2);
        keywords.put(".DW", TokenId.Directive2);
        keywords.put(".BYTE", TokenId.Directive2);
        keywords.put(".TEXT", TokenId.Directive2);

        keywords.put(".INCLUDE", TokenId.Directive1);
        keywords.put(".EQU", TokenId.Directive1);
        keywords.put(".ORG", TokenId.Directive2);
        keywords.put(".ENDM", TokenId.Directive1);
        keywords.put(".ENDP", TokenId.Directive1);
        keywords.put(".ENDS", TokenId.Directive1);
        keywords.put(".END", TokenId.Directive1);
        keywords.put(".ENDIF", TokenId.Directive1);
        keywords.put(".ELSE", TokenId.Directive1);
        keywords.put(".ERROR", TokenId.Directive1);
        keywords.put(".WARNING", TokenId.Directive1);
        keywords.put(".FILL", TokenId.Directive1);

        keywords.put("A", TokenId.Register);
        keywords.put("B", TokenId.Register);
        keywords.put("C", TokenId.Register);
        keywords.put("D", TokenId.Register);
        keywords.put("E", TokenId.Register);
        keywords.put("F", TokenId.Register);
        keywords.put("H", TokenId.Register);
        keywords.put("L", TokenId.Register);
        keywords.put("AF", TokenId.Register);
        keywords.put("BC", TokenId.Register);
        keywords.put("DE", TokenId.Register);
        keywords.put("HL", TokenId.Register);
        keywords.put("SP", TokenId.Register);
        keywords.put("IX", TokenId.Register);
        keywords.put("IY", TokenId.Register);
        keywords.put("I", TokenId.Register);
        keywords.put("R", TokenId.Register);

        keywords.put("Z", TokenId.Flag);
        keywords.put("NZ", TokenId.Flag);
        keywords.put("C", TokenId.Flag);
        keywords.put("NC", TokenId.Flag);
        keywords.put("PO", TokenId.Flag);
        keywords.put("PE", TokenId.Flag);
        keywords.put("P", TokenId.Flag);
        keywords.put("M", TokenId.Flag);
    }

    public Z80TokenMarker() {
    }

    @Override
    public Token markTokens(String text, int offset) {
        boolean backslash = false;

        TokenId token = TokenId.Null;
        firstToken = lastToken = null;
        lastOffset = lastKeyword = 0;

        int start = 0;

        for (int i = start; i < text.length(); i++) {
            int i1 = (i + 1);
            char c = text.charAt(i);

            if (c == '\\') {
                backslash = !backslash;
                continue;
            }

            switch (token) {
                case Null:
                    switch (c) {
                        case '\'':
                        case '"':
                            doKeyword(text, i, c);
                            if (backslash) {
                                backslash = false;
                            }
                            else {
                                addToken(i - lastOffset, token);
                                token = c == '"' ? TokenId.StringLiteral1 : TokenId.StringLiteral2;
                                lastOffset = lastKeyword = i;
                            }
                            break;
                        case ';': {
                            backslash = false;
                            doKeyword(text, i, c);
                            addToken(i - lastOffset, token);
                            int mlength = text.indexOf('\n', i);
                            if (mlength == -1) {
                                mlength = text.length();
                            }
                            addToken(mlength - i, TokenId.Comment);
                            lastOffset = lastKeyword = i = mlength;
                            break;
                        }
                        case '$':
                            backslash = false;
                            doKeyword(text, i, c);
                            addToken(i - lastOffset, token);

                            int mlength = i + 1;
                            while (mlength < text.length() && isHexDigit(text.charAt(mlength))) {
                                mlength++;
                            }
                            if ((mlength - i) > 1) {
                                addToken(mlength - i, TokenId.NumberLiteral);
                                lastOffset = lastKeyword = i = mlength;
                            }
                            break;
                        default:
                            backslash = false;
                            if (!Character.isLetterOrDigit(c) && c != '_' && c != '.') {
                                doKeyword(text, i, c);
                            }
                            break;
                    }
                    break;
                case StringLiteral1:
                    if (backslash) {
                        backslash = false;
                    }
                    else if (c == '"') {
                        addToken(i1 - lastOffset, token);
                        token = TokenId.Null;
                        lastOffset = lastKeyword = i1;
                    }
                    break;
                case StringLiteral2:
                    if (backslash) {
                        backslash = false;
                    }
                    else if (c == '\'') {
                        addToken(i1 - lastOffset, token);
                        token = TokenId.Null;
                        lastOffset = lastKeyword = i1;
                    }
                    break;
                default:
                    // Do nothing
                    break;
            }
        }

        if (token == TokenId.Null) {
            doKeyword(text, text.length(), '\0');
        }

        switch (token) {
            case StringLiteral1:
            case StringLiteral2:
                addToken(text.length() - lastOffset, null);
                token = null;
                break;
            default:
                addToken(text.length() - lastOffset, token);
                break;
        }

        return firstToken;
    }

}
