import collections
import csv
import editdistance as ed
import os
import re
from unidecode import unidecode

def load_data(data,enc='utf-8',translate_unicode=False):
    if type(data) is str:
        csv_data = []
        with open(data,'r',encoding = enc,errors = 'replace') as f:
            if translate_unicode == True:
                reader = csv.reader((line.encode().decode('unicode_escape').replace('\0','') for line in f)) #remove NULL bytes
            else:
                reader = csv.reader((line.replace('\0','') for line in f)) #remove NULL bytes
            for row in reader:
                if row != []:
                    csv_data.append(row)
        print('Data loaded from file "' + data + '".')
        return csv_data
    else:
        print('Data loaded.')
        return copy.deepcopy(data)
        
def get_geo_info(geo_input='',re_sub='',loc_index=None):
    geo_input_pp = re.sub(re_sub,'',unidecode(geo_input).lower().strip())
    curr_match = (None,1000)
    
    for i in loc_index:
        ed_match = ed.eval(geo_input_pp,i)
        if ed_match < curr_match[1]:
            curr_match = (i,ed_match)
    geodict = collections.OrderedDict(
        {'geo_input':geo_input,
        'geo_input_pp':geo_input_pp,
        'geo_input_match':curr_match[0],
        'geo_city':loc_index[curr_match[0]][0],
        'geo_subcountry':loc_index[curr_match[0]][1],
        'geo_country':loc_index[curr_match[0]][2],
        'ed_best_match':curr_match[1],
        'ed_tolerance':curr_match[1]/max([len(geo_input_pp),len(curr_match[0])])})
    return geodict
    
def resolve(geostring,max_tolerance=0.25):
    for i in geostring.results:
        if i['ed_tolerance'] >= max_tolerance:
            print('Tolerance between "' + i['geo_input'] + '" and "' + i['geo_input_match'] + '" (' + str(i['ed_tolerance']) + ') equals or exceeds max tolerance of',max_tolerance,'; removing...')
    geostring.results = [i for i in geostring.results if i['ed_tolerance'] < max_tolerance]
                
    if geostring.results == []:
        print('No results, Geostring object empty...')
        return
    else:
        resolved_location = collections.OrderedDict({'resolved_city':'','resolved_subcountry':'','resolved_country':''})
        if len(geostring.results) == 1:
            resolved_location['resolved_city'] = geostring.results[0]['geo_city']
            resolved_location['resolved_subcountry'] = geostring.results[0]['geo_subcountry']
            resolved_location['resolved_country'] = geostring.results[0]['geo_country']
            return resolved_location
        else:        
            all_locs = []
            all_locs.append([i['geo_city'] for i in geostring.results])
            all_locs.append([i['geo_subcountry'] for i in geostring.results])
            all_locs.append([i['geo_country'] for i in geostring.results])
        # vertical resolution: match within corresponding fields
        for n,i in enumerate(all_locs):
            loc_list = []
            for j in i:
                loc_list.extend(j.split('?'))
            top_locs = collections.Counter(loc_list).most_common()
            top_n = top_locs[0][1]
            resolved_locs = []
            for j in top_locs:
                if j[1] == top_n or n == 0:
                    resolved_locs.append(j[0])
            resolved_locs = '?'.join(resolved_locs)
            if n == 0:
                resolved_location['resolved_city'] = resolved_locs
            elif n == 1:
                resolved_location['resolved_subcountry'] = resolved_locs
            elif n == 2:
                resolved_location['resolved_country'] = resolved_locs
        #horizontal resolution: match across fields
        r_countries = resolved_location['resolved_country'].split('?')
        r_subcountries = resolved_location['resolved_subcountry'].split('?')
        
        r2_subc = []
        for rc in r_countries:
            r2_subc.extend([rl for rl in resolved_location['resolved_subcountry'].split('?') if rl != '' and rc in geostring.loc_index[re.sub(geostring.re_sub,'',unidecode(rl).lower().strip())][2]])
        resolved_location['resolved_subcountry'] = '?'.join(sorted(list(set(r2_subc))))
        
        r2_city = []
        for rc in r_subcountries:
            r2_city.extend([rl for rl in resolved_location['resolved_city'].split('?') if rl != '' and rc in geostring.loc_index[re.sub(geostring.re_sub,'',unidecode(rl).lower().strip())][1]])
        resolved_location['resolved_city'] = '?'.join(sorted(list(set(r2_city))))
        
        return resolved_location    
        
def create_loc_index(world_data_fn='world_places.csv',world_nick_fn='world_nicknames.csv',re_sub='[^a-z]'):
    mod_path = os.path.dirname(os.path.abspath(__file__)) + '/'
    world_data=load_data(mod_path + world_data_fn)
    city_index = {re.sub(re_sub,'',unidecode(i[0]).lower().strip()):['','',''] for i in world_data}
    subc_index = {re.sub(re_sub,'',unidecode(i[2]).lower().strip()):['','',''] for i in world_data}
    country_index = {re.sub(re_sub,'',unidecode(i[1]).lower().strip()):['','',''] for i in world_data}
    
    for i in world_data:
        city = re.sub(re_sub,'',unidecode(i[0]).lower().strip())
        subc = re.sub(re_sub,'',unidecode(i[2]).lower().strip())
        country = re.sub(re_sub,'',unidecode(i[1]).lower().strip())
        
        city_index[city][0] = i[0].lower()
        city_index[city][1] += "?" + i[2].lower().strip()
        city_index[city][2] += "?" + i[1].lower().strip()
        
        subc_index[subc][0] = ''
        subc_index[subc][1] = i[2].lower()
        subc_index[subc][2] += "?" + i[1].lower().strip()
        
        country_index[country][0] = ''
        country_index[country][1] = ''
        country_index[country][2] = i[1].lower().strip()
           
    loc_index = {}
    loc_index.update(city_index)
    subc_city_matches = (i for i in subc_index if i in city_index)
    for i in subc_city_matches:
        loc_index[i][1] += "?" + subc_index[i][1]
        loc_index[i][2] += "?" + subc_index[i][2]
    subc_city_nonmatches = {i:subc_index[i] for i in subc_index if i not in city_index}
    loc_index.update(subc_city_nonmatches)
    loc_index.update(country_index) 
    del loc_index['']
    
    for i in loc_index:
        if '?' in loc_index[i][1]:
            loc_index[i][1] = '?'.join(sorted(list(set(loc_index[i][1].split('?')))))
        if '?' in loc_index[i][2]:
            loc_index[i][2] = '?'.join(sorted(list(set(loc_index[i][2].split('?')))))
        if len(loc_index[i][1]) > 0 and loc_index[i][1][0] == '?':
            loc_index[i][1] = loc_index[i][1][1:]
        if len(loc_index[i][2]) > 0 and loc_index[i][2][0] == '?':
            loc_index[i][2] = loc_index[i][2][1:]

    loc_index['georgia'] = ['','georgia','georgia?united states']
    # add nickname data
    if world_nick_fn != '':
        nickname_data = load_data(mod_path + world_nick_fn)
        nickname_index = {re.sub(re_sub,'',unidecode(i[0]).lower().strip()):['','',''] for i in nickname_data}
        for i in nickname_data:
            nick = re.sub(re_sub,'',unidecode(i[0]).lower().strip())
            nickname_index[nick][0] += "?" + i[1].lower().strip()
            nickname_index[nick][1] += "?" + i[3].lower().strip()
            nickname_index[nick][2] += "?" + i[2].lower().strip()
            
        for i in nickname_index:
            for j in range(3):
                if '?' in nickname_index[i][j]:
                    nickname_index[i][j] = '?'.join(sorted(list(set(nickname_index[i][j].split('?')))))
                if len(nickname_index[i][j]) > 1 and nickname_index[i][j][0] == '?':
                    nickname_index[i][j] = nickname_index[i][j][1:]
                    
    loc_index.update(nickname_index)
    print('World index created.')
    return loc_index
        
class Geostring(object):
    loc_index = create_loc_index()
    def __init__(self,geo_input='',re_sub='[^a-z]',delimiters=[',',';','\|','&',' and ','/','\\\\'],loc_index=''):
        self.geo_input = geo_input
        self.re_sub = re_sub
        self.delimiters = delimiters
        self.results = []
        if loc_index == '':
            self.loc_index = Geostring.loc_index
        else:
            self.loc_index = loc_index
        if geo_input != '':
            delimiters = '|'.join(delimiters)
            geo_input = re.sub(delimiters,',',geo_input)
            for s in geo_input.split(','):
                self.results.append(get_geo_info(s,self.re_sub,self.loc_index))
