#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Sun Oct 28 18:37:20 2018

@author: gykovacs

This script contains all the functions and codes we used to produce the
documentation pages. Before using the script, update the "results_path" variable
below to the path containing the cache file generated by the CacheAndValidate
object.
"""

# import SMOTE variants
import smote_variants as sv

# imbalanced databases
import imbalanced_databases as imbd

# some packages to transform the data
import numpy as np
import pandas as pd
import pickle

# path to the file generated by the CacheAndValidate object of the smote_variants package
results_path='/home/gykovacs/workspaces/smote_results/results.pickle'

foldings='https://drive.google.com/open?id=1PKw1vETVUzaToomio1-RGzJ9_-buYjOW'
raw_results='https://drive.google.com/open?id=12CfB3184nchLIwStaHhrjcQK7Ari18Mo'
agg_results='https://drive.google.com/open?id=19JGikRYXQ6-eOxaFVrqkF64zOCiSdT-j'

# the thresholds used to categorize the datasets
ir_threshold= 9
n_min_threshold= 30
n_attr_threshold= 10

category_mapping= {'NR': 'noise removal',
                   'DR': 'dimension reduction',
                   'Clas': 'uses classifier',
                   'SCmp': 'componentwise sampling',
                   'SCpy': 'sampling by cloning',
                   'SO': 'ordinary sampling',
                   'M': 'memetic',
                   'DE': 'density estimation',
                   'DB': 'density based',
                   'Ex': 'extensive',
                   'CM': 'changes majority',
                   'Clus': 'uses clustering',
                   'BL': 'borderline',
                   'A': 'application'}

def tokenize_bibtex(entry):
    """
    Tokenize bibtex entry string
    Args:
        entry(str): string of a bibtex entry
    Returns:
        dict: the bibtex entry
    """
    start= entry.find('{') + 1
    token_start= start
    quote_level= 0
    brace_level= 0
    tokens= []
    for i in range(start, len(entry)):
        if entry[i] == '"':
            quote_level= quote_level + 1
        if entry[i] == '{':
            brace_level= brace_level + 1
        if entry[i] == '}':
            brace_level= brace_level - 1
        if (entry[i] == ',' and brace_level == 0) or (entry[i] == '}' and brace_level < 0) and quote_level % 2 == 0:
            tokens.append(entry[token_start:(i)])
            token_start= i + 1

    result= {}
    result['key']= tokens[0].strip()
    for i in range(1, len(tokens)):
        splitted= tokens[i].strip().split('=', 1)
        if len(splitted) == 2:
            key= splitted[0].strip().lower()
            value= splitted[1].strip()[1:-1]
            result[key]= value

    if 'year' not in result:
        print("No year attribute in %s" % result['key'])

    return result

def extract_bibtex_entry(string, types= ['@article', '@inproceedings', '@book', '@unknown']):
    """
    Extract bibtex entry from string
    Args:
        string (str): string to process
        types (list(str)): types of bibtex entries to find
    Returns:
        dict: the dict of the bibtex entry
    """
    lowercase= string.lower()
    for t in types:
        t= t.lower()
        i= lowercase.find(t)
        if i >= 0:
            num_brace= 0
            for j in range(i+len(t), len(string)):
                if string[j] == '{':
                    num_brace+= 1
                if string[j] == '}':
                    num_brace-= 1
                if num_brace == 0:
                    be= tokenize_bibtex(string[i:(j+1)])
                    be['type']= t
                    return be
    return {}

def oversampling_bib_lookup():
    """
    Creates a bibtex lookup table for oversampling techniques based on
    the bibtex entries in the source code.

    Returns:
        dict: a lookup table for bibtex entries
    """
    oversamplers= sv.get_all_oversamplers()
    if sv.NoSMOTE in oversamplers:
        oversamplers.remove(sv.NoSMOTE)

    oversampling_bibtex= {o.__name__: extract_bibtex_entry(o.__doc__) for o in oversamplers}

    return oversampling_bibtex

def top_results_overall(databases= 'all', n_entries= 10):
    """
    Creates a table summarizing the overall performances
    Args:
        databases (str): 'all'/'high_ir'/'low_ir'/'high_n_min'/'low_n_min'/'high_n_attr'/'low_n_attr'
        n_entries (int): number of entries to show
    """
    results= pickle.load(open(results_path, 'rb'))

    oversampling_bibtex= oversampling_bib_lookup()

    if databases == 'high_ir':
        results= results[results['imbalanced_ratio'] > ir_threshold]
    elif databases == 'low_ir':
        results= results[results['imbalanced_ratio'] <= ir_threshold]
    elif databases == 'high_n_min':
        results= results[results['db_size']/(1.0 + results['imbalanced_ratio']) > n_min_threshold]
    elif databases == 'low_n_min':
        results= results[results['db_size']/(1.0 + results['imbalanced_ratio']) <= n_min_threshold]
    elif databases == 'high_n_attr':
        results= results[results['db_n_attr'] > n_attr_threshold]
    elif databases == 'low_n_attr':
        results= results[results['db_n_attr'] <= n_attr_threshold]

    results_agg= results.groupby(by='sampler').agg({'auc': np.mean, 'gacc': np.mean, 'f1': np.mean, 'p_top20': np.mean})
    results_agg= results_agg.reset_index()
    results_agg= results_agg[results_agg['sampler'] != 'NoSMOTE']

    results_rank= results_agg.rank(numeric_only= True, ascending= False)
    results_rank['sampler']= results_agg['sampler']
    results_rank['overall']= np.mean(results_rank[['auc', 'gacc', 'f1', 'p_top20']], axis= 1)
    results_rank.columns= ['rank_auc', 'rank_gacc', 'rank_f1', 'rank_ptop20', 'sampler', 'overall']
    results_agg['rank_auc']= results_rank['rank_auc']
    results_agg['rank_gacc']= results_rank['rank_gacc']
    results_agg['rank_f1']= results_rank['rank_f1']
    results_agg['rank_ptop20']= results_rank['rank_ptop20']
    results_agg['overall']= results_rank['overall']
    results_agg= results_agg.sort_values('overall')
    results_agg= results_agg[['sampler', 'overall', 'auc', 'rank_auc', 'gacc', 'rank_gacc', 'f1', 'rank_f1', 'p_top20', 'rank_ptop20']]
    results_agg= results_agg.reset_index(drop= True)
    results_agg.index= results_agg.index + 1
    final= results_agg.iloc[:n_entries]

    final['rank_auc']= final['rank_auc'].astype(int)
    final['rank_gacc']= final['rank_gacc'].astype(int)
    final['rank_f1']= final['rank_f1'].astype(int)
    final['rank_ptop20']= final['rank_ptop20'].astype(int)

    final['sampler']= final['sampler'].apply(lambda x: x.replace('_', '-') + ' cite(' + oversampling_bibtex[x]['key'] + '))')

    table= final.to_latex(float_format= lambda x: ('%.4f' % x).replace(' 0.', '.'))
    table= table.replace('cite(', '\\cite{')
    table= table.replace('))', '}')
    table= table.replace('\_', '_')
    print(table)

    return final

def create_documentation_page_os():
    oversamplers= sv.get_all_oversamplers()

    docs= "Oversamplers\n"
    docs= docs + "*"*len("Oversamplers") + "\n\n"

    for o in oversamplers:
        docs= docs + o.__name__ + "\n" + '-'*len(o.__name__) + "\n"
        docs= docs + "\n\n"
        docs= docs + "API\n"
        docs= docs + "^"*len("API") + "\n\n"
        docs= docs + ('.. autoclass:: smote_variants::%s' % o.__name__) + "\n"
        docs= docs + ('    :members:') + "\n"
        docs= docs + "\n"
        docs= docs + ('    .. automethod:: __init__')
        docs= docs + "\n\n"
        docs= docs + "Example\n"
        docs= docs + "^"*len("Example")
        docs= docs + "\n\n"
        docs= docs + ("    >>> oversampler= smote_variants.%s()\n" % o.__name__)
        docs= docs + "    >>> X_samp, y_samp= oversampler.sample(X, y)\n"
        docs= docs + "\n\n"
        docs= docs + ".. image:: figures/base.png" + "\n"
        docs= docs + (".. image:: figures/%s.png" % o.__name__) + "\n\n"
        docs= docs + o.__doc__.replace("\n    ", "\n")
        docs= docs + "\n\n"

    file= open("oversamplers.rst", "w")
    file.write(docs)
    file.close()

    return docs

def create_documentation_page_nf():
    noise_filters= sv.get_all_noisefilters()

    docs= "Noise filters and prototype selection\n"
    docs= docs + "*"*len("Noise filters and prototype selection") + "\n\n"

    for o in noise_filters:
        docs= docs + o.__name__ + "\n" + '='*len(o.__name__) + "\n"
        docs= docs + "\n\n"
        docs= docs + "API\n"
        docs= docs + "^"*len("API") + "\n\n"
        docs= docs + ('.. autoclass:: smote_variants.%s' % o.__name__) + "\n"
        docs= docs + ('    :members:') + "\n"
        docs= docs + "\n"
        docs= docs + ('    .. automethod:: __init__')
        docs= docs + "\n\n"
        docs= docs + "Example\n"
        docs= docs + "^"*len("Example")
        docs= docs + "\n\n"
        docs= docs + ("    >>> noise_filter= smote_variants.%s()\n" % o.__name__)
        docs= docs + "    >>> X_samp, y_samp= noise_filter.remove_noise(X, y)\n"
        docs= docs + "\n\n"
        docs= docs + ".. image:: figures/base.png" + "\n"
        docs= docs + (".. image:: figures/%s.png" % o.__name__) + "\n\n"
        docs= docs + o.__doc__.replace("\n    ", "\n")

    file= open("noise_filters.rst", "w")
    file.write(docs)
    file.close()

    return docs

def create_gallery_page():
    oversamplers= sv.get_all_oversamplers()
    noise_filters= sv.get_all_noisefilters()

    docs= "Gallery\n" + '*'*len('Gallery\n') + "\n\n"

    docs= docs + "In this page, we demonstrate the output of various oversampling \
                    and noise removal techniques, using default parameters.\n\n"
    docs= docs + "For binary oversampling and nosie removal, an artificial database was used, available in the ``utils` directory of the github repository.\n\n"
    #docs= docs + "For binary oversampling and noise removal, the figures can be reproduced by the ``ballpark_sample`` function using \
    #                a built-in or user definied dataset:\n\n"
    #docs= docs + ".. autofunction:: smote_variants.ballpark_sample\n\n"

    docs= docs + "For multiclass oversampling we have used the 'wine' dataset from \
                    ``sklearn.datasets``, which has 3 classes and many features, out \
                    which the first two coordinates have been used for visualization.\n\n"

    docs= docs + "Oversampling sample results\n"
    docs= docs + "="*len('Oversampling sample results\n') + "\n\n"

    docs= docs + "In the captions of the images some abbreviations \
                    referring to the operating principles are placed. Namely:\n\n"
    docs= docs + "    * NR: noise removal is involved\n"
    docs= docs + "    * DR: dimension reduction is applied\n"
    docs= docs + "    * Clas: some supervised classifier is used\n"
    docs= docs + "    * SCmp: sampling is carried out componentwise (attributewise)\n"
    docs= docs + "    * SCpy: sampling is carried out by copying instances\n"
    docs= docs + "    * SO: ordinary sampling (just like in SMOTE)\n"
    docs= docs + "    * M: memetic optimization is used\n"
    docs= docs + "    * DE: density estimation is used\n"
    docs= docs + "    * DB: density based - the sampling is based on a density of importance assigned to the instances\n"
    docs= docs + "    * Ex: the sampling is extensive - samples are added successively, not optimizing the holistic distribution of a given number of samples\n"
    docs= docs + "    * CM: changes majority - even majority samples can change\n"
    docs= docs + "    * Clus: uses some clustering technique\n"
    docs= docs + "    * BL: identifies and samples the neighborhoods of borderline samples\n"
    docs= docs + "    * A: developed for a specific application\n"

    docs= docs + "\n"
    docs= docs + ".. figure:: figures/base.png" + "\n\n\n"

    i= 0
    for o in oversamplers:
        docs= docs + (".. image:: figures/%s.png\n" % o.__name__)
        i= i + 1
        if i % 4 == 0:
            docs= docs + "\n"

    docs= docs + "Noise removal sample results\n"
    docs= docs + "="*len('Noise removal sample results\n') + "\n\n"

    docs= docs + ".. figure:: figures/base.png" + "\n\n\n"

    i= 0
    for n in noise_filters:
        docs= docs + (".. image:: figures/%s.png\n" % n.__name__)
        i= i + 1
        if i % 4 == 0:
            docs= docs + "\n"

    docs= docs + "Multiclass sample results\n"
    docs= docs + "="*len('Multiclass sample results\n') + "\n\n"

    docs= docs + ".. figure:: figures/multiclass-base.png" + "\n\n\n"

    oversamplers= [o for o in oversamplers if not sv.OverSampling.cat_changes_majority in o.categories and 'proportion' in o().get_params()]

    i= 0
    for o in oversamplers:
        docs= docs + (".. image:: figures/multiclass-%s.png\n" % o.__name__)
        i= i + 1
        if i % 4 == 0:
            docs= docs + "\n"

    file= open("gallery.rst", "w")
    file.write(docs)
    file.close()

    return docs

def create_ranking_page():
    final= top_results_overall()

    final['sampler']= final['sampler'].apply(lambda x: x.split(' ')[0])

    from tabulate import tabulate

    docs= "Ranking\n" + "*"*len("Ranking") + "\n\n"
    docs= docs + "Based on a thorough evaluation using 104 imbalanced datasets, the following 10 techniques provide the highest performance in terms of the AUC, GAcc, F1 and P20 scores, in nearest neighbors, support vector machine, decision tree and multilayer perceptron based classification scenarios.\n"
    docs= docs + "For more details on the evaluation methodology, see our paper on the comparative study.\n\n"

    docs= docs + tabulate(final.values, final.columns, tablefmt="rst")
    docs= docs + "\n\n"

    file= open("ranking.rst", "w")
    file.write(docs)
    file.close()

def create_readme_page():
    bibs= oversampling_bib_lookup()

    docs= """.. -*- mode: rst -*-

|TravisCI|_ |CircleCI|_ |Codecov|_ |ReadTheDocs|_ |PythonVersion|_ |PyPi|_ |Gitter|_

.. |TravisCI| image:: https://travis-ci.org/gykovacs/smote_variants.svg?branch=master
.. _TravisCI: https://travis-ci.org/gykovacs/smote_variants

.. |CircleCI| image:: https://circleci.com/gh/gykovacs/smote_variants.svg?style=svg
.. _CircleCI: https://circleci.com/gh/gykovacs/smote_variants

.. |Codecov| image:: https://codecov.io/gh/gykovacs/smote_variants/branch/master/graph/badge.svg
.. _Codecov: https://codecov.io/gh/gykovacs/smote_variants

.. |ReadTheDocs| image:: https://readthedocs.org/projects/smote-variants/badge/?version=latest
.. _ReadTheDocs: https://smote-variants.readthedocs.io/en/latest/?badge=latest

.. |PythonVersion| image:: https://img.shields.io/badge/python-3.5%20%7C%203.6%20%7C%203.7-green.svg
.. _PythonVersion: https://img.shields.io/badge/python-3.5%20%7C%203.6%20%7C%203.7-green.svg

.. |PyPi| image:: https://badge.fury.io/py/smote-variants.svg
.. _PyPi: https://badge.fury.io/py/smote-variants

.. |Gitter| image:: https://badges.gitter.im/smote_variants.svg
.. _Gitter: https://gitter.im/smote_variants?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
"""

    docs= docs + "\n\n" + "SMOTE-variants\n" + '='*len('SMOTE-variants') + "\n\n"

    docs= docs + "Introduction\n" + '-'*len('Introduction') + '\n\n'

    docs= docs + """The package implements 85 variants of the Synthetic Minority Oversampling Technique (SMOTE).
Besides the implementations, an easy to use model selection framework is supplied to enable
the rapid evaluation of oversampling techniques on unseen datasets.

The implemented techniques: %s\n\n""" % ", ".join(["[" + s + "]_ " for s in list(bibs.keys())])

    docs= docs + "Citation\n" + '-'*len('Citation') + '\n\n'

    docs= docs + "The publication of this work and its derivatives is going on, please come back in a couple of days or weeks for updates.\n\n"

    docs= docs + "Documentation\n" + '-'*len('Documentation') + '\n\n'

    docs= docs + "For a detailed documentation see http://smote-variants.readthedocs.io.\n\n"

    docs= docs + "Downloads\n" + '-'*len('Downloads') + '\n\n'

    docs= docs + ("* Database foldings: `%s <%s>`__\n" % (foldings, foldings))
    docs= docs + ("* Raw results: `%s <%s>`__\n" % (raw_results, raw_results))
    docs= docs + ("* Aggregated results: `%s <%s>`__\n\n" % (agg_results, agg_results))

    docs= docs + "References\n" + '-'*len('References') + '\n\n'

    for s in bibs:
        docs= docs + (".. [%s] " % s) + bibs[s]['author'] + (""", "%s" """ % bibs[s]['title'])
        if 'journal' in bibs[s]:
            docs= docs + (", %s" % bibs[s]['journal'])
        if 'booktitle' in bibs[s]:
            docs= docs + (", %s" % bibs[s]['booktitle'])
        if 'year' in bibs[s]:
            docs= docs + (", %s" % str(bibs[s]['year']))
        if 'pages' in bibs[s]:
            docs= docs + (", pp. %s" % str(bibs[s]['pages']))

        docs= docs + "\n\n"

    file= open("../README.rst", "w")
    file.write(docs)
    file.close()

def create_downloads_page():
    docs= "Downloads\n" + "*"*len("Ranking") + "\n\n"

    docs= docs + ("* Database foldings: `%s <%s>`__\n" % (foldings, foldings))
    docs= docs + ("* Raw results: `%s <%s>`__\n" % (raw_results, raw_results))
    docs= docs + ("* Aggregated results: `%s <%s>`__\n\n" % (agg_results, agg_results))

    file= open("downloads.rst", "w")
    file.write(docs)
    file.close()

def generate_doc_pages():
    create_documentation_page_os()
    #create_documentation_page_nf()
    create_gallery_page()
    #create_ranking_page()
    #create_readme_page()
    #create_downloads_page()

generate_doc_pages()
