#!/usr/bin/env python

import os
import re
import sys
import math
import json


class MapDoorText:
    number_map = {
        "a ": 1,
        "an ": 1,
        "the ": 1,
        "one ": 1,
        "two ": 2,
        "three ": 3,
        "four ": 4,
        "five ": 5,
        "six ": 6,
        "seven ": 7,
        "eight ": 8,
        "nine ": 9,
        "ten ": 10,
        "eleven ": 11,
        "twelve ": 12,
        "thirteen ": 13,
        "fourteen ": 14,
        "fifteen ": 15,
        "sixteen ": 16,
        "seventeen ": 17,
        "eighteen ": 18,
        "nineteen ": 19,
        "twenty ": 20,
        "twenty-one ": 21,
        "twenty-two ": 22,
        "twenty-three ": 23,
        "twenty-four ": 24,
        "twenty-five ": 25,
        "twenty-six ": 26,
        "twenty-seven ": 27,
        "twenty-eight ": 28,
        "twenty-nine ": 29,
        "thirty ": 30,
    }

    direction_map = {
        "north": "n",
        "northeast": "ne",
        "east": "e",
        "southeast": "se",
        "south": "s",
        "southwest": "sw",
        "west": "w",
        "northwest": "nw",
        "n": "n",
        "ne": "ne",
        "e": "e",
        "se": "se",
        "s": "s",
        "sw": "sw",
        "w": "w",
        "nw": "nw",
        "fore": "f",
        "aft": "a",
        "starboard": "s",
        "port": "p",
        "port aft": "pa",
        "port fore": "pf",
        "starboard aft": "sa",
        "starboard fore": "sf"
    }
    colour_map = {
        # http://terminal-color-builder.mudasobwa.ru/
        "orange": "\033[01;38;05;214m",
        "red": "\033[01;38;05;196m",
        "cyan": "\033[01;38;05;37m",
        "reset":  "\033[00;39;49m"
    }

    def __init__(self):
        self.return_value = []
        self.custom_matches = []

        # Load the JSON configuration file
        with open('mdtconfig.json', 'r') as config_file:
            config = json.load(config_file)

        # Load any additional custom matches
        with open('logs/features/mdt_custom_matches.json', 'r') as custom_file:
            additional_matches = json.load(custom_file)

        config['custom_matches'] = config['custom_matches'] + additional_matches;

        # Strip comments from custom matches
        for match in config['custom_matches']:
            if len(match) > 1:
                self.custom_matches.append(match)

        self.default_npc_value = config['default_npc_value']
        self.bonus_player_value = config['bonus_player_value']
        self.minimum_room_value = config['minimum_room_value']
        self.show_hidden_room_count = config['show_hidden_room_count']

    @staticmethod
    def explode(div, mdt):
        if div == '':
            return False
        pos, fragments = 0, []
        for m in re.finditer(div, mdt):
            fragments.append(mdt[pos:m.start()])
            pos = m.end()
        fragments.append(mdt[pos:])
        return fragments

    def parse_mdt(self, mdt_line):
        # Make lower case, do initial replacements
        mdt_line = mdt_line.lower()
        mdt_line = mdt_line.replace(" are ", " is ")
        mdt_line = mdt_line.replace(" black and white ", " black white ")
        mdt_line = mdt_line.replace(" brown and white ", " brown white ")
        mdt_line = mdt_line.replace("the limit of your vision is ", "the limit of your vision:")
        mdt_line = mdt_line.replace(" and ", ", ")
        mdt_line = mdt_line.replace(" is ", ", ")
        mdt_table = self.explode(', ', mdt_line)

        data = {
            'last_direction': '',
            'last_enemy_line': '',
            'last_count': 0,
            'last_was_dir': 0,
            'enemies_by_square': [],
            'ignoring_exits': False,
            'entity_table': [],
            'room_id': 1,
            'room_value': 0,
            'longest_direction': 0,
            'nothing': True,
            'next_color': ''
        }
        exit_strings = ['doors ', 'a door ', 'exits ', 'an exit ', 'a hard to see through exit ']

        for entry in mdt_table:
            if entry != "" and ' of a ' not in entry:
                if entry.startswith(tuple(exit_strings)):
                    # print('Special exit, ignore this line? next line is processed...')
                    data['ignoring_exits'] = True
                elif entry.startswith('the limit of your vision:'):
                    if data['last_count'] > 0:
                        this_square = [data['last_count'], data['last_direction'], data['room_id'], int(math.floor(data['room_value']))]
                        data['enemies_by_square'].append(this_square)
                        data['nothing'] = False
                        data['next_color'] = ''
                        data['room_id'] = data['room_id'] + 1
                        data['room_value'] = 0
                    data['last_direction'] = ''
                    data['last_enemy_line'] = ''
                    data['last_count'] = 0
                    data['last_was_dir'] = 0
                else:
                    # find the quantity first
                    quantity = 1
                    for nm_key in self.number_map:
                        if entry.startswith(nm_key):
                            quantity = self.number_map[nm_key]
                            entry = entry[len(nm_key):]
                            break

                    is_direction = 0
                    this_direction = ''

                    if entry.startswith("northeast"):
                        is_direction = 1
                        this_direction = "northeast"
                    elif entry.startswith("northwest"):
                        is_direction = 1
                        this_direction = "northwest"
                    elif entry.startswith("southeast"):
                        is_direction = 1
                        this_direction = "southeast"
                    elif entry.startswith("southwest"):
                        is_direction = 1
                        this_direction = "southwest"
                    elif entry.startswith("north"):
                        is_direction = 1
                        this_direction = "north"
                    elif entry.startswith("east"):
                        is_direction = 1
                        this_direction = "east"
                    elif entry.startswith("south"):
                        is_direction = 1
                        this_direction = "south"
                    elif entry.startswith("west"):
                        is_direction = 1
                        this_direction = "west"
                    elif entry.startswith("port fore"):
                        is_direction = 1
                        this_direction = "port fore"
                    elif entry.startswith("port aft"):
                        is_direction = 1
                        this_direction = "port aft"
                    elif entry.startswith("starboard fore"):
                        is_direction = 1
                        this_direction = "starboard fore"
                    elif entry.startswith("starboard aft"):
                        is_direction = 1
                        this_direction = "starboard aft"
                    elif entry.startswith("port"):
                        is_direction = 1
                        this_direction = "port"
                    elif entry.startswith("starboard"):
                        is_direction = 1
                        this_direction = "starboard"
                    elif entry.startswith("fore"):
                        is_direction = 1
                        this_direction = "fore"
                    elif entry.startswith("aft"):
                        is_direction = 1
                        this_direction = "aft"

                    if is_direction == 1:
                        if not data['ignoring_exits']:
                            # print('[handling direction, not exits]')
                            data['last_was_dir'] = 1

                            if data['last_direction'] != '':
                                data['last_direction'] = '{}, '.format(data['last_direction'])
                        
                            data['last_direction'] = '{}{} {}'.format(
                                data['last_direction'], quantity, self.direction_map[this_direction]
                            )
                        else:
                            # print('[ignoring exits direction line]')
                            pass
                    else:
                        data['ignoring_exits'] = False
                        if data['last_was_dir'] == 1:
                            # reset count
                            if data['last_count'] > 0:
                                this_square = [data['last_count'], data['last_direction'], data['room_id'], int(math.floor(data['room_value']))]
                                data['enemies_by_square'].append(this_square)
                                data['nothing'] = False
                                data['next_color'] = ''
                                data['room_id'] = data['room_id'] + 1
                                data['room_value'] = 0
                            data['last_direction'] = ''
                            data['last_enemy_line'] = ''
                            data['last_count'] = 0
                            data['last_was_dir'] = 0

                        data['next_color'] = ''
                        add_player_value = False

                        # Special GMCP MDT colour codes
                        if entry[0:6] == 'u001b[':
                            # u001b[38;5;37mRuhsbaaru001b[39;49mu001b[0m
                            here = entry.index('m')
                            data['next_color'] = entry[7:here]
                            # entry = entry[here + 1:-20]
                            # entry = entry.replace('u001b', '')
                            entry = entry.replace('u001b', '\033')

                            # Might be a second colour code for PK
                            if entry[0:6] == 'u001b[':
                                here = entry.index('m')
                                data['next_color'] = entry[7:here]
                                entry = entry[here + 1:-20]
                            add_player_value = True

                        this_value = self.default_npc_value

                        for custom_match in self.custom_matches:
                            if custom_match[3]:
                                # This is a regex match
                                rexp = re.compile(custom_match[0])
                                if rexp.match(entry):
                                    if custom_match[1] and custom_match[1] in self.colour_map:
                                        entry = '{}{}{}'.format(
                                            self.colour_map[custom_match[1]],
                                            entry,
                                            self.colour_map['reset']
                                        )
                                    this_value = custom_match[2]
                            else:
                                # This is a regular string match
                                if custom_match[0] in entry:
                                    if custom_match[1] and custom_match[1] in self.colour_map:
                                        entry = '{}{}{}'.format(
                                            self.colour_map[custom_match[1]],
                                            entry,
                                            self.colour_map['reset']
                                        )

                                    this_value = custom_match[2]

                        if add_player_value == True:
                            this_value = this_value + self.bonus_player_value

                        data['room_value'] = data['room_value'] + (this_value * quantity)

                        if quantity > 1:
                            entry = '{} {}'.format(quantity, entry)
                        data['entity_table'].append([data['room_id'], entry, data['next_color']])

                        data['last_count'] = data['last_count'] + quantity
                        if data['last_enemy_line'] != '':
                            data['last_enemy_line'] = '{}, '.format(data['last_enemy_line'])
                        data['last_enemy_line'] = '{}{}'.format(data['last_enemy_line'], entry)

        if data['nothing']:
            self.return_value.append('Nothing seen, try elsewhere!')
        else:
            done_here = False
            rooms_ignored = 0

            data['enemies_by_square'].sort(key=lambda square: square[3])

            # Grab shortest
            for square in data['enemies_by_square']:
                # Only show if this room meets the minimum value
                if square[3] >= self.minimum_room_value and len(square[1]) > data['longest_direction']:
                    data['longest_direction'] = len(square[1])

            for square in data['enemies_by_square']:
                # Only show if this room meets the minimum value
                if square[3] >= self.minimum_room_value:
                    done_here = False

                    # add colour to points output
                    square[3] = '{}{}{}'.format(self.colour_map['cyan'], square[3], self.colour_map['reset'])
                    fstring = '{{:<{}}} [{{}}] '.format(data['longest_direction'])
                    output = fstring.format(square[1], square[3])
                    for entity in data['entity_table']:
                        if entity[0] == square[2]:
                            if done_here:
                                output = '{}, '.format(output)
                            output = '{}{}'.format(output, entity[1])
                            done_here = True
                    if square[0] < 2:
                        output = '{} [{} thing]'.format(output, square[0])
                    else:
                        output = '{} [{} things]'.format(output, square[0])
                    self.return_value.append(output)
                else:
                    rooms_ignored = rooms_ignored + 1

                square = None

            for entity in data['entity_table']:
                entity = None

            if rooms_ignored > 0 and self.show_hidden_room_count:
                output = '({} rooms below your value limit of {})'.format(rooms_ignored, self.minimum_room_value)
                self.return_value.append(output)


if __name__ == '__main__':
    if len(sys.argv) < 2:
        print('[error] No input provided.')
        sys.exit()

    argument, mdt_line = sys.argv.pop(1), None

    # Is this a file passed to us?
    if os.path.exists(argument):
        with open(argument, 'r') as f:
            mdt_line = f.readline()

    else:
        mdt_line = argument

    mdt = MapDoorText()
    mdt.parse_mdt(mdt_line)

    for line in mdt.return_value:
        print(line)
