import os
import sys
import numpy as np
import pandas as pd
import pickle
import csv
from math import isnan
from datetime import datetime
from zipfile import ZipFile
from bisect import bisect_left as bisleft
from tkinter import filedialog as fidia
from shutil import copyfile, rmtree
from datetime import datetime as dt
import tkinter as tk
import requests as rq
import decimal
from decimal import Decimal as dc
from functools import reduce
import json

def dtUni(strDate):
	dtObj = datetime.strptime(strDate, '%d.%m.%Y %H:%M:%S')
	return datetime.timestamp(dtObj)

def dtUniForm(strDate,form):
	dtObj = datetime.strptime(strDate, form)
	return datetime.timestamp(dtObj)

def uniDt(uniStamp):
	dtN = datetime.fromtimestamp(float(uniStamp))
	return datetime.strftime(dtN, '%d.%m.%Y %H:%M:%S')

def uniDtForm(uniStamp, form):
	dtN = datetime.fromtimestamp(uniStamp)
	return datetime.strftime(dtN, form)


def rev_date(st_date, dtForm_1="%d.%m.%Y", dtForm_2="%y%m%d"):
	""" reverses "%d.%m.%Y" date to "%y%m%d" """
	dt_ = dt.strptime(st_date, dtForm_1)
	return dt_.strftime(dtForm_2)


def startUni(uniStamp, minutes):
	uniDiff = (uniStamp - 1514757600) // (60 * minutes)
	return 1514757600 + (uniDiff + 1) * (60 * minutes)

def uniPdTs(uniList):
	""" returns pd.Timestamp list from Unix timestamp list"""
	dtList = [datetime.fromtimestamp(i) for i in uniList]
	return [pd.Timestamp(i) for i in dtList]

def log3(dayRate):
	return pow((dayRate + 1), (1 / 3)) - 1

def fl_to_dc(float_):
	if isnan(float_):
		return float_
	elif isinstance(float_, float) or isinstance(float_, int):
		whole_part = int(float_)
		small_part = int((float_ - whole_part) * 10 ** 9)
		return dc(whole_part + dc(small_part) / (10 ** 9))

def to_dc(list_):
	""" takes a list of float values and converts them to decimal """
	for i in range(len(list_)):
		list_[i] = fl_to_dc(list_[i])
	return list_

def format_dc(dec, opt=0):
	""" formats a decimal with 5,4,3 meaningful digits;
	-dec- may not be decimal """
	if dec >= 0.1:
		return format(dec, '.5g')
	elif dec >= 0.01 and dec < 0.1:
		if opt == 0:
			decimal.getcontext().prec = 4
			str_ = str(dc(dec) *1)
			return str_
		else:
			decimal.getcontext().prec = 3
			str_ = str(dc(dec) *1)
			return str_
	elif dec >= 0.0001 and dec < 0.001:
		if opt == 0:
			decimal.getcontext().prec = 5 # xrp
			str_ = str(dc(dec) *1)
			return str_
		elif opt == 1:
			decimal.getcontext().prec = 4 # eos
			str_ = str(dc(dec) *1)
			return str_
	elif dec >= 0.00001 and dec < 0.0001:		
		if opt == 0:
			decimal.getcontext().prec = 4 # xrp, ada
			str_ = str(dc(dec) *1)
			return str_
		elif opt == 1:
			decimal.getcontext().prec = 3 
			str_ = str(dc(dec) *1)
			return str_
	else:
		decimal.getcontext().prec = 3 # trx
		str_ = str(dc(dec) *1)
		return str_

def toDay():
	#from datetime import datetime
	return date.today().strftime("%d.%m.%y")

def dig_find(s_1):
	""" for a passed string gives index of the 1st digit """
	for i, c in enumerate(s_1):
		if c.isdigit():
			return i

def d_key_len(d_):
	""" returns the length of each element in a dictionary """
	for k in d_.keys():
		print(k + ':' + str(len(d_[k])))


def d_find_key(d_, prd, coin):
	""" returns the type of the contract
	or a second level dictionary key of an element in a list """
	# dictionary with timeframe data
	d_tframe = d_[prd]
	# contract trading type
	for c_type in d_tframe.keys():
		if coin in d_tframe[c_type]:
			return c_type

def req_url(url_):
	""" for a given url return json format of the result """
	resp = rq.get(url_)
	data = resp.json()
	return data


def ave_list(list_):
	""" for a given list returns the average of its numbers """
	return reduce(lambda a, b: a + b, list_) / len (list_)


def bp_diff(fl_1, fl_2):
	""" for two given floats gives the diff in basis points """
	return (fl_2 - fl_1) / (fl_1 * 0.0001)


def pd_corr(list_1, list_2):
	""" for given 2 list of equal lenght returns
	Pearson's correlation coefficient """
	x = pd.Series(list_1)
	y = pd.Series(list_2)
	return x.corr(y)


def intersect(lst1, lst2):
	""" for 2 given lists returns 
	the intersection list of those """
	if len(lst1) > len(lst2):
		set_1 = set(lst1)
		lst3 = [value for value in lst2 if value in set_1]
	else:
		set_2 = set(lst2)
		lst3 = [value for value in lst1 if value in set_2]
	return lst3 


def non_intersect(l_1, l_2):
	""" for 2 given lists returns 
	all the members of the 1st list that
	are not found in the second list """
	l_3 = [value for value in l_1 if value not in l_2]
	return l_3

def find_ll_ele(ll_, str_, ind_):
	""" finds string in a list of lists by index """
	return next(c for c in ll_ if c[ind_] == str_)


def subpath_rename(path_, subpath, index_, type_='.csv'):
	""" renames subpath of files base on index with the subpath name """
	for i in os.listdir(path_ + subpath):
		if os.path.isfile(os.path.join(path_ + subpath, i)) and type_ in i:
			os.rename(path_ + subpath + i, path_ + subpath + i[:index_] + '_' + subpath[:-1] + '_' + i[index_ + 1:])

def index_rename(path_, index_, type_='.csv'):
	""" shortens name for path of files based on index """
	for i in os.listdir(path_):
		if os.path.isfile(os.path.join(path_, i)) and type_ in i:
			if i[index_] == '.':
				os.rename(path_ + i, path_ + i[index_ + 1:])
			else:
				os.rename(path_ + i, path_ + i[index_:])

def subpath_to_path(path_, subpath, type_='.csv'):
	""" moves files from subpath to path """
	for i in os.listdir(path_ + subpath):
		if os.path.isfile(os.path.join(path_ + subpath, i)) and type_ in i:
			os.rename(path_ + subpath + i, path_ + i)

def mass_subpath_to_path(path_, opt_del=0):
	""" for a path of subpaths full of files performs -subpath_to_path- """
	for i in os.listdir(path_):
		if os.path.isdir(os.path.join(path_, i)):
			subpath_to_path(path_, i + '\\', type_='.csv')
			# if set deletes the empty folders
			if opt_del:
				rmtree(os.path.join(path_, i))
	""" cleans for a base_folder+intrument_folder+year_folder
	for i in os.listdir(p_):
		if os.path.isdir(os.path.join(p_, i)):
			mass_subpath_to_path(p_+i+'\\', opt_del=1) """


def dfNewIndex(df):
	""" sets a new index of a dataframe """
	ind = list(range(len(df)))
	indS = pd.Series(ind)
	df = df.set_index(indS)
	return df

def ll_to_csv(ll_, f_csv):
	""" writes given list of lists 
	as CSV file	"""
	with open(f_csv, "w", newline="") as f:
		writer = csv.writer(f)
		writer.writerows(ll_)


def pickleRead(piFile):
	with open(piFile, 'rb') as fp:
		df = pickle.load(fp)
	return df

def sec_piRead(piFile):
	""" secure reading pi files through creating .backup file """
	# check if -piFile- exists > yes
	if os.path.isfile(piFile):
		# check if new file size is bigger than backup
		if os.path.getsize(piFile) > os.path.getsize(piFile + '.backup'):
			# overwrite the old backup file without asking
			copyfile(piFile, piFile + '.backup')
			return pickleRead(piFile)
		else:
			# copy & open the backupfile
			copyfile(piFile  + '.backup', piFile)
			return pickleRead(piFile + '.backup')
	# check if -piFile- exists > no
	else: 
		# open the backupfile
		return pickleRead(piFile + '.backup')

def pickleWrite(df, piFile):
	with open(piFile, 'wb') as fp:
		pickle.dump(df, fp)
	print("_{}_ File {} is written!".format(datetime.strftime(datetime.now(), '%H:%M:%S'), piFile))

def sec_piWrite(df, piFile):
	""" securely pickleWrite-s file plus a -. backup- copy """
	counter = 0
	# first overwrite the backup file
	pickleWrite(df, piFile + '.backup')
	# if the backup has bigger size that the base file, overwrite the base file
	if os.path.getsize(piFile + '.backup') > os.path.getsize(piFile):
		copyfile(piFile + '.backup', piFile)
		counter = 0
	# otherwise overwrite the backup
	else:
		# up to 10 times copy the base file as backup
		if counter < 11:
			copyfile(piFile, piFile + '.backup')
			counter += 1
		# exit the program
		else:
			print("10 unsuccessful attempts to update {}!".format(piFile))
			sys.exit()

def dubl_piRead(piFile):
	""" secure reading pi files through creating .backup file """
	# check if -piFile- exists > yes
	if os.path.isfile(piFile):
		# check if new file size is equal to backup
		if os.path.getsize(piFile) == os.path.getsize(piFile + '.backup'):
			# overwrite the old backup file without asking
			copyfile(piFile, piFile + '.backup')
			return pickleRead(piFile)
		else:
			# copy & open the backupfile
			print('Something went wrong with {} ...'.format(piFile))
			sys.exit()
	# check if -piFile- exists > no
	else: 
		# open the backupfile
		print('The passed file {} does not exist!'.format(piFile))
		sys.exit()


def unzip(f_zip, dest_):
	""" unzips a .zip file """
	zf = ZipFile(f_zip, 'r')
	zf.extractall(dest_)
	zf.close()


def subtract_years(dt, years):
	""" Year substraction avoiding leap year extra day"""
	try:
		dt = dt.replace(year=dt.year-years)
	except ValueError:
		dt = dt.replace(year=dt.year-years, day=dt.day-1)
	return dt

def fileDia(fType):
	root = tk.Tk()
	root.withdraw()
	
	fileType={	'txt':["txt files", "*.txt"],
				'csv':["csv files", "*.csv"],
				'pi':["pi files", "*.pi"],
				'h5':["h5 files", "*.h5"],
				'db':["db files", "*.db"]}
	
	myFile =  fidia.askopenfilename(	initialdir = "data",
										title = "Select file",
										filetypes = ((fileType[fType][0],fileType[fType][0]),
										("all files","*.*")))
	return myFile

def query_yes_no(question, default="yes"):
    """Ask a yes/no question via raw_input() and return their answer.

    "question" is a string that is presented to the user.
    "default" is the presumed answer if the user just hits <Enter>.
        It must be "yes" (the default), "no" or None (meaning
        an answer is required of the user).

    The "answer" return value is True for "yes" or False for "no".
    """
    valid = {"yes": True, "y": True, "ye": True,
             "no": False, "n": False}
    if default is None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("invalid default answer: '%s'" % default)

    while True:
        sys.stdout.write(question + prompt)
        choice = input().lower()
        if default is not None and choice == '':
            return valid[default]
        elif choice in valid:
            return valid[choice]
        else:
            sys.stdout.write("Please respond with 'yes' or 'no' "
                             "(or 'y' or 'n').\n")

def v(dict_):
	""" visualize dictionary with pandas from_dict method """
	return pd.DataFrame.from_dict(dict_)

def d_edit(dict_, list_, val):
	""" for a given dictionary and a sequence of keys sets equal to a value """
	if len(list_) > 1 and len(list_) < 7:
		if len(list_) == 2:
			dict_[list_[0]][list_[1]] = val
		elif len(list_) == 3:
			dict_[list_[0]][list_[1]][list_[2]] = val
		elif len(list_) == 4:
			dict_[list_[0]][list_[1]][list_[2]][list_[3]] = val
		elif len(list_) == 5:
			dict_[list_[0]][list_[1]][list_[2]][list_[3]][list_[4]] = val
		elif len(list_) == 6:
			dict_[list_[0]][list_[1]][list_[2]][list_[3]][list_[4]][list_[5]] = val
		return dict_
	else:
		print('Bad key list passed!')

def d_mass(dict_, list_key, list_, val):
	""" for a given list of 1st level dictionary keys ([0]) performs -d_edit- """
	for key in list_key:
		list_new = [key] + list_
		dict_ = d_edit(dict_, list_new, val)


def bisRound(myList, myVal):
	""" 
	Returns the index of an ascending sorted list member to which the value is closest. Return -1, when myVal < min(myList); return len(myList) when 
	myVal > max(myList).
	"""
	bInd = bisleft(myList, myVal)
	if bInd == 0:
		return -1
	elif bInd == len(myList):
		return len(myList)
	else:
		if myVal < np.mean(myList[bInd-1:bInd+1]):
			return bInd - 1
		else:
			return bInd

if __name__ == "__main__":
	#pickleRead(piFile)
	pass