# encoding: utf-8

'''
@author: Majeed.sahebzadha
'''

from __future__ import unicode_literals
import copy
import time
import numpy as np
import pandas as pd
import quantipy as qp
from os import path
from collections import OrderedDict
from pptx import Presentation
from quantipy.core.cluster import Cluster
from quantipy.core.chain import Chain
from quantipy.core.helpers.functions import(
    finish_text_key,
    paint_view)
from quantipy.core.builds.powerpoint.add_shapes import(
    chart_selector,
    add_stacked_bar_chart,
    add_textbox,
    add_net)
from quantipy.core.builds.powerpoint.transformations import(
    is_grid_element,
    get_base,
    validate_cluster_orientations,
    drop_hidden_codes,
    partition_view_df,
    strip_html_tags,
    rename_label,
    df_splitter,
    auto_sort,
    round_df_cells)
from quantipy.core.builds.powerpoint.visual_editor import(
    return_slide_layout_by_name)
from pptx.enum.text import(
  PP_ALIGN,
  MSO_AUTO_SIZE,
  MSO_ANCHOR
  )
from pptx.util import(
    Emu,
    Pt,
    Cm,
    Inches
    )
from quantipy.core.builds.powerpoint.add_shapes import (
    percentage_of_num,
    get_cht_plot_height,
    get_upper_cht_plot_gap)

thisdir = path.split(__file__)[0]

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def chain_generator(cluster):
    '''
    Generate chains

    Parameters
    ----------
    cluster : quantipy.Cluster
        quantipy cluster object
    '''

    for chain_name in cluster.keys():
        yield cluster[chain_name]

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def chain_has_weighted_views(chain):
    '''
    Check if a qp.Chain contains weighted frequency views

    Parameters
    ----------
    chain : quantipy.Chain
        quantipy chain object
    '''

    for el in chain.views:
        e0, e1, e2, e3, e4, e5 = el.split('|')
        if e0 == 'x' and e1 == 'f' and e3 == 'y' and e4:
            return True
        elif e2 == 'x++:' and e3 == 'y' and e4:
            return True
    return False

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def get_grid_el_label(df):
    '''
    Grabs a grid element level label

    Parameters
    ----------
    df : dataframe
        pandas dataframe object
    '''

    grid_el_label = strip_html_tags(df.index[0][0])
    if ' - ' in grid_el_label:
        label = grid_el_label.split(' - ')[-1].strip()
    elif '. ' in grid_el_label:
        label = grid_el_label.split('. ',1)[-1].strip()
    else:
        label = 'Label missing'
    return label

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def df_meta_filter(
    df,
    meta,
    conditions,
    index_key=None):
    '''
    Selects rows based on multiple binary conditions -True/False

    Parameters
    ----------
    df: pandas dataframe
    meta: pandas dataframe
    conditions: dict or pandas.Series object
    index_key: column label or list of column labels / arrays

    example useage: df_meta_filter(
        df,
        meta,
        {'is_pct': True, 'is_weighted': 'True'},
        index_key='label')

    resource: http://stackoverflow.com/questions/34740778/use-series-to-select-rows-from-df-pandas
    http://stackoverflow.com/questions/34726569/get-subsection-of-df-based-on-multiple-conditions
    '''

    con = conditions.copy()

    df = df.reset_index()
    meta = meta.reset_index()

    if not isinstance(con, pd.Series):
        con = pd.Series(con)

    # pull rows where all the conditions are met
    # get subset of df based on labels in conditions
    df = df[(meta == con)[con.index].all(axis=1)]

    if not df.empty:
        if not index_key:
            key_names = ['Values']
        else:
            if not isinstance(index_key, list):
                index_key = [index_key]
            key_names = index_key

        # replace names with labels (note in the future, use text first then labels)
        if len(key_names)>1:
            # use label and overlap those by text which are not empty string
            idx = meta.loc[df.index]['label'].where(meta.loc[df.index]['text']=='', meta.loc[df.index]['text'].values)
        else:
            idx = meta.loc[df.index].set_index(key_names).index

        df = df.set_index(df.columns[0])
        # replace label index with name index
        df.index = idx

        return df
    else:
        # empty df
        return pd.DataFrame()

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def gen_meta_df(painted_df, qp_view):
    '''
    Creates a df containing only metadata

    Parameters
    ----------
    painted_df: pandas dataframe
    unpainted_df: pandas dataframe
    qp_view: quantipy view
    '''

    df_meta = partition_view_df(qp_view.dataframe)[0]
    df_meta['short_name'] = qp_view.meta()['agg']['name']
    df_meta['text'] = qp_view.meta()['agg']['text']
    df_meta['method'] = qp_view.meta()['agg']['method']
    df_meta['is_block'] = qp_view.meta()['agg']['is_block']
    df_meta['is_pct'] = str(qp_view.is_pct())
    df_meta['is_base'] = str(qp_view.is_base())
    df_meta['is_weighted'] = str(qp_view.is_weighted())
    df_meta['is_counts'] = str(qp_view.is_counts())
    df_meta['is_meanstest'] = str(qp_view.is_meanstest())
    df_meta['is_propstest'] = str(qp_view.is_propstest())
    df_meta['is_sum'] = str(qp_view.is_sum())
    df_meta['is_stat'] = str(qp_view.is_stat())
    df_meta['label'] = painted_df.index

    # distinguish between net and expanded
    net_bools=[]
    for row in df_meta.index:
        if qp_view.is_net():
            v_described = qp_view.describe_block()
            all_normal = all(vt == 'normal' for vt in v_described.itervalues())
            if not all_normal:
                if row in v_described:
                    if v_described[row] == 'net':
                        net_bools.append('True')
                    else:
                        net_bools.append('False')
            else:
                net_bools.append('True')
        else:
            net_bools.append('False')
    df_meta['is_net'] = net_bools

    # rearrange the columns
    df_meta = df_meta[['label', 'short_name', 'text', 'method', 'is_pct',
                       'is_net', 'is_weighted', 'is_counts', 'is_block',
                       'is_base', 'is_stat', 'is_sum', 'is_propstest',
                       'is_meanstest']]

    return df_meta

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def same_labels(listofdfs):
    '''
    Before concatenating dfs make sure their row/index labels match.
    Some times, a set of grid element tables which belond to the same
    grid contain different views which is not ideal.

    Parameters
    ----------
    listofdfs: list of pandas dataframes
    '''

    for x in range(0, len(listofdfs)):
        if not all(listofdfs[0].index == listofdfs[x].index):
            raise Exception('index labels mismatch')

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def same_num_of_elements(listofdfs):
    '''
    Counts the num of elements in listofdfs, checks if they are all the same.

    Parameters
    ----------
    listofdfs: list of pandas dataframes
    '''

    el_len = [len(el) for el in listofdfs]
    if not all(x == el_len[0] for x in el_len):
        raise Exception('cannot merge elements - uneven '
                        'number of element views.')

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def all_same(val_array):
    '''
    Check if all the values in given list the same

    Parameters
    ----------
    numpy_list: numpy array
    '''

    # check if val_array is a numpy array
    if type(val_array).__module__ == np.__name__:
        val = val_array.tolist()
        if isinstance(val[0], list):
            #handle list of lists
            return all(round(x[0]) == round(val[0][0]) for x in val)
        else:
            #handle single list
            return all(round(x) == round(val[0]) for x in val)
    else:
        raise Exception('This function only takes a numpy array')

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def insert_values_to_labels(
    add_values_to,
    take_values_from,
    index_position=0):
    '''
    Takes two dfs, adds values from a given row from one df and adds
    it to the other dfs column labels.

    Parameters
    ----------
    add_values_to: pandas dataframe
    take_values_from: pandas dataframe
    index_position: int, optional
    '''

    # check 1 - if the labels from both dfs are the same
    if all(add_values_to.columns == take_values_from.columns):

        # pull a given row's values
        row_vals = take_values_from.ix[[index_position],:].values
        # flatten the list of values
        row_vals = row_vals.flatten()
        # get column labels
        col_labels = add_values_to.columns

        # loop over and add the values to the labels
        for x,y in zip(col_labels, row_vals):
            col_name = x + " (n=" + str(int(round(y))) +")"
            add_values_to.rename(columns={x: col_name}, inplace=True)

        return add_values_to
    else:
        raise Exception('Cannot add values to df labels')

'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
'~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'

def PowerPointPainter(
    path_pptx,
    meta,
    cluster,
    path_pptx_template=None,
    slide_layout='Blank',
    text_key=None,
    force_chart=True,
    force_crossbreak=None,
    base_type='weighted',
    base_repr=None,
    include_nets=True,
    shape_properties=None,
    display_var_names=True,
    date_range=None,
    split_busy_dfs=False,
    verbose=True):
    '''
    Builds PowerPoint file (PPTX) from cluster, list of clusters, or
    dictionary of clusters.

    Parameters
    ----------
    path_pptx : str
        PowerPoint file path
    meta : dict
        metadata as dictionary used to paint datframes
    cluster : quantipy.Cluster / list / dict
        container for cluster(s)
    path_pptx_template : str, optional
        full path to PowerPoint template
    slide_layout : str / int, optional
        valid slide layout name or index
    text_key : str, optional
        language
    force_chart : boolean, optional
        ues default settings to produce a PowerPoint file
    force_crossbreak : str / list, optional
        use given crossbreaks to build a PowerPoint file
    base_type : str, optional
        use weighted or unweighted base
    include_nets : str / boolean
        True/False: include/exclude net views in chart data
        'partly': include nets in chart data except for array summary charts
    shape_properties : dict, optional
        keys as format properties, values as change from default
    display_var_names : boolean
        variable names append to question labels
    split_busy_dfs : boolean
        if True, spreads busy dataframes evenly across multiple slide
    '''
    if verbose:
        print(
            '\n{ast}\n{ast}\n{ast}\nINITIALIZING POWERPOINT '
            'AUTOMATION SCRIPT...'.format(ast='*' * 80))

    # check path extension
    if path_pptx.endswith('.pptx'):
        path_pptx = path_pptx[:-5]
    elif path_pptx.endswith('/') or path_pptx.endswith('\\'):
        raise Exception('File name not provided')

    # check base type string
    base_type = base_type.lower()

    # render cluster
    names = []
    clusters = []
    if isinstance(cluster, Cluster):
        names.append(cluster.name)
        clusters.append(cluster)
    elif isinstance(cluster, list):
        for c in cluster:
            names.append(c.name)
            clusters.append(c)
    elif isinstance(cluster, dict):
        names_clusters_dict = cluster
        for sheet_name, c in cluster.iteritems():
            names.append(sheet_name)
            clusters.append(c)

    # default settings
    default_props = {
        'crossbreak': ['@'],
        'chart_type': 'bar',
        'sort_order': 'none',
        'chart_color': 'green',
        'fixed_categories': [],
        'base_description': '',
        'chart_layout': '1',
        'slide_title_text': 'Click to add title',
        'question_label': 'Unknown',
        'copied_from': '',
        'center_header': '',
        'right_footer': '',
        'title_footer': ''}

    spec = meta['sets'].get('spec', False)

    # update 'crossbreak' key's value in default_props if
    # force_crossbreak parameter is true
    if force_crossbreak:
        if isinstance(force_crossbreak, list):
            pass
        elif isinstance(force_crossbreak, str):
            force_crossbreak = [force_crossbreak]
        for c in force_crossbreak:
            default_props['crossbreak'].append(c)

    if not path_pptx_template:
        path_pptx_template = path.join(
            thisdir,
            'templates\default_template.pptx')

    # get the default text key if none provided
    if text_key is None:
        text_key = finish_text_key(meta, text_key)

    # default shape properties (minimum level, only shape dimensions)
    # if none provided
    if shape_properties is None:
         shape_properties = {