import pandas as pd
import numpy as np
pd.set_option("display.max_columns", None)

from os import listdir
from os.path import isfile, join

import Levenshtein as lev
import re


def intersection_over_union( str1, str2 ):
    
    ''' 
    Calculates the IOU score (https://en.wikipedia.org/wiki/Jaccard_index)
    Considers both strings as sets of characters.
    '''
    iou_score = len(set(str1).intersection(set(str2))) / len(set(str1).union(set(str2)))
    
    return iou_score


def typo_matcher( typo_dict, iou_val, lev_dist, col_name, key_col ):
    
    ''' Check if certain 'col_name' can be mapped to 'key_col' in the 'typo_dict'
    
    We use 3 conditions for matching:
    1) IOU score > some_val (0.75)
    2) Levenshtein distance > some_val (5) (https://en.wikipedia.org/wiki/Levenshtein_distance)
    3) Length of words to be matched > 2
    
    '''
    
    # CREATE A FLAG TO INDICATE IF A TYPO WAS MAPPED TO A KEY IN THE TYPO_DICT
    typo_flag = False
    
    '''
    IF col_name == key_col, no need to check matching
        return
    ELSE check matching
        return
    '''
    if col_name == key_col:
        typo_flag = True
        return typo_dict, typo_flag
      
    elif intersection_over_union(col_name, key_col) > iou_val:
        if lev.distance(col_name, key_col) < lev_dist:
            if  len(key_col)>2 and len(col_name)>2:
                
                if key_col not in typo_dict.keys():
                    print('please check - new key col added:',key_col)
                    print('----------------')
                    typo_dict[key_col] = []
            
                typo_dict[key_col].append(col_name)
                
                typo_flag = True

    
    return typo_dict, typo_flag


def map_known_typos(df):
    
    '''
    Map columns in dataframe to common typos found for
    1) variable name time
    2) construct name
    3) english translation
    
    Returns: known_typo_dict
    '''
    
    iou_val = 0.75  # Used to calculate the IOU score (https://en.wikipedia.org/wiki/Jaccard_index)
    lev_dist = 5    # Used to calculate levenshtein/edit distance (https://en.wikipedia.org/wiki/Levenshtein_distance)
    
    '''
    INITIALIZE TYPO DICTIONARY
    '''
    typo_dict = {}
    for i in range(1,8):
        typo_dict['variable name time '+str(i)] = []
        
    for i in range(1,8):
        typo_dict['construct name time '+str(i)] = []

    typo_dict['english translation'] = [] 
    
    '''
    Iterate over all columns in combined dataframe
    '''
    
    for col_name in sorted(df.columns):
        
        if col_name in typo_dict.keys():
            '''
            IF col_name is already a key in typo_dict:
                SKIP to next iteration
            '''
            continue
         
        '''
        Check similarity with 'english translation'
        '''
        var_key = 'english translation'
        typo_dict, typo_flag = typo_matcher(typo_dict, iou_val, lev_dist, col_name, var_key)
        if typo_flag == True:
            continue
            
        '''
        If above typo_flag is false we reach following code block.
        Extract numbers from string to map to appropriate key
        '''
        nums = re.findall(r'\d+', col_name) #re.findall(r'[\d\.\d]+',col_name)
        
        
        ##### Check based on number extraction
        
        if len(nums) == 0:
            continue
        
        elif len(nums) > 1:
            print('------- Number extraction exception: More than 1 number extracted ---------')
            print('colname:', col_name, ' -- extracted nums:', nums)
            print('---------------------------------------------------------------------------')
            print()
            continue
        
        elif len(nums) == 1:
        
            '''
            Check similarity with 'variable name time'
            '''
            var_key = 'variable name time ' + str(nums[0]) 
            
            typo_dict, typo_flag = typo_matcher(typo_dict, iou_val, lev_dist, col_name, var_key)
            if typo_flag == True:
                continue
            
            '''
            Check similarity with 'construct name time'
            '''
            var_key = 'construct name time ' + str(nums[0]) 
            
            typo_dict, typo_flag = typo_matcher(typo_dict, iou_val, lev_dist, col_name, var_key)
            
                
    return typo_dict


def map_unknown_typos(df, known_typo_dict):
    
    '''Look for possible unknown typos in dataframe columns
    
    We do not consider typos that are already present in the known_typo_dict
   
    Returns: unknown_typo dict
    '''
    
    iou_val = 0.7   # Used to calculate the IOU score (https://en.wikipedia.org/wiki/Jaccard_index)
    lev_dist = 5    # Used to calculate levenshtein/edit distance (https://en.wikipedia.org/wiki/Levenshtein_distance)
    
    typo_dict = {}
    
    for col_name in sorted(df.columns):
        
        if (col_name in known_typo_dict.keys()) or ([col_name] in known_typo_dict.values()):
            continue
        
        insertion_flag = False

        # Check if any mapping can be done. Insert if mapped.
        # Iterate over all existing keys
        for key_col in typo_dict.keys():
            
            typo_dict, insertion_flag = typo_matcher(typo_dict, iou_val, lev_dist, col_name, key_col)
            
            if insertion_flag == True:
                break
                
        if insertion_flag == False:
            typo_dict[col_name] = []  
                
    
    return typo_dict
