#exec(open("BolsasAux.py").read())

#    Black Monday Q3 de 1987
#    Perde 7% num dia Q3 de 1989
#    Segunda Black Monday Q3 de1997
#    Crise Argentina Q3 1999
#    NASDAQ  Q1 de 2000
#    11 Setembro 2001
#    IRAQ 19 Q1 de 2003
#    Subprime Q3 de 2007
#    Covid 2020 Q2
#    Trump tarifs Q2 de 2025

import os
import numpy as np

#from smeUtils import *
#from smeGudhi import *

class criaRipsComplex:
    def __init__ (self, pts, fname, edgeMax, sparse=None, maxDim=2, cols=[], skip=0, persLim=0.03):
        self.pts = np.array(pts)
        self.fname = fname
        self.edgeMax = edgeMax
        self.sparse = sparse
        self.maxDim = maxDim
        self.cols = cols
        self.skip = skip
        self.persLim = persLim

        self.infos = None
        self.classif = np.array([])    # Não usado
        self.rips_complex = None
        self.simplex_tree = None
        self.persistence = None
        self.barcode = None
        self.numBetti = None
        self.holes = None

        self.meanDist = 0
        self.meanDistErr = 0
        self.vol = 0

        self.rips_complex = gudhi.RipsComplex(points=pts, max_edge_length=edgeMax, sparse=sparse)
        self.simplex_tree = self.rips_complex.create_simplex_tree(max_dimension=maxDim)
        self.persistence = self.simplex_tree.persistence()

        self.meanDist, self.meanDistErr = getDistMean(pts)

    def visualizar_pontos (self, titulo="Conjunto de Pontos"):
        from sklearn.decomposition import PCA

        plt.figure(figsize=(10, 8))
        
        if self.pts.shape[1] == 2:
            plt.scatter(self.pts[:, 0], self.pts[:, 1], 
                       c='blue', s=100, alpha=0.7, edgecolors='black')
            plt.xlabel('X')
            plt.ylabel('Y')
        elif self.pts.shape[1] == 3:
            ax = plt.subplot(111, projection='3d')
            ax.scatter(self.pts[:, 0], self.pts[:, 1], self.pts[:, 2],
                      c='blue', s=100, alpha=0.7)
            ax.set_xlabel('X')
            ax.set_ylabel('Y')
            ax.set_zlabel('Z')
        else:
            # Para dimensões maiores, usar PCA
            pca = PCA(n_components=2)
            pts_2d = pca.fit_transform(self.pts)
            plt.scatter(pts_2d[:, 0], pts_2d[:, 1], 
                       c='blue', s=100, alpha=0.7, edgecolors='black')
            plt.xlabel(f'PC1 ({pca.explained_variance_ratio_[0]*100:.1f}%)')
            plt.ylabel(f'PC2 ({pca.explained_variance_ratio_[1]*100:.1f}%)')
        
        plt.title(titulo)
        plt.grid(True, alpha=0.3)
        plt.tight_layout()
        plt.show()

    def visualizar_diag_persistencia (self, raio_otimo=None):
        if self.persistence is None:
            print("Erro: Calcule a persistência primeiro!")
            return
        
        plt.figure(figsize=(12, 5))

        dims = {}
        for (dim, (birth, death)) in self.persistence:
            if dim not in dims: dims[dim] = []
            dims[dim].append((birth, death))
        
        # Plota cada dimensão
        cores = ['blue', 'red', 'green', 'purple', 'orange']
        subplot_idx = 1
        
        for dim in sorted(dims.keys()):
            plt.subplot(1, len(dims), subplot_idx)
            
            for birth, death in dims[dim]:
                if np.isinf(death):
                    # Componente conexa infinita
                    plt.plot([birth], [dim], 'o', color=cores[dim % len(cores)], markersize=10, label=f'Dim {dim}')
                else:
                    plt.plot([birth, death], [dim, dim], '-', color=cores[dim % len(cores)], linewidth=2)
                    plt.plot([birth, death], [dim, dim], 'o', color=cores[dim % len(cores)], markersize=6)
            
            if raio_otimo:
                plt.axvline(x=raio_otimo, color='red', linestyle='--', 
                           label=f'Raio = {raio_otimo:.3f}')
            
            plt.xlabel('Raio')
            plt.ylabel('Dimensão')
            plt.title(f'Dimensão {dim}')
            plt.grid(True, alpha=0.3)
            subplot_idx += 1
        
        plt.tight_layout()
        plt.show()

    def findHoles (self, limiar_persistencia=0.1, show=False):
        if self.persistence is None:
            print("Erro: Calcule a persistência primeiro!")
            return []
        
        holes = []
        for (dim, (birth, death)) in self.persistence:
            if dim > 0 and not np.isinf(death):  # Ignora componentes conexas
                persistencia = death - birth
                if persistencia > limiar_persistencia:
                    holes.append({
                        'dimensão': dim,
                        'nascimento': birth,
                        'morte': death,
                        'persistência': persistencia,
                        'ponto_central': birth + (death - birth) / 2
                    })
        holes.sort(key=lambda x: x['persistência'], reverse=True)
        self.holes = holes

        if show: print(f"Buracos significativos encontrados: {len(holes)}")

        return holes


# x1 = bolsaFileRead("NY_20080303_20081031__319_dim169.txt")
def bolsaFileRead (fname, skip=0, sep=' ', cols=0, dType=float):
    import numpy as np

    v1 = []
    vInfo = []
    n1 = -1
    with open(fname) as f1:
        while line := f1.readline():
            n1 += 1
            if n1 < skip: continue
            if line == '': break

            spLine = line.split("|")
            lenSpLine = len(spLine)
            if len(spLine) > 1:
                if lenSpLine == 4:
                    vdic = {'name':spLine[0].strip(' '), 'sname':spLine[1].strip(' '), 'class':spLine[2].strip(' ')}
                elif lenSpLine == 5:
                    vdic = {'name':spLine[0].strip(' '), 'sname':spLine[1].strip(' '), 'class':spLine[2].strip(' '),
                            'vol':float(spLine[3].strip(' '))}
                else:
                    print("\n***** Atenção: Número de separadores incorrecto!!!!!\n")
                    return []
                vInfo.append(vdic)
                line = spLine[lenSpLine-1]

            line = [dType(float(x)) for x in line.strip('\n').strip().split(sep)] # if line != '']

            if v1 == []:
                qtCols = len(line)
                if type(cols) == int:
                    vCols = [1]*qtCols
                else:
                    n2 = -1
                    vCols = [0]*qtCols
                    while n2 < qtCols:
                        n2 += 1
                        if n2 in cols: vCols[n2] = 1

            n2 = 0
            v2 = []
            while n2 < qtCols:
                if vCols[n2] == 1: v2.append(line[n2])
                n2 += 1
            v1.append(v2)

            #v1.append(np.array([float(val) for val in line2.rstrip('\n').split(' ') if val != '']))
            #v1.append([float(val) for val in line2.rstrip('\n').split(' ') if val != ''])

        return [v1, vInfo]

##################################################################

def graphSectores_title (fname, dirs):
    pos = fname.rfind('.')
    x2 = (fname[:pos]).split("_")
    lx2 = len(x2)

    qtVar = x2[lx2-1]
    qtEmp = x2[lx2-2]
    data2 = x2[lx2-4]
    data1 = x2[lx2-5]

    data1 = data1[:4] + "-" + data1[4:6] + "-" + data1[6:8]
    data2 = data2[:4] + "-" + data2[4:6] + "-" + data2[6:8]
    
    x3 = f"["
    for y in dirs: x3 += str(y) + ","
    x3 = x3[:len(x3)-1] + "]"
    title = f"{data1} -- {data2}  *  N.Emp: {qtEmp}  *  Direcções: {x3}"

    return title

#
# Exemplo:
#    rc = Bolsas(2004, 0, edgeMax=0.15, maxDim=4, sparse=None, skip=0, cols=[0,1,2,3], persLim=0.03, graphShow=False)
#    graphSectores (rc.pts, rc.infos, [1,3])
#    graphSectores (rc.pts, rc.infos, [1,2,3])
#    graphSectores (rc.pts, rc.infos, [1,2,3],sectGraph=[10,20,30])
#    graphSectores (rc.pts, rc.infos, [1,2,3],sectGraph=[10,20,30], sectMode='ambos',coordNome=["Eixo xx", "Eixo yy", "Eixo zz"])
#
# pts       : vector com os valores das coordenadas das empresas
# infos     : Informação sobre as empresas (Nome. Sigla, Sector)
# dirs      : São as dimensões a utilizar (2 ou 3) existentes em "rc.pts". Por default [0,1]
# sectGraph : Sectores a representar (classes). Por default todas ([])
# figsize   : Tamanho da figura
# marks     : Pode ser "sigla" ou "pts" (default)
# fontSize  : Tamanho da fonte (default: 9)
# title     : Título
# dicSect   : Dicionário com os sectores
# sectMode  : Mode de apresentação dos sectores: 'num' (mostra o número), 'nome' (mostro o nome), "ambos" (mostra número e nome)
# coordNome : vector com duas ou três dimensões com os nomes a aparecerem nos eixos
#


def graphSectores (pts, infos, dirs, sectGraph=[], figsize=(10, 8), marks="pts", fontSize=9, title="", dicSect={}, sectMode="ambos", coordNome=[]):
    import numpy as np
    import matplotlib.pyplot as plt

    if dicSect == {}:
        dicSect = {'10':'Energy','15':'Materials','20':'Industrials','25':'Cons. Discret.','30':'Cons. Staples',
                   '35':'Health Care','40':'Financials','45':'Inform Technol.','50':'Telecom. Services','55':'Utilities',
                   '60':'Real Estate', '95':'Não Classif.'}
    
    lenDirs = len(dirs)
    if lenDirs > 3:
        print("As direcções escolhidas só podem ser no máximo três.")
        return
    elif lenDirs < 2:
        print("Tem de ter indicar pelo menos duas direcções.");
        return

    if title == "" and 'fname' in rc.infos[0]:
        title = graphSectores_title (rc.infos[0]['fname'], dirs)

    n1 = 0
    qt = len(pts)
    sect = np.array([])
    while n1 < qt:
        if not np.isin(infos[n1]['class'], sect): sect = np.append(infos[n1]['class'], sect)
        n1 += 1
    sect = np.sort(sect)
    lenSect = len(sect)

    if sectGraph == []:
        sectGraph = sect
    else:
        for x1 in sectGraph:
            if not np.isin(x1, sect):
                print(f"\nErro: o sector {x1} não existe. A lista válida é\n     {sect}\n")
                return
        
        if type(sectGraph[0]) == int:
            n1 = 0
            while n1 < len(sectGraph):
                sectGraph[n1] = str(sectGraph[n1])
                n1 += 1
    lenSectGraph = len(sectGraph)
    #strSectGraph = sectGraph
    #print(type(sectGraph[0]))
        

    vGraf = {}
    for x in sect:
        vGraf[x] = {'ok':0, 'x':np.array([]), 'y':np.array([]), 'z':np.array([]), 'c':np.array([]), 's':np.array([]), 'n':np.array([])}

    colors = np.array(["red","green","blue","yellow","pink","black","purple","brown","gray","cyan","magenta", "orange"]) # "beige", "orange",
    formas = np.array(["o", "v", "^", "<", ">", "*", "+", "p", "H", "x", "d", "s", "h"])

    qt = len(pts)
    n1 = 0
    while n1 < qt:
        lab = infos[n1]['class']
        if lenSectGraph == 0 or np.isin(lab, sectGraph):
            vGraf[lab]['ok'] += 1
            vGraf[lab]['x'] = np.append(vGraf[lab]['x'], pts[n1][dirs[0]])
            vGraf[lab]['y'] = np.append(vGraf[lab]['y'], pts[n1][dirs[1]])
            if lenDirs == 3: vGraf[lab]['z'] = np.append(vGraf[lab]['z'], pts[n1][dirs[2]])
            n3 = np.where(sect == lab)
            vGraf[lab]['c'] = np.append(vGraf[lab]['c'], colors[n3[0]])
            vGraf[lab]['s'] = np.append(vGraf[lab]['s'], int(50))
            vGraf[lab]['n'] = np.append(vGraf[lab]['n'], infos[n1]['sname'])
        n1 += 1

    fig = plt.figure(figsize=figsize)
    plt.suptitle(title, fontsize=16)
    ax = fig.add_subplot(1,1,1)
    if lenDirs == 3: ax = plt.axes(projection='3d')

    
    n1 = 0
    for x1 in sectGraph:
        lenGSect = len(vGraf[x1]['x'])
        #print(f"x1: {x1}")
        if vGraf[x1]['ok'] == 0 or lenGSect == 0: continue
        if sectMode == "ambos":
            bboxLen = 1.35 if lenDirs == 2 else 1.58
            labText = f"{x1}: {dicSect[x1]}"
        elif sectMode == "nome":
            bboxLen = 1.30 if lenDirs == 2 else 1.52
            labText = f"{dicSect[x1]}"
        else:
            bboxLen = 1.21 if lenDirs == 2 else 1.40
            labText = f"Sector {x1}"
        if lenDirs == 2:
            if marks == "sigla":
                #ax.plot(vGraf[x1]['x'], vGraf[x1]['y'], color=colors[n1], marker=".", linewidth=0, markersize=1, label=f"Sector {x1}")
                ax.plot(vGraf[x1]['x'], vGraf[x1]['y'], color=colors[n1], marker=".", linewidth=0, markersize=1, label=labText)
                n2 = 0
                while n2 < lenGSect:
                    plt.text(vGraf[x1]['x'][n2], vGraf[x1]['y'][n2], vGraf[x1]['n'][n2], color=colors[n1])
                    n2 += 1
            else:
                #ax.plot(vGraf[x1]['x'], vGraf[x1]['y'], color=colors[n1], marker=formas[n1], linewidth=0, label=f"Sector {x1}")
                ax.plot(vGraf[x1]['x'], vGraf[x1]['y'], color=colors[n1], marker=formas[n1], linewidth=0, label=labText)
        else:
            if marks == "sigla":
                #ax.plot(vGraf[x1]['x'], vGraf[x1]['y'], vGraf[x1]['z'], color=colors[n1], marker=formas[n1], markersize=1, linewidth=0, label=f"Sector {x1}")
                ax.plot(vGraf[x1]['x'], vGraf[x1]['y'], vGraf[x1]['z'], color=colors[n1], marker=formas[n1], markersize=1, linewidth=0, label=labText)
                n2 = 0
                while n2 < lenGSect:
                    ax.text(vGraf[x1]['x'][n2], vGraf[x1]['y'][n2], vGraf[x1]['z'][n2], vGraf[x1]['n'][n2], color=colors[n1], size=fontSize)
                    n2 += 1
            else:
                #ax.plot(vGraf[x1]['x'], vGraf[x1]['y'], vGraf[x1]['z'], color=colors[n1], marker=formas[n1], linewidth=0, label=f"Sector {x1}")
                ax.plot(vGraf[x1]['x'], vGraf[x1]['y'], vGraf[x1]['z'], color=colors[n1], marker=formas[n1], linewidth=0, label=labText)
        n1 += 1

    if coordNome == []:
        plt.xlabel(f"Coordenada {dirs[0]}", fontsize=16)
        plt.ylabel(f"Coordenada {dirs[1]}", fontsize=16)
    else:
        plt.xlabel(f"{coordNome[0]}", fontsize=16)
        plt.ylabel(f"{coordNome[1]}", fontsize=16)
    box = ax.get_position()
    ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])

    plt.legend(loc='upper right', bbox_to_anchor=(bboxLen, 1), labelcolor='linecolor', fontsize=11, frameon=True, edgecolor='black', facecolor='lightgray')
    
#    if lenDirs == 2:
#        plt.legend(loc='upper right', bbox_to_anchor=(1.21, 1), labelcolor='linecolor', fontsize=11, frameon=True, edgecolor='black', facecolor='lightgray')
#    else:
#        plt.legend(loc='upper right', bbox_to_anchor=(1.52, 1), labelcolor='linecolor', fontsize=11, frameon=True, edgecolor='black', facecolor='lightgray')
    if lenDirs == 3:
        if coordNome == []:
            ax.set_zlabel(f"Coordenada {dirs[2]}", rotation=90, fontsize=16)
        else:
            ax.set_zlabel(f"{coordNome[2]}", rotation=90, fontsize=16)

    plt.show()
    return


def getDistMean (pts):
    import math
    
    qtEmp = len(pts)
    qtDim = len(pts[0])

    vol = 0.
    mDist = 0.
    mDist2 = 0.
    qtMDist = 0
    n1 = 0
    while n1 < qtDim:
        n2 = n1 + 1
        while n2 < qtDim:
            n3 = 0
            d2 = 0.
            while n3 < qtDim:
                d1 = pts[n1][n3] - pts[n2][n3]
                d2 += math.pow(d1, 2)
                #print(f"[{n3:2}] ({pts[n1][n3]} | {pts[n2][n3]}) ({math.fabs(d1)}) ; d1: {d1} ; d2: {d2}")
                n3 += 1
                #if n3 == 10: return 0
            #print(f"[{n1:2}, {n1:2}] d2: {d2}")
            qtMDist += 1
            mDist2 += d2
            mDist += math.sqrt(d2)
            n2 += 1
        n1 += 1

    mDist2 = mDist2 / qtMDist
    mDist = mDist / qtMDist
    sDist2 = mDist2 - math.pow(mDist, 2)
    sDist = math.sqrt(sDist2)

    return mDist, sDist
    
