#!/usr/bin/python3
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2019 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import base64
import csv
import io
import os
import numpy as np
import sys

try:
    import sixel

    class DumbWriter(sixel.SixelWriter):
        def restore_position(self, output):
            return
except ImportError:
    pass


def iterm_draw(data):
    databuf = data.getbuffer()
    datalen = len(databuf)
    data = base64.b64encode(databuf).decode('utf8')
    sys.stdout.write(
        '\x1b]1337;File=inline=1;size={}:'.format(datalen))
    sys.stdout.write(data)
    sys.stdout.write('\a')
    sys.stdout.write('\n')
    sys.stdout.flush()


def kitty_draw(data):
    data = base64.b64encode(data.getbuffer())
    while data:
        chunk, data = data[:4096], data[4096:]
        m = 1 if data else 0
        sys.stdout.write('\x1b_Ga=T,f=100,m={};'.format(m))
        sys.stdout.write(chunk.decode('utf8'))
        sys.stdout.write('\x1b\\')
        sys.stdout.flush()
    sys.stdout.write('\n')


def plot(gui, output, plotdata, bins, fmt):
    import matplotlib as mpl
    if gui and mpl.get_backend() == 'agg':
        sys.stderr.write('Error: No GUI backend available and -g specified!\n')
    if not gui:
        mpl.use('Agg')
    import matplotlib.pyplot as plt
    n, bins, patches = plt.hist(plotdata, bins)
    plt.show()
    if not gui:
        if output:
            tdata = output
        else:
            tdata = io.BytesIO()
        plt.savefig(tdata)
    if not gui and not output:
        if fmt == 'environment':
            fmt = os.environ.get('CONFLUENT_IMAGE_PROTOCOL', 'kitty')
        if fmt == 'sixel':
            writer = DumbWriter()
            writer.draw(tdata)
        elif fmt == 'kitty':
            kitty_draw(tdata)
        elif fmt == 'iterm':
            iterm_draw(tdata)
    return n, bins

def textplot(plotdata, bins):
    n, bins = np.histogram(plotdata, bins)
    labels = []
    for bin in bins:
        labels.append('{0:0.1f}'.format(bin))
    width = 80
    # Since this will be primarily piped into, hard to get 
    # terminal width
    labelwidth = 0
    for lab in labels:
        if len(lab) > labelwidth:
            labelwidth = len(lab)
    width -= (labelwidth) + 1
    labelfmt = '{{0:>{0}s}}|'.format(labelwidth)
    maxn = 0.0
    for lgth in n:
        if lgth > maxn:
            maxn = float(lgth)
    for i in range(len(n)):
        print(labelfmt.format(labels[i]) + '=' * int(np.round((n[i]/maxn) * width)))
    return n, bins

histogram = False
aparser = argparse.ArgumentParser(description='Quick access to common statistics')
aparser.add_argument('-c', type=int, default=0, help='Column number to analyze (default is last column)')
aparser.add_argument('-d', default=None, help='Value used to separate columns')
aparser.add_argument('-x', default=False, action='store_true', help='Output histogram in graphical format')
aparser.add_argument('-f', default='environment', help='Format for histogram output (sixel/iterm/kitty)')
aparser.add_argument('-s', default=0, help='Number of header lines to skip before processing')
aparser.add_argument('-g', default=False, action='store_true', help='Open histogram in separate graphical window')
aparser.add_argument('-o', default=None, help='Output histogram to the specified filename in PNG format')
aparser.add_argument('-t', default=False, action='store_true', help='Output a histogram in text format')
aparser.add_argument('-v', default=False, action='store_true', help='Attempt to list nodes relevant to each histogram bar (requires -s, -o, or -t)')
aparser.add_argument('-b', type=int, default=10, help='Number of bins to use in histogram (default is 10)')
args = aparser.parse_args(sys.argv[1:])
plotdata = []
headlines = int(args.s)
while headlines >= 0:
    data = sys.stdin.readline()
    headlines -= 1
if args.d:
    delimiter = args.d
else:
    if '\t' in data:
        delimiter = '\t'
    elif ' ' in data:
        delimiter = ' '
    elif ',' in data:
        delimiter = ','
    else:
        delimiter = ' '  # handle single column
data = list(csv.reader([data], delimiter=delimiter))[0]
nodebydatum = {}
idx = args.c - 1
autoidx = False
while data:
    node = None
    if ':' in data[0]:
        node, data[0] = data[0].split(':', 1)
    else:
        node = data[0]
    if idx == -1 and not autoidx:
        while not autoidx:
            try:
                datum = float(data[idx])
            except ValueError:
                idx -= 1
                continue
            except IndexError:
                sys.stderr.write('Unable to identify a numerical column\n')
                sys.exit(1)
            autoidx = True
    else:
        datum = float(data[idx])
    if node:
        if datum in nodebydatum:
            nodebydatum[datum].add(node)
        else:
            nodebydatum[datum] = set([node])
    plotdata.append(datum)
    data = sys.stdin.readline()
    data = list(csv.reader([data], delimiter=delimiter))[0]
n = None
if args.g or args.o or args.x:
    n, bins = plot(args.g, args.o, plotdata, bins=args.b, fmt=args.f)
if args.t:
    n, bins = textplot(plotdata, bins=args.b)
print('Samples: {5} Min: {3} Median: {0} Mean: {1} Max: {4} StandardDeviation: {2} Sum: {6}'.format(np.median(plotdata), np.mean(plotdata), np.std(plotdata), np.min(plotdata), np.max(plotdata), len(plotdata), np.sum(plotdata)))
if args.v and n is not None and nodebydatum:
    print('')
    currbin = bins[0]
    bins = bins[1:]
    currbinmembers = []
    for datum in sorted(nodebydatum):
        if datum > bins[0]:
           nextbin = None
           endbin = bins[0]
           while len(bins) and bins[0] < datum:
                nextbin = bins[0]
                bins = bins[1:]
           if not nextbin:
               nextbin = np.max(plotdata)
           print('Entries between {0} and {1}'.format(currbin, endbin))
           currbin = nextbin
           print('-' * 80)
           print(','.join(sorted(currbinmembers)))
           print('')
           print('')
           currbinmembers = []
        for node in nodebydatum[datum]:
            currbinmembers.append(node)
    if currbinmembers:
       print('Entries between {0} and {1}'.format(currbin, np.max(plotdata)))
       print('-' * 80)
       print(','.join(sorted(currbinmembers)))
       print('')
       print('')
