import csv
import numpy as np

from optparse import OptionParser
from collections import Counter

from sklearn.cluster import KMeans
from os.path import abspath, join, dirname

from context import *
from clustering.log import *

_default_minimum_cluster = 2
_default_maximum_cluster = 3


def set_dataset(row):
    return {
        'name': row[0],
        'type': int(row[1]),
        'feature': np.array(row[2:])
    }


def main(train_file, minimum_cluster, maximum_cluster, output_file):
    dataset = {}

    with open(train_file, 'r') as csvfile:
        reader = csv.reader(csvfile, delimiter=',', quoting=csv.QUOTE_MINIMAL)
        for index, row in enumerate(reader):
            if index > 0:
                dataset[index] = set_dataset(row)
    log().info("dataset load success. total={}".format(len(dataset)))
    feature = (np.array([row[1]['feature'] for row in dataset.items()]))
    label = (np.array([row[1]['type'] for row in dataset.items()]))

    best_cluster = {'c_number': 0, 'precision': 0}

    for i in range(minimum_cluster, maximum_cluster+1):
        tp = 0  # true-positive
        fp = 0  # false-positive
        tn = 0  # true-negative
        fn = 0  # true-positive
        c_result = {}

        kmeans = KMeans(n_clusters=i, random_state=0).fit(feature)
        labels = kmeans.labels_.tolist()

        # clustering result check using dicitonary
        for j in range(len(labels)):
            if labels[j] in c_result:
                c_result[labels[j]].append(label[j])
            else:
                c_result[labels[j]] = [label[j]]

        for k in range(i):
            try:
                each_c = Counter(c_result[k])  # each cluster bucket
                morb = max(each_c, key=each_c.get)  # malware or benign in each cluster     
                if morb == 0:
                    tn += each_c[0]
                    fp += each_c[1]
                # if morb is 1
                else:
                    tp += each_c[1]
                    fn += each_c[0]
            except Exception as e:
                log().error("{}, {}".format(type(e),
                                            e))
                pass

        accuracy = (tp + tn) / (fp + tp + fn + tn)

        log().debug("Running result of {} cluster".format(i))
        log().debug("tp: {} / fp: {} tn: {} / fn: {}".format(tp, fp, tn, fn))
        log().debug("%d cluster's Precision: %0.4f" %(i, accuracy))

        if best_cluster['precision'] < accuracy:
            best_cluster['precision'] = accuracy
            best_cluster['c_number'] = i

    log().info("="*58)
    log().info("Best of the Best cluster: {}".format(best_cluster['c_number']))
    log().info("Precision: {}".format(best_cluster['precision']))
    log().info("=" * 58)


def verify_options(options):
    if options.minimum_cluster is None:
        minimum_cluster = _default_minimum_cluster
    else:
        minimum_cluster = options.minimum_cluster
    if options.maximum_cluster is None:
        maximum_cluster = _default_maximum_cluster
    else:
        maximum_cluster = options.maximum_cluster
    if options.output_file is None:
        output_file = ''
    else:
        output_file = options.output_file
    return minimum_cluster, maximum_cluster, output_file


if __name__ == '__main__':
    opt_parser = OptionParser()
    opt_parser.add_option(
        '-c', '--csv', dest='train_file',
        help='csv input feature table.')
    opt_parser.add_option(
        '--min', dest='minimum_cluster', type=int,
        help='the number of minimum cluster (default: 2)')
    opt_parser.add_option(
        '--max', dest='maximum_cluster', type=int,
        help='the number of maximum cluster (default: 3)')
    opt_parser.add_option(
        '--output', dest='output_file',
        help='.csv output filename. silhouette result')

    options, _ = opt_parser.parse_args()
    if options.train_file is None:
        opt_parser.print_help()
        exit(-1)
    minimum_cluster, maximum_cluster, output_file = verify_options(options)
    main(options.train_file, minimum_cluster, maximum_cluster, output_file)
