import pygame,sys
from archivos2 import*
from animaciones2 import *
from class_personaje import *
from modo2 import *

def actualizar_pantalla(pantalla, un_personaje: Personaje, fondo, lista_plataformas, plataforma_imagen,plataforma_imagen_2):
    pantalla.blit(fondo, (0, 0))
    pantalla.blit(plataforma_imagen, plataforma)
    pantalla.blit(plataforma_imagen_2, plataforma2)
    un_personaje.update(pantalla, lista_plataformas)

#PANTALLA:
W, H = 1200, 600
TAMAÑO= (W,H)
FPS = 10
RELOJ = pygame.time.Clock()
PANTALLA = pygame.display.set_mode(TAMAÑO)
# Fondo
fondo = pygame.image.load("ciudad.jpg")
fondo = pygame.transform.scale(fondo, TAMAÑO)

pygame.init()

#PERSONAJE:
posicion_inicial= (H/2 - 300,500)
tamaño=(75,85)
diccionario_animaciones={}
diccionario_animaciones["quieto"]= personaje_quieto
diccionario_animaciones["salta"]= personaje_salta
diccionario_animaciones["camina_derecha"]= personaje_camina
diccionario_animaciones["camina_izquierda"]= personaje_camina_izquierda

mi_personaje = Personaje(tamaño, diccionario_animaciones, posicion_inicial, 10)

#ENEMIGO:
enemigos_group = pygame.sprite.Group()
sprites = pygame.sprite.Group()
posicion_enemigo = (H/2 - 100, 500)  # Cambia la posición según tus necesidades
tamaño_enemigo = (75, 85)  # Cambia el tamaño según tus necesidades
diccionario_animaciones_enemigo = {}
diccionario_animaciones_enemigo["camina_derecha"] = enemigo_camina_derecha
diccionario_animaciones_enemigo["camina_izquierda"] = enemigo_camina_izquierda

mi_enemigo = Enemigo(tamaño_enemigo, diccionario_animaciones_enemigo, posicion_enemigo, 7)  # Cambia la velocidad según tus necesidades
enemigos_group.add(mi_enemigo)

#PISO:
piso= pygame.Rect(0,0,W,15)
piso.top= mi_personaje.lados["main"].bottom

lados_piso= obtener_rectangulos(piso)
#PLATAFORMA:
tamaño_plataforma=(200,100)
# PLATAFORMA:
plataforma_imagen = pygame.image.load("plataforma5.png.png")
plataforma_imagen = pygame.transform.scale(plataforma_imagen, tamaño_plataforma)

plataforma = pygame.Rect(400, 460, tamaño_plataforma[0], tamaño_plataforma[1])  # Posición (300, 400) y tamaño personalizado
lados_plataforma = obtener_rectangulos(plataforma)

tamaño_plataforma2 = (250, 100)
plataforma_imagen_2 = pygame.image.load("plataforma5.png.png")
plataforma_imagen_2 = pygame.transform.scale(plataforma_imagen_2, tamaño_plataforma2)
plataforma2 = pygame.Rect(600, 400, tamaño_plataforma2[0], tamaño_plataforma2[1])
lados_plataforma2 = obtener_rectangulos(plataforma2)

lista_plataformas= [lados_piso,lados_plataforma, lados_plataforma2]

#LIMITES:
limite_izquierdo = 0
limite_derecho = W - mi_personaje.lados["main"].width
velocidad = 5
#BALAS:
tanda_balas=0
bullets=[]
ruta_musica_bala= "rayo.mp3"
sonido_bala= pygame.mixer.Sound(ruta_musica_bala)
ruta_golpe="golpe.mp3"
sonido_golpe=pygame.mixer.Sound(ruta_golpe)
#############################

# def redraw():

#     # Dibuja los proyectiles

#     for bala in mi_personaje.balas:
#         bala.draw(PANTALLA)
#     pygame.display.update()

def redraw():

    # Dibuja los proyectiles
    for bala in mi_personaje.balas:
        mi_personaje.dibujar(PANTALLA)

    pygame.display.update()
# bullets=[]

ruta_musica= "musica.mp3"
musica_fondo= pygame.mixer.music.load(ruta_musica)

pygame.mixer.music.play(-1)
#####################
limite_izquierdo = 0
limite_derecho = W - mi_enemigo.lados["main"].width

while True:
    RELOJ.tick(FPS)

    for evento in pygame.event.get():
        if evento.type == pygame.QUIT:
            pygame.quit()
            sys.exit(0)
        elif evento.type == pygame.KEYDOWN:
            if evento.key == pygame.K_TAB:
                cambiar_modo()
            elif evento.key == pygame.K_SPACE:
                    mi_personaje.lanzar_proyectil()

    keys = pygame.key.get_pressed() # Teclas presionadas

    direccion=0
    if keys[pygame.K_RIGHT] and mi_personaje.lados["main"].right + velocidad <= limite_derecho  :
        mi_personaje.que_hace = "derecha"
        mi_personaje.direccion = "derecha"  # Establecer dirección como "derecha"
        if mi_personaje.direccion == "derecha":
             direccion= 1
    elif keys[pygame.K_LEFT] and  mi_personaje.lados["main"].left - velocidad >= limite_izquierdo :
            mi_personaje.que_hace = "izquierda"
            mi_personaje.direccion = "izquierda"  # Establecer dirección como "derecha"
            if mi_personaje.direccion == "izquierda":
             direccion= -1
    elif keys[pygame.K_UP]:
        mi_personaje.que_hace = "salta"

    else:
        mi_personaje.que_hace = "quieto" # Si no se realiza ninguna acción, está quieto
        direccion=0

    # for bala in mi_personaje.balas:
    #     if len(mi_personaje.balas ) <5:
    #         mi_personaje.balas.append(proyectiles(round( + mi_personaje.ancho//2), round(mi_personaje.y + mi_personaje.alto //2), 10, "Black", direccion))
    #         sonido_bala.play()
    # tanda_balas +=1
    if tanda_balas >0:
        tanda_balas +=1
    if tanda_balas>3:
        tanda_balas=0
    #Contacto del rpoyectil con el villano:

    for bala in mi_personaje.balas:
        if len(mi_personaje.balas) < 5:
            x_bala = mi_personaje.lados["main"].x + mi_personaje.lados["main"].width // 2
            mi_personaje.balas.append(proyectiles(x_bala, mi_personaje.lados["main"].y + mi_personaje.alto // 2, 10, "Black", direccion))
            sonido_bala.play()
        tanda_balas += 1
    # for bala in :
    #     if bala.rect.colliderect(mi_enemigo.rect):
    #             sonido_golpe.play()
    #             mi_enemigo.balas.remove(bala)
    #             mi_enemigo.salud -= 5
    #     if bala.x < W and bala.x > 0:
    #         # Mueve la bala si está dentro de la ventana
    #             bala.x += bala.velocidad
    #     else:
    #         # Elimina la bala si está fuera de la ventana
    #             mi_personaje.balas.remove(bala)
    # for bala in mi_personaje.balas:
    #     if bala.rect.colliderect(mi_enemigo.rect):
    #         sonido_golpe.play()
    #         mi_enemigo.recibir_dano(5)  # Restar 5 puntos de vida al enemigo
    #         mi_personaje.balas.remove(bala)
    #     if bala.x < W and bala.x > 0:
    #         # Mueve la bala si está dentro de la ventana
    #         bala.x += bala.velocidad
    #     else:
    #         # Elimina la bala si está fuera de la ventana
    #         mi_personaje.balas.remove(bala)

        for bala in mi_personaje.balas:
            if bala.rect.colliderect(mi_enemigo.rect):
                sonido_golpe.play()
                mi_enemigo.recibir_dano(3)  # Restar 5 puntos de vida al enemigo
                mi_personaje.balas.remove(bala)

            if bala.x < W and bala.x > 0:
            # Mueve la bala si está dentro de la ventana
                bala.x += bala.velocidad
            else:
            # Elimina la bala si está fuera de la ventana
                mi_personaje.balas.remove(bala)

    pygame.display.flip()
    if mi_enemigo.rect.left <= limite_izquierdo or mi_enemigo.rect.right >= limite_derecho:
        mi_enemigo.cambiar_direccion()

    # mi_personaje.update(PANTALLA, lista_plataformas)

    # # Actualizar enemigo
    # mi_enemigo.update(PANTALLA, lista_plataformas, mi_personaje.balas)

    actualizar_pantalla(PANTALLA, mi_personaje, fondo, lista_plataformas, plataforma_imagen, plataforma_imagen_2)
    for lado in lados_piso:

        pygame.draw.rect(PANTALLA,"Orange",lados_piso[lado],3)
    for lado in mi_personaje.lados:
            pygame.draw.rect(PANTALLA,"Blue",mi_personaje.lados[lado],3)

    for lado in lados_plataforma2:
        pygame.draw.rect(PANTALLA, "Green", lados_plataforma2[lado], 3)

    # Dibujar y actualizar enemigo
    enemigos_group.update(PANTALLA, lista_plataformas, mi_enemigo.balas )

    mi_personaje.dibujar(PANTALLA)
    mi_enemigo.dibujar(PANTALLA)
    sprites.update()
    sprites.draw(PANTALLA)
    # sprites = pygame.sprite.Group()
    # sprites = pygame.sprite.Group()

    pygame.draw.rect(PANTALLA, "Red", plataforma,3)
    pygame.draw.rect(PANTALLA,"Green", plataforma2, 3)

    # redraw()
    # for bala in mi_personaje.balas:
    #     bala.draw(PANTALLA)
    redraw()

    pygame.display.update()


# coding: utf-8
import datetime
import json
import time

import tweepy
import logging
from google.auth import jwt
from google.cloud import pubsub_v1
from tweepy.streaming import StreamListener

# Config
service_account_info = json.load(open("../twitter-stock-sentiment-83440f2bdf14.json"))
publisher_audience = "https://pubsub.googleapis.com/google.pubsub.v1.Publisher"
credentials = jwt.Credentials.from_service_account_info(
    service_account_info, audience=publisher_audience
)
publisher = pubsub_v1.PublisherClient(credentials=credentials)
topic_path = publisher.topic_path("twitter-stock-sentiment", "stock-twitter")

# Load in a json file with your Tweepy API credentials
with open("account.json") as json_data:
    account_data = json.load(json_data)

# Select the account you want to listen with
listener_1 = account_data["user_1"]

# Authenticate to the API
auth = tweepy.OAuthHandler(listener_1["consumer_key"], listener_1["consumer_secret"])
auth.set_access_token(listener_1["access_token"], listener_1["access_token_secret"])

api = tweepy.API(auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=False)

# Define the list of terms to listen to
lst_hashtags = ["#tesla", "#elon musk", "#tsla"]

# Method to push messages to pubsub
def write_to_pubsub(data):
    try:
        if data["lang"] == "en":
            sent_json_data = json.dumps({
                "id": data["id"],
                "text": data["text"],
                "user_id": data["user_id"],
                "posted_at": datetime.datetime.fromtimestamp(data["created_at"]).strftime('%Y-%m-%d %H:%M:%S')
            }).encode("utf-8")
            publisher.publish(topic_path, data=json.dumps({
                "id": data["id"],
                "text": data["text"],
                "user_id": data["user_id"],
                "posted_at": datetime.datetime.fromtimestamp(data["created_at"]).strftime('%Y-%m-%d %H:%M:%S')
            }).encode("utf-8"), tweet_id=str(data["id"]).encode("utf-8"))
            print('{} is written to pubsub'.format(data['text']))
            print('{} json data is sent'.format(sent_json_data))
    except Exception as e:
        raise

# Method to format a tweet from tweepy
def reformat_tweet(tweet):
    x = tweet

    processed_doc = {
        "id": x["id"],
        "lang": x["lang"],
        "retweeted_id": x["retweeted_status"]["id"] if "retweeted_status" in x else None,
        "favorite_count": x["favorite_count"] if "favorite_count" in x else 0,
        "retweet_count": x["retweet_count"] if "retweet_count" in x else 0,
        "coordinates_latitude": x["coordinates"]["coordinates"][0] if x["coordinates"] else 0,
        "coordinates_longitude": x["coordinates"]["coordinates"][0] if x["coordinates"] else 0,
        "place": x["place"]["country_code"] if x["place"] else None,
        "user_id": x["user"]["id"],
        "created_at": time.mktime(time.strptime(x["created_at"], "%a %b %d %H:%M:%S +0000 %Y"))
    }

    if x["entities"]["hashtags"]:
        processed_doc["hashtags"] = [{"text": y["text"], "startindex": y["indices"][0]} for y in
                                     x["entities"]["hashtags"]]
    else:
        processed_doc["hashtags"] = []

    if x["entities"]["user_mentions"]:
        processed_doc["usermentions"] = [{"screen_name": y["screen_name"], "startindex": y["indices"][0]} for y in
                                         x["entities"]["user_mentions"]]
    else:
        processed_doc["usermentions"] = []

    if "extended_tweet" in x:
        processed_doc["text"] = x["extended_tweet"]["full_text"]
    elif "full_text" in x:
        processed_doc["text"] = x["full_text"]
    else:
        processed_doc["text"] = x["text"]

    return processed_doc

# Custom listener class
class StdOutListener(StreamListener):
    """ A listener handles tweets that are received from the stream.
    This is a basic listener that just pushes tweets to pubsub
    """

    def __init__(self):
        super(StdOutListener, self).__init__()
        self._counter = 0

    def on_status(self, data):
        write_to_pubsub(reformat_tweet(data._json))
        self._counter += 1
        logging.info('{} tweets received'.format(self._counter))
        return True

    def on_error(self, status):
        if status == 420:
            print("rate limit active")
            return False

# Start listening
l = StdOutListener()
stream = tweepy.Stream(auth, l, tweet_mode='extended')
stream.filter(track=lst_hashtags)


#!/usr/bin/env python3
# coding=utf-8
import array
import random
import sys
import time

from crc16pure import crc16xmodem
from spidriver import SPIDriver

# print(hex(crc16xmodem(bytes([0xaa, 0xbb, 0xcc]), 0xffff)))

def rnd(n):
    return random.randrange(n)

if __name__ == '__main__':
    s = SPIDriver(sys.argv[1])
    t1 = time.time() + float(sys.argv[2])
    i = 0
    random.seed(7)
    while time.time() < t1:
        expected = s.ccitt_crc
        s.unsel()
        l = 1 + rnd(100)
        db = [rnd(256) for j in range(l)]
        s.write(db)
        expected = crc16xmodem(db, expected)
        s.unsel()

        db = [rnd(256) for j in range(64)]
        r = list(array.array('B', s.writeread(db)))
        expected = crc16xmodem(db, expected)
        expected = crc16xmodem(r, expected)

        s.getstatus()
        print("expected=%04x actual=%04x" % (expected, s.ccitt_crc))
        assert expected == s.ccitt_crc, "pass %d with %d bytes %s, expected=%04x actual=%04x" % (i, len(db), list(db), expected, s.ccitt_crc)
        i += 1
    for i in range(20):
        s.seta(0)
        s.setb(0)
        s.seta(1)
        s.setb(1)


from .models import *
from celery import shared_task
from .models import *

@shared_task
def process_operation(amount, client, operation):
    user = Client.objects.get(id=client)
    if operation == 'addition':
        user.balance += int(amount)
        user.save()

    elif operation == 'substraction':
        user.hold += int(amount)
        user.save()

@shared_task
def process_hold():
    clients = Client.objects.filter(hold__gt=0, balance__gt=0, status=True)
    for client in clients:
        if client.balance >= client.hold:
            client.balance -= client.hold
            client.hold = 0
        else:
            amount = client.balance
            client.hold -= client.balance
            client.balance = 0
        client.save()

    print('Processed {} accounts'.format(len(clients)))


from r2a.ir2a import IR2A
from player.parser import *
import time
import math
import matplotlib.pyplot as plt
from base.timer import Timer

class R2A_BBA0(IR2A):
    def __init__(self, id):
        IR2A.__init__(self, id)

        # C(t) is system capacity

        self.qi = []
        self.throughputs = []
        self.timer = Timer.get_instance()
        self.request_time = 0

        self.reservoir = 20
        self.upper_reservoir = 54

        self.rate_index: int = 0
        self.rate_index_min: int = 0
        self.rate_index_max: int = None

        self.buffer_size: int = 0

    def handle_xml_request(self, msg):
        self.request_time = time.perf_counter()
        self.send_down(msg)

    def handle_xml_response(self, msg):
        self.parsed_mpd = parse_mpd(msg.get_payload())

        self.qi = self.parsed_mpd.get_qi()
        self.rate_index_max = len(self.qi) - 1

        self.send_up(msg)

    def handle_segment_size_request(self, msg):
        self.buffer_size = self.whiteboard.get_amount_video_to_play()

        if self.buffer_size <= self.reservoir:
            self.rate_index = self.rate_index_min
        elif self.buffer_size >= self.upper_reservoir:
            self.rate_index = self.rate_index_max
        else:
            ideal_rate_index = (
                (self.buffer_size - self.reservoir) * self.rate_index_max
            ) / (self.upper_reservoir - self.reservoir)

            if ideal_rate_index >= (self.rate_index + 1):
                self.rate_index = math.floor(ideal_rate_index)
            elif ideal_rate_index <= (self.rate_index - 1):
                self.rate_index = math.ceil(ideal_rate_index)

        msg.add_quality_id(self.qi[self.rate_index])
        self.request_time = time.perf_counter()

        self.send_down(msg)

    def handle_segment_size_response(self, msg):
        current_time = self.timer.get_current_time()
        t = time.perf_counter() - self.request_time
        self.throughputs.append((current_time, (msg.get_bit_length() / t)))
        self.send_up(msg)

    def initialize(self):
        pass

    def finalization(self):
        self.draw_graphs(self.whiteboard.get_playback_qi(), self.throughputs,
                         "QIvsThroughput", "Quality Index vs. Throughput", "QI")
        self.draw_graphs(self.whiteboard.get_playback_buffer_size(), self.throughputs,
                         "BuffervsThroughput", "Buffer Size vs. Throughput", "Buffer")
        self.draw_graphs(self.whiteboard.get_playback_buffer_size(),
                         self.whiteboard.get_playback_qi(), "BuffervsQI",
                         "Buffer Size vs. Quality Index", "Buffer", "QI", tp=False)

    def draw_graphs(self, data1, data2, file_name, title, y_axis_left,
                    y_axis_right="Mbps", x_axis="execution_time (s)", tp=True):
        x1 = [item[0] for item in data1]
        y1 = [item[1] for item in data1]

        x2 = [item[0] for item in data2]
        y2 = [item[1] for item in data2]

        _, ax1 = plt.subplots()
        ax1.plot(x1, y1, color="blue")
        ax1.set_xlabel(x_axis)
        ax1.set_ylabel(y_axis_left, color="blue")
        ax1.tick_params(axis="y", labelcolor="blue")
        plt.ylim(min(y1), max(y1) * 4 / 3)

        if tp:
            ax2 = ax1.twinx()
            ax2.vlines(x2, [0], y2, color="red")
            ax2.set_ylabel(y_axis_right, color="red")
            ax2.tick_params(axis="y", labelcolor="red")
            plt.ylim(min(y2), max(y2) * 4 / 3)
        else:
            ax2 = ax1.twinx()
            ax2.set_ylabel(y_axis_right, color="green")
            ax2.tick_params(axis="y", labelcolor="green")
            plt.ylim(min(y2), max(y2) * 4 / 3)
            ax2.plot(x2, y2, color="green")

        plt.title(title)
        plt.savefig(f'./results/{file_name}.png')
        plt.clf()
        plt.cla()
        plt.close()


import os
from skimage import io
import numpy as np
from numpy import ndarray
import pandas as pd

def read_image(path_of_image_folder):
    """
    This function reads images from a given path and puts them into a list of arrays.
    :param path_of_image_folder: path of the images
    :return: list of np.arrays
    """
    image_list = []
    if not os.path.exists(path_of_image_folder):
        raise FileNotFoundError
    for filename in os.listdir(path_of_image_folder):
        img = io.imread(os.path.join(path_of_image_folder, filename))
        image = np.asarray(img)
        if img is not None:
            image_list.append(image)
    return image_list

def read_imagename(path_of_image_folder):
    """
    This function creates a list of the filenames in a directory.
    :param: path_of_image path of the folder with the files the filenames are wanted from
    :return: a list of the filenames
    """
    name_list = []
    if not os.path.exists(path_of_image_folder):
        raise FileNotFoundError
    for filename in os.listdir(path_of_image_folder):
        if filename is not None:
            name_list.append(filename)
    return name_list

def image_flatten(image_list):
    """
    This function flattens all arrays of a list.
    :param image_list: A list of images as arrays.
    :return: A list of flattened arrays.
    """
    # if type(image_list) != 'list':
    # raise TypeError("Input has to be of type 'list'.")
    image_list_flattened = []
    for element in image_list:
        if element is not None:
            flattened = ndarray.flatten(element)
            reshaped = flattened.reshape(1, -1)
            image_list_flattened.append(reshaped)
    return image_list_flattened

def dataframe(image_list, name_list):
    """
    This function creates a dataframe from a list of flattened arrays (as rows) and their names as row names.
    :param image_list: A list of flattened arrays.
    :param name_list: A list of names, in the same order as the arrays in the image_list.
    :return:
    """
    if len(image_list) != len(name_list):
        raise ValueError("Lists have to be of the same length.")
    dataframe_images = pd.DataFrame()
    for i in range(0, len(image_list)):
        array = image_list[i]
        if array.shape[0] != 1:
            raise ValueError("Array has to be of the shape (1, x).")
        element = pd.DataFrame(array)
        i += 1
        dataframe_images = dataframe_images.append(element)
    dataframe_images = dataframe_images.set_axis(name_list, axis=0)
    dataframe_images = dataframe_images.transpose()
    return dataframe_images

def fuse_dataframes(dataframe1, name1, dataframe2, name2, dataframe3, name3):
    """
    Fusing 3 dataframes, with similiar content, by inserting the first row of dataframe2 and after that of dataframe 3,
    after the first row of dataframe 1. The name of the dataframe is added to the according rownames to distinguish
    between the different samples.
    :param dataframe1: dataframe 1, with pixels as features and rows as samples.
    :param dataframe2: dataframe 2, with pixels as features and rows as samples.
    :param dataframe3: dataframe 3, with pixels as features and rows as samples.
    :param name1: names of images of dataframe 1
    :param name2: names of images of dataframe 2
    :param name3: names of images of dataframe 3
    :return: dataframe composed of all 3 input-dataframes
    """
    if dataframe1.shape != dataframe2.shape != dataframe3.shape:
        raise ValueError("Dataframes have to be the same shape.")
    fused_dataframe = pd.DataFrame()
    dataframe1 = dataframe1.rename(index=lambda s: s + str(name1))
    dataframe2 = dataframe2.rename(index=lambda s: s + str(name2))
    dataframe3 = dataframe3.rename(index=lambda s: s + str(name3))
    for row in range(0, len(dataframe1)):
        for dataframe in [dataframe1, dataframe2, dataframe3]:
            fused_dataframe = fused_dataframe.append(dataframe.iloc[row, :])
        row += 1
    return fused_dataframe

if __name__ == '__main__':
    # test
    dataframe1 = pd.DataFrame([['A', 'B', 'C', 'D'], ['E', 'F', 'G', 'H'], ['I', 'J', 'K', 'L'], ['M', 'N', 'O', 'P']])
    dataframe2 = pd.DataFrame(
        [['1', '2', '3', '4'], ['5', '6', '7', '8'], ['9', '10', '11', '12'], ['13', '14', '15', '16']])
    dataframe3 = pd.DataFrame([['a', 'b', 'c', 'd'], ['e', 'f', 'g', 'h'], ['i', 'j', 'k', 'l'], ['m', 'n', 'o', 'p']])

    dataframe1_names = ['A', 'B', 'C', 'D']
    dataframe2_names = ['1', '2', '3', '4']
    dataframe3_names = ['a', 'b', 'c', 'd']

    d1 = dataframe1.set_axis(dataframe1_names, axis=0)
    d2 = dataframe2.set_axis(dataframe2_names, axis=0)
    d3 = dataframe3.set_axis(dataframe3_names, axis=0)

    # print(fuse_dataframes(d1, 'd1', d2, 'd2', d3, 'd3'))

    # Tests

    imageread1 = read_image('../Data/N2DH-GOWT1/img')
    # print(imageread1)
    imagenames1 = read_imagename('../Data/N2DH-GOWT1/img')
    # print(imagenames1)
    imageflattened1 = image_flatten(imageread1)
    # print(imageflattened1)
    data1 = dataframe(imageflattened1, imagenames1)
    # print(data1)

    imageread2 = read_image('../Data/N2DL-HeLa/img')
    # print(imageread2)
    imagenames2 = read_imagename('../Data/N2DL-HeLa/img')
    # print(imagenames2)
    imageflattened2 = image_flatten(imageread2)
    # print(imageflattened2)
    data2 = dataframe(imageflattened2, imagenames2)
    # print(data2)

    imageread3 = read_image('../Data/NIH3T3/img')
    # print(imageread3)
    imagenames3 = read_imagename('../Data/NIH3T3/img')
    # print(imagenames3)
    imageflattened3 = image_flatten(imageread3)
    # print(imageflattened3)
    data3 = dataframe(imageflattened3, imagenames3)
    # print(data3)

    # print(fuse_dataframes(data1, 'd1', data1, 'd2', data1, 'd3'))


"""arrayframe.py
A frame for holding different data types which are similar to a numpy array.
"""
# Package Header #
from ..header import *

# Header #
__author__ = __author__
__credits__ = __credits__
__maintainer__ = __maintainer__
__email__ = __email__

# Imports #
# Standard Libraries #
from collections.abc import Iterable
from bisect import bisect_right
from typing import Any, NamedTuple
from warnings import warn

# Third-Party Packages #
from baseobjects.functions import singlekwargdispatch
from baseobjects.cachingtools import CachingObject, timed_keyless_cache
import numpy as np

# Local Packages #
from .arrayframeinterface import ArrayFrameInterface
from framestructure.arrayframe.arraycontainer import ArrayContainer

# Definitions #
# Classes #
class FrameIndex(NamedTuple):
    index: int | None
    start_index: int | None
    inner_index: int | None

class RangeIndices(NamedTuple):
    start: FrameIndex | int | None
    stop: FrameIndex | int | None

class ArrayFrame(ArrayFrameInterface):
    """A frame for holding different data types which are similar to a numpy array.

    This object acts as an abstraction of several contained numpy like objects, to appear as combined numpy array.
    Accessing the data within can be used with standard numpy data indexing which is based on the overall data at the
    deepest layer of the structure.

    Class Attributes:
        default_return_frame_type: The default type of frame to return when returning a frame.
        default_combine_type: The default type of combined frame to return when combining frames.

    Attributes:
        mode: Determines if this frame is editable or read only.
        target_shape: The shape that frame should be and if resized the shape it will default to.
        axis: The axis of the data which this frame extends for the contained data frames.
        combine_type: The object type to return when combining frames.
        return_frame_type: The frame type to return when returning frames from this object.
        frames: The list of frames/objects in this frame.

    Args:
        frames: An iterable holding frames/objects to store in this frame.
        mode: Determines if the contents of this frame are editable or not.
        update: Determines if this frame will start updating or not.
        init: Determines if this object will construct.
    """

    # TODO: Consider making the frame work multidimensional. (Only single-dimensional right now.)
    default_return_frame_type: type = ArrayFrameInterface
    default_combine_type: type | None = ArrayContainer

    # Magic Methods
    # Construction/Destruction
    def __init__(
        self,
        frames: Iterable[ArrayFrameInterface] | None = None,
        mode: str = "a",
        update: bool = True,
        init: bool = True,
        *args: Any,
        **kwargs: Any,
    ) -> None:
        # Parent Attributes #
        super().__init__(*args, int=init, **kwargs)

        # New Attributes #
        # Shape
        self.target_shape: Iterable[int] | None = None
        self.axis: int = 0

        # Assign Classes #
        self.combine_type: type = self.default_combine_type
        self.return_frame_type: type = self.default_return_frame_type

        # Containers #
        self.frames: list = []

        # Object Construction #
        if init:
            self.construct(frames=frames, mode=mode, update=update)

    @property
    def shapes(self) -> tuple[tuple[int]]:
        """Returns the shapes of all contained frames and uses cached value if available."""
        try:
            return self.get_shapes.caching_call()
        except AttributeError:
            return self.get_shapes()

    @property
    def min_shape(self) -> tuple[tuple[int]]:
        """Get the minimum shapes from the contained frames/objects if they are different across axes."""
        try:
            return self.get_min_shape.caching_call()
        except AttributeError:
            return self.get_min_shape()

    @property
    def max_shape(self) -> tuple[tuple[int]]:
        """Get the maximum shapes from the contained frames/objects if they are different across axes."""
        try:
            return self.get_max_shape.caching_call()
        except AttributeError:
            return self.get_max_shape()

    @property
    def shape(self) -> tuple[int]:
        """Returns the shape of this frame if all contained shapes are the same and uses cached value if available."""
        try:
            return self.get_shape.caching_call()
        except AttributeError:
            return self.get_shape()

    @property
    def max_ndim(self) -> int:
        """The maximum number dimension in the contained frames/objects if they are different across axes."""
        return len(self.max_shape)

    @property
    def min_ndim(self) -> int:
        """The minimum number dimension in the contained frames/objects if they are different across axes."""
        return len(self.min_shape)

    @property
    def lengths(self) -> tuple[int]:
        """Returns the lengths of each contained frames as a tuple and uses cached value if available."""
        try:
            return self.get_lengths.caching_call()
        except AttributeError:
            return self.get_lengths()

    @property
    def length(self) -> int:
        """Returns the sum of all lengths of contained frames and uses cached value if available."""
        try:
            return self.get_length.caching_call()
        except AttributeError:
            return self.get_length()

    @property
    def frame_start_indices(self) -> tuple[int]:
        """Returns the start index of the contained frames and uses cached value if available."""
        try:
            return self.get_frame_start_indices.caching_call()
        except AttributeError:
            return self.get_frame_start_indices()

    @property
    def frame_end_indices(self) -> tuple[int]:
        """Returns the end index of the contained frames and uses cached value if available."""
        try:
            return self.get_frame_end_indices.caching_call()
        except AttributeError:
            return self.get_frame_end_indices()

    # Container Methods
    def __len__(self) -> int:
        """Gets this object's length.

        Returns:
            The number of nodes in this object.
        """
        try:
            return self.get_length.caching_call()
        except AttributeError:
            return self.get_length()

    # Arithmetic
    def __add__(self, other: ArrayFrameInterface | list):
        """When the add operator is called it concatenates this frame with other frames or a list."""
        return self.concatenate(other=other)

    # Numpy ndarray Methods
    def __array__(self, dtype: Any = None) -> np.ndarray:
        """Returns an ndarray representation of this object with an option to cast it to a dtype.

        Allows this object to be used as ndarray in numpy functions.

        Args:
            dtype: The dtype to cast the array to.

        Returns:
            The ndarray representation of this object.
        """
        if dtype is None:
            return self[...]
        else:
            return self[...].as_type(dtype)

    # Instance Methods
    # Constructors/Destructors
    def construct(
        self,
        frames: Iterable[ArrayFrameInterface] = None,
        mode: str = None,
        update: bool | None = None,
    ) -> None:
        """Constructs this object.

        Args:
            frames: An iterable holding frames/objects to store in this frame.
            mode: Determines if the contents of this frame are editable or not.
            update: Determines if this frame will start updating or not.
        """
        if update is not None:
            self._is_updating = update
            if update:
                self.enable_updating()
            else:
                self.disable_updating()

        if frames is not None:
            self.frames.clear()
            self.frames.extend(frames)

        if mode is not None:
            self.mode = mode

    # Editable Copy Methods
    def _default_spawn_editable(self, *args: Any, **kwargs: Any) -> ArrayFrameInterface:
        """The default method for creating an editable version of this frame.

        Returns:
            An editable version of this frame.
        """
        return self.combine_frames()

    # Cache and Memory
    def refresh(self) -> None:
        """Resets this frame's caches and fills them with updated values."""
        self.get_shapes()
        self.get_shape()
        self.get_lengths()
        self.get_length()

    def clear_all_caches(self) -> None:
        """Clears this frame's caches and all the contained frame's caches."""
        self.clear_caches()
        for frame in self.frames:
            try:
                frame.clear_all_caches()
            except AttributeError:
                continue

    # Updating
    def enable_updating(self, get_caches: bool = False) -> None:
        """Enables updating for this frame and all contained frames/objects.

        Args:
            get_caches: Determines if get_caches will run before setting the caches.
        """
        self.timed_caching(get_caches=get_caches)
        for frame in self.frames:
            frame.enable_updating(get_caches=get_caches)

    def enable_last_updating(self, get_caches: bool = False) -> None:
        """Enables updating for this frame and the last contained frame/object.

        Args:
            get_caches: Determines if get_caches will run before setting the caches.
        """
        self.timed_caching(get_caches=get_caches)
        try:
            self.frames[-1].enable_updating(get_caches=get_caches)
        except IndexError as e:
            pass  # Maybe raise warning.

    def disable_updating(self, get_caches: bool = False) -> None:
        """Disables updating for this frame and all contained frames/objects.

        Args:
            get_caches: Determines if get_caches will run before setting the caches.
        """
        self.timeless_caching(get_caches=get_caches)
        for frame in self.frames:
            frame.disable_updating(get_caches=get_caches)

    def clear(self) -> None:
        """Clears this object."""
        self.frames.clear()
        self.clear_caches()

    # Getters
    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_any_updating(self) -> bool:
        """Checks if any contained frames/objects are updating.

        Returns:
            If any contained frames/objects are updating.
        """
        for frame in self.frames:
            if frame.get_any_updating():
                return True
        return False

    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_shapes(self) -> tuple[tuple[int]]:
        """Get the shapes from the contained frames/objects.

        Returns:
            The shapes of the contained frames/objects.
        """
        return tuple(frame.get_shape() for frame in self.frames)

    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_min_shape(self) -> tuple[int]:
        """Get the minimum shapes from the contained frames/objects if they are different across axes.

        Returns:
            The minimum shapes of the contained frames/objects.
        """
        n_frames = len(self.frames)
        n_dims = [None] * n_frames
        shapes = [None] * n_frames
        for index, frame in enumerate(self.frames):
            shapes[index] = shape = frame.get_shape()
            n_dims[index] = len(shape)

        max_dims = max(n_dims)
        shape_array = np.zeros((n_frames, max_dims), dtype="i")
        for index, s in enumerate(shapes):
            shape_array[index, : n_dims[index]] = s

        shape = [None] * max_dims
        for ax in range(max_dims):
            if ax == self.axis:
                shape[ax] = sum(shape_array[:, ax])
            else:
                shape[ax] = min(shape_array[:, ax])
        return tuple(shape)

    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_max_shape(self) -> tuple[int]:
        """Get the maximum shapes from the contained frames/objects if they are different across axes.

        Returns:
            The maximum shapes of the contained frames/objects.
        """
        n_frames = len(self.frames)
        n_dims = [None] * n_frames
        shapes = [None] * n_frames
        for index, frame in enumerate(self.frames):
            shapes[index] = shape = frame.get_shape()
            n_dims[index] = len(shape)

        max_dims = max(n_dims)
        shape_array = np.zeros((n_frames, max_dims), dtype="i")
        for index, s in enumerate(shapes):
            shape_array[index, : n_dims[index]] = s

        shape = [None] * max_dims
        for ax in range(max_dims):
            if ax == self.axis:
                shape[ax] = sum(shape_array[:, ax])
            else:
                shape[ax] = max(shape_array[:, ax])
        return tuple(shape)

    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_shape(self) -> tuple[int]:
        """Get the shape of this frame from the contained frames/objects.

         If the contained frames/object are different across axes this will raise a warning and return the minimum
         shape.

        Returns:
            The shape of this frame or the minimum shapes of the contained frames/objects.
        """
        if not self.validate_shape():
            warn(f"The ArrayFrame '{self}' does not have a valid shape, returning minimum shape.")
        return self.get_min_shape()

    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_lengths(self) -> tuple[int]:
        """Get the lengths of the contained frames/objects.

        Returns:
            All the lengths of the contained frames/objects.
        """
        shapes = self.get_shapes()
        lengths = [0] * len(shapes)
        for index, shape in enumerate(shapes):
            lengths[index] = shape[self.axis]

        return tuple(lengths)

    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_length(self) -> int:
        """Get the length of this frame as the sum of the contained frames/objects length.

        Returns:
            The length of this frame.
        """
        return int(sum(self.get_lengths()))

    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_frame_start_indices(self) -> tuple[int]:
        """Get the start indices of the contained files based on the lengths of the contained frames/objects.

        Returns:
            The start indices of each of the contained frames/objects.
        """
        lengths = self.lengths
        starts = [None] * len(lengths)
        previous = 0
        for index, frame_length in enumerate(self.lengths):
            starts[index] = int(previous)
            previous += frame_length
        return tuple(starts)

    @timed_keyless_cache(lifetime=1.0, call_method="clearing_call", local=True)
    def get_frame_end_indices(self) -> tuple[int]:
        """Get the end indices of the contained files based on the lengths of the contained frames/objects.

        Returns:
            The end indices of each of the contained frames/objects.
        """
        lengths = self.get_lengths()
        ends = [None] * len(lengths)
        previous = -1
        for index, frame_length in enumerate(lengths):
            previous += frame_length
            ends[index] = int(previous)
        return tuple(ends)

    # Main Get Item
    @singlekwargdispatch("item")
    def get_item(self, item: Any) -> Any:
        """Get an item from this frame based on the input. For Ellipsis return all the data.

        Args:
            item: An object to get an item from this frame.

        Returns:
            An object from this arrayframe
        """
        if item is Ellipsis:
            return self.get_all_data()
        else:
            raise TypeError(f"A {type(item)} cannot be used to get an item from {type(self)}.")

    @get_item.register
    def _(self, item: slice) -> Any:
        """Get an item from this frame based on a slice and return a range of contained data.

        Args:
            item: The slice to select the data range to return.

        Returns:
            The data of interest from within this frame.
        """
        return self.get_slice(item)

    @get_item.register
    def _(self, item: Iterable) -> Any:
        """Get an item from this frame based on an iterable and return a range of contained.

        Args:
            item: The slice to select the range to return.

        Returns:
            The of interest from within this frame.
        """
        is_slices = True
        for element in item:
            if not isinstance(element, slice):
                is_slices = False
                break

        if is_slices:
            return self.get_slices(item)
        else:
            return self.get_from_index(item)

    @get_item.register
    def _(self, item: int) -> ArrayFrameInterface:
        """Get an item from this frame based on an int and return a frame.

        Args:
            item: The index of the frame to return.

        Returns:
            The frame interest from within this frame.
        """
        return self.get_frame(item)

    # Shape
    def validate_shape(self) -> bool:
        """Checks if this frame has a valid/continuous shape.

        Returns:
            If this frame has a valid/continuous shape.
        """
        shapes = np.asarray(self.shapes)
        return np.delete(shapes == shapes[0], self.axis, 1).all()

    def resize(self, shape: Iterable[int] | None = None, **kwargs: Any) -> None:
        """Changes the shape of the frame without changing its data.

        Args:
            shape: The target shape to change this frame to.
            **kwargs: Any additional kwargs need to change the shape of contained frames/objects.
        """
        # Todo: Fix this for along length.
        if self.mode == "r":
            raise IOError("not writable")

        if shape is None:
            shape = self.target_shape

        for frame in self.frames:
            if not frame.validate_shape() or frame.shape != shape:
                frame.change_size(shape, **kwargs)

    # Frames
    def frame_sort_key(self, frame: Any) -> Any:
        """The key to be used in sorting with the frame as the sort basis.

        Args:
            frame: The frame to sort.
        """
        return frame

    def sort_frames(self, key: Any = None, reverse: bool = False) -> None:
        """Sorts the contained frames/objects.

        Args:
            key: The key base the sorting from.
            reverse: Determines if this frame will be sorted in reverse order.
        """
        if key is None:
            key = self.frame_sort_key
        self.frames.sort(key=key, reverse=reverse)

    @singlekwargdispatch("other")
    def concatenate(self, other: ArrayFrameInterface | list[ArrayFrameInterface]) -> ArrayFrameInterface:
        """Concatenates this frame object with another frame or a list.

        Args:
            other: The other object to concatenate this frame with.

        Returns:
            A new frame which is the concatenation of this frame and another object.
        """
        raise TypeError(f"A {type(other)} cannot be used to concatenate a {type(self)}.")

    @concatenate.register
    def _(self, other: ArrayFrameInterface) -> ArrayFrameInterface:
        """Concatenates this frame object with another frame.

        Args:
            other: The other frame to concatenate this frame with.

        Returns:
            A new frame which is the concatenation of this frame and the other frame.
        """
        return type(self)(frames=self.frames + other.frames, update=self.is_updating)

    @concatenate.register(list)
    def _(self, other: list[ArrayFrameInterface]) -> ArrayFrameInterface:
        """Concatenates this frame object with another list.

        Args:
            other: The other list to concatenate this frame with.

        Returns:
            A new frame which is the concatenation of this frame and another list.
        """
        return type(self)(frames=self.frames + other, update=self.is_updating)

    def append(self, item: Any) -> None:
        """Append an item to the frame.

        Args:
            item: The object to add to this frame.
        """
        self.frames.append(item)

    def combine_frames(
        self, start: int | None = None, stop: int | None = None, step: int | None = None
    ) -> ArrayFrameInterface:
        """Combines a range of frames into a single frame.

        Args:
            start: The start frame.
            stop: The stop frame.
            step: The step between frames to combine.

        Returns:
            A single combined frame.
        """
        return self.combine_type(frames=self.frames[start:stop:step])

    # Get Frame within by Index
    @singlekwargdispatch("indices")
    def get_from_index(
        self,
        indices: Iterable[int | slice | Iterable] | int | slice,
        reverse: bool = False,
        frame: bool | None = None,
    ) -> Any:
        """Gets data from this object if given an index which can be in serval formats.

        Args:
            indices: The indices to find the data from.
            reverse:  Determines, when using a Iterable of indices, if it will be read in reverse order.
            frame: Determines if returned object is a Frame or an array, default is this object's setting.

        Returns:
            The requested frame or data.
        """
        raise TypeError(f"A {type(indices)} be used to get from super_index for a {type(self)}.")

    @get_from_index.register(Iterable)
    def _(
        self,
        indices: Iterable[int | slice | Iterable[slice | int | None]],
        reverse: bool = False,
        frame: bool | None = None,
    ) -> ArrayFrameInterface | np.ndarray:
        """Gets a nested frame or data from within this frame.

        Args:
            indices: A series of indices of the nested frames to get from, can end with slices to get ranges of data.
            reverse: Determines if the indices series will be read in reverse order.
            frame:  Determines if returned object is a Frame or an array, default is this object's setting.

        Returns:
            The requested frame or data.
        """
        indices = list(indices)
        if not reverse:
            index = indices.pop(0)
        else:
            index = indices.pop()

        if indices:
            return self.frames[index].get_from_index(indices=indices, reverse=reverse, frame=frame)
        elif isinstance(index, Iterable):
            return self.get_slices(slices=index, frame=frame)
        elif isinstance(index, int):
            return self.get_from_index(indices=index, frame=frame)
        else:
            return self.get_slice(item=indices, frame=frame)

    @get_from_index.register
    def _(self, indices: int, frame: bool | None = None) -> Any:
        """Get a contained frame/object or data from a contained frame/object.

        Args:
            indices: The frame index to get the data from.
            frame: Determines if returned object is a Frame or an array, default is this object's setting.

        Returns:
            The requested frame or data.
        """
        if (frame is None and self.returns_frame) or frame:
            return self.get_frame(index=indices)
        else:
            frame_index, _, inner_index = self.find_inner_frame_index(super_index=indices)
            return self.frames[frame_index].get_from_index(indices=inner_index)

    # Find Inner Indices within Frames
    def find_inner_frame_index(self, super_index: int) -> FrameIndex:
        """Find the frame and inner index of a super index.

        Args:
            super_index: The super index to find.

        Returns:
            The index information as a FrameIndex.
        """
        length = self.length
        frame_start_indices = self.frame_start_indices

        # Check if index is in range.
        if super_index >= length or (super_index + length) < 0:
            raise IndexError("index is out of range")

        # Change negative indexing into positive.
        if super_index < 0:
            super_index = length - super_index

        # Find
        frame_index = bisect_right(frame_start_indices, super_index) - 1
        frame_start_index = frame_start_indices[frame_index]
        frame_inner_index = int(super_index - frame_start_index)
        return FrameIndex(frame_index, frame_start_index, frame_inner_index)

    def find_inner_frame_indices(self, super_indices: Iterable[int]) -> tuple[FrameIndex]:
        """Find the frame and inner index of several super indices.

        Args:
            super_indices: The super indices to find.

        Returns:
            The indices' information as FrameIndex objects.
        """
        length = self.length
        frame_start_indices = self.frame_start_indices
        super_indices = list(super_indices)
        inner_indices = [None] * len(super_indices)

        # Validate Indices
        for i, super_index in enumerate(super_indices):
            # Check if super_index is in range.
            if super_index >= length or (super_index + length) < 0:
                raise IndexError("super_index is out of range")

            # Change negative indexing into positive.
            if super_index < 0:
                super_indices[i] = self.length + super_index

        # Finding Methods
        if len(super_indices) <= 32:  # Few indices to find
            for i, super_index in enumerate(super_indices):
                frame_index = bisect_right(frame_start_indices, super_index) - 1
                frame_start_index = frame_start_indices[frame_index]
                frame_inner_index = int(super_index - frame_start_index)
                inner_indices[i] = FrameIndex(frame_index, frame_start_index, frame_inner_index)
        else:  # Many indices to find
            frame_indices = list(np.searchsorted(frame_start_indices, super_indices, side="left"))
            for i, frame_index in enumerate(frame_indices):
                frame_start_index = frame_start_indices[frame_index]
                frame_inner_index = int(super_indices[i] - frame_start_index)
                inner_indices[i] = FrameIndex(frame_index, frame_start_index, frame_inner_index)

        return tuple(inner_indices)

    def parse_range_super_indices(self, start: int | None = None, stop: int | None = None) -> RangeIndices:
        """Parses indices for a range and returns them as FrameIndex objects.

        Args:
            start: The start index of the range.
            stop: The stop index of the range.

        Returns:
            The start and stop indices as FrameIndex objects in a RangeIndices object.
        """
        if start is not None and stop is not None:
            start_index, stop_index = self.find_inner_frame_indices([start, stop])
        else:
            if start is not None:
                start_index = self.find_inner_frame_index(start)
            else:
                start_index = FrameIndex(0, 0, 0)
            if stop is not None:
                stop_index = self.find_inner_frame_index(stop)
            else:
                stop_frame = len(self.frames) - 1
                stop_index = FrameIndex(
                    stop_frame,
                    self.frame_start_indices[stop_frame],
                    self.lengths[stop_frame],
                )

        return RangeIndices(start_index, stop_index)

    # Get Ranges of Data with Slices
    def get_slices(self, slices: Iterable[slice], frame: bool | None = None) -> ArrayFrameInterface | np.ndarray:
        """Get data from within using slices to determine the ranges.

        Args:
            slices: The slices along the axes to get data from
            frame: Determines if returned object is a Frame or an array, default is this object's setting.

        Returns:
            The requested range.
        """
        if (frame is None and self.returns_frame) or frame:
            return self.get_slices_frame(slices=slices)
        else:
            return self.get_slices_array(slices=slices)

    def get_slices_frame(self, slices: Iterable[slice] | None = None) -> ArrayFrameInterface:
        """Gets a range of data as a new frame.

        Args:
            slices: The ranges to get the data from.

        Returns:
            The requested range as a frame.
        """
        slices = list(slices)
        axis_slice = slices[self.axis]
        range_frame_indices = self.parse_range_super_indices(start=axis_slice.start, stop=axis_slice.stop)

        start_frame = range_frame_indices.start.index
        stop_frame = range_frame_indices.stop.index

        return self.return_frame_type(frames=[self.frames[start_frame:stop_frame]])

    def get_slices_array(self, slices: Iterable[slice | int | None] | None = None) -> np.ndarray:
        """Gets a range of data as an array.

        Args:
            slices: The ranges to get the data from.

        Returns:
            The requested range as an array.
        """
        if slices is None:
            slices = [slice(None)] * self.max_ndim

        # Create nan numpy array
        slices = list(slices)
        full_slices = slices + [slice(None)] * (self.max_ndim - len(slices))
        t_shape = [None] * len(full_slices)
        for i, slice_ in enumerate(full_slices):
            if slice_ is not None:
                start = 0 if slice_.start is None else slice_.start
                stop = self.max_shape[i] if slice_.stop is None else slice_.stop
                step = 1 if slice_.step is None else slice_.step
                t_shape[i] = int(stop - start) // step
            else:
                t_shape[i] = 1
        data = np.empty(shape=t_shape)
        # data.fill(np.nan)

        # Get range via filling the array with values
        return self.fill_slices_array(data_array=data, slices=full_slices)

    def fill_slices_array(
        self,
        data_array: np.ndarray,
        array_slices: Iterable[slice] | None = None,
        slices: Iterable[slice | int | None] | None = None,
    ) -> np.ndarray:
        """Fills a given array with values from the contained frames/objects.

        Args:
            data_array: The numpy array to fill.
            array_slices: The slices to fill within the data_array.
            slices: The slices to get the data from.

        Returns:
            The original array but filled.
        """
        slices = list(slices)
        # Get indices range
        da_shape = data_array.shape
        axis_slice = slices[self.axis]
        range_frame_indices = self.parse_range_super_indices(start=axis_slice.start, stop=axis_slice.stop)

        start_frame = range_frame_indices.start.index
        stop_frame = range_frame_indices.stop.index
        inner_start = range_frame_indices.start.inner_index
        inner_stop = range_frame_indices.stop.inner_index
        slices[self.axis] = slice(inner_start, inner_stop, axis_slice.step)

        # Get start and stop data array locations
        array_slices = [slice(None)] * len(da_shape) if array_slices is None else list(array_slices)

        da_axis_slice = array_slices[self.axis]
        array_start = 0 if da_axis_slice.start is None else da_axis_slice.start
        array_stop = da_shape[self.axis] if da_axis_slice.stop is None else da_axis_slice.stop

        # Contained frame/object fill kwargs
        fill_kwargs = {
            "data_array": data_array,
            "array_slices": array_slices,
            "slices": slices,
        }

        # Get Data
        if start_frame == stop_frame:
            self.frames[start_frame].fill_slices_array(**fill_kwargs)
        else:
            # First Frame
            frame = self.frames[start_frame]
            d_size = len(frame) - inner_start
            a_stop = array_start + d_size
            array_slices[self.axis] = slice(array_start, a_stop, da_axis_slice.step)
            slices[self.axis] = slice(inner_start, None, axis_slice.step)
            frame.fill_slices_array(**fill_kwargs)

            # Middle Frames
            slices[self.axis] = slice(None, None, axis_slice.step)
            for frame in self.frames[start_frame + 1 : stop_frame]:
                d_size = len(frame)
                a_start = a_stop
                a_stop = a_start + d_size
                array_slices[self.axis] = slice(a_start, a_stop, da_axis_slice.step)
                frame.fill_slices_array(**fill_kwargs)

            # Last Frame
            a_start = a_stop
            array_slices[self.axis] = slice(a_start, array_stop, da_axis_slice.step)
            slices[self.axis] = slice(None, inner_stop, axis_slice.step)
            self.frames[stop_frame].fill_slices_array(**fill_kwargs)

        return data_array

    # Get Range Along Axis
    def get_range(
        self,
        start: int | None = None,
        stop: int | None = None,
        step: int | None = None,
        axis: int | None = None,
        frame: bool | None = None,
    ) -> ArrayFrameInterface | np.ndarray:
        """Gets a range of data along an axis.

        Args:
            start: The first super index of the range to get.
            stop: The length of the range to get.
            step: The interval to get the data of the range.
            axis: The axis to get the data along.
            frame: Determines if returned object is a Frame or an array, default is this object's setting.

        Returns:
            The requested range.
        """
        if axis is None:
            axis = self.axis

        slices = [slice(None)] * self.max_ndim
        slices[axis] = slice(start, stop, step)
        if (frame is None and self.returns_frame) or frame:
            return self.get_slices_frame(slices=slices)
        else:
            return self.get_slices_array(slices=slices)

    def get_slice(
        self,
        item: slice,
        axis: int | None = None,
        frame: bool | None = None,
    ) -> ArrayFrameInterface | np.ndarray:
        """Gets a range of data along the main axis using a slice.

        Args:
            item: The slice which is the range to get the data from.
            axis: The axis to get the data along.
            frame: Determines if returned object is a Frame or an array, default is this object's setting.

        Returns:
            The requested range.
        """
        if axis is None:
            axis = self.axis

        slices = [slice(None)] * self.max_ndim
        slices[axis] = item
        if (frame is None and self.returns_frame) or frame:
            return self.get_slices_frame(slices=slices)
        else:
            return self.get_slices_array(slices=slices)

    # Get Frame based on Index
    def get_frame(self, index: int, frame: bool | None = None) -> ArrayFrameInterface | np.ndarray:
        """Get a contained frame/object or data from a contained frame/object.

        Args:
            index: The frame index to get the data from.
            frame: Determines if returned object is a Frame or an array, default is this object's setting.

        Returns:
        The requested frame or data.
        """
        if (frame is None and self.returns_frame) or frame:
            return self.frames[index]
        else:
            return self.frames[index].get_slices_array()

    def get_all_data(self, frame: bool | None = None) -> Any:
        """Get all the contained frames/objects as another frame or data from the contained frames/objects.

        Args:
            frame: Determines if returned object is a Frame or an array, default is this object's setting.

        Returns:
            The new frame or all the data as an array
        """
        if (frame is None and self.returns_frame) or frame:
            return self
        else:
            return self.get_slices_array()

# Assign Cyclic Definitions
ArrayFrame.default_return_frame_type = ArrayFrame


# -*- coding: utf-8 -*-
import scrapy
from tangspiderframe.items import TangspiderframeItem

class TextKoreaDongaLinkSpider(scrapy.Spider):
    name = 'text_korea_donga_link'
    allowed_domains = ['kids.donga.com']
    start_urls = [
        # 'https://kids.donga.com/?ptype=article&psub=world&gbn=01'
        'https://kids.donga.com/?ptype=article&psub=world&gbn=02'
    ]

    def parse(self, response):
        links = response.xpath("//ul[@class='article']//dt[@class='at_title']/a/@href").extract()
        for link in links:
            item = TangspiderframeItem()
            item['url'] = link
            yield item
        a_link = response.xpath("//a[@class='pg_page pg_next']/@href").extract()
        if a_link:
            next_link = "https://kids.donga.com" + a_link[0].strip(".")
            yield scrapy.Request(next_link, callback=self.parse, dont_filter=False)


from eodhd import APIClient
import os
import urllib, json
import pandas as pd
import numpy as np
import datetime
import yfinance as yf

#EOD API TO AWS
#API - DATA - PROCESS - RATIOS & SCREENER DATAFRAME

#EOD API
YOUR_API_KEY="647950fbb96692.96123493"
api = APIClient(YOUR_API_KEY)

#URL LIST

#"ExchangeList":f"https://eodhistoricaldata.com/api/exchanges-list/?api_token={YOUR_API_KEY}",
#"TickerInExchange":f"https://eodhistoricaldata.com/api/exchange-symbol-list/{EXCHANGE_CODE}?api_token={YOUR_API_KEY}",
#"CompanyOverview":f"https://eodhistoricaldata.com/api/fundamentals/{ticker}?api_token={YOUR_API_KEY}"}

def read_url(url=" "):
    response = urllib.request.urlopen(url)
    data = json.loads(response.read())
    df=pd.DataFrame.from_dict(data)
    return df

dfExchange=read_url(url=f"https://eodhistoricaldata.com/api/exchanges-list/?api_token={YOUR_API_KEY}")

exchangeList = dfExchange["Code"].tolist()
dfGlobalTicker=[]
for exchangeCode in exchangeList[:2]:
    dfTicker = read_url(url=f"https://eodhistoricaldata.com/api/exchange-symbol-list/{exchangeCode}?api_token={YOUR_API_KEY}&fmt=json")
    dfGlobalTicker.append(dfTicker)

dfGlobalTicker=pd.concat(dfGlobalTicker,axis=0, ignore_index=True)
tickerTypes=dfGlobalTicker["Type"].unique().tolist()
dfStockTickers = dfGlobalTicker[dfGlobalTicker["Type"]=="Common Stock"]

def EOD_Data(exchange=[],tickerList=[]):

    allDataKeys=['General', 'Highlights', 'Valuation', 'SharesStats', 'Technicals',
           'SplitsDividends', 'AnalystRatings', 'Holders', 'InsiderTransactions',
           'ESGScores', 'outstandingShares', 'Earnings', 'Financials']

    #Key Sub Classification
    #ESGScource - Outdated
    companyInfoDataKeys = ['General', 'Highlights', 'Valuation', 'SharesStats', 'Technicals', 'AnalystRatings']
    splitsDividendKey = ["SplitsDividend"]
    shareholdersKey = ["Holders"]
    iTransactionKey = ["InsiderTransactions"]
    osSharesKey = ['outstandingShares']
    earningsKey = ["Earnings"]
    financialsKey = ["Financials"]

    companyInfo = []
    officersInfo = []
    listingsInfo = []
    shareholderInfo = []
    shareOutstandInfo = []
    insiderTransacInfo = []
    earningHist = []
    earningTrend  = []
    earningAnnual = []
    statements = ['Balance_Sheet','Cash_Flow','Income_Statement']
    periods = ["quarterly","yearly"]
    annualFS=[]
    quarterFS=[]
    noData = {}
    noDataDf = []
    count = 1

    for ticker in tickerList:
        url=f"https://eodhistoricaldata.com/api/fundamentals/{ticker}.{exchange}?api_token={YOUR_API_KEY}"
        response = urllib.request.urlopen(url)
        data = json.loads(response.read())

        #Company Info
        dfCI = []
        for key in companyInfoDataKeys:
            dfCI.append(pd.DataFrame.from_dict([data[key]]))

        dfCompanyInfo=pd.concat(dfCI,axis=1)
        companyInfo.append(dfCompanyInfo)

        #Company Financials
        dfF= []
        for period in periods:
            for statement in statements:
                dfF.append(pd.DataFrame.from_dict(data['Financials'][statement][period]))

            dfFS=pd.concat(dfF,axis=1,ignore_index=True).transpose()
            dfFS["Ticker"]=ticker
            if period == "quarterly":
                  quarterFS.append(dfFS)
                  dfF = []
            else:
                  annualFS.append(dfFS)
                  dfF = []
    return companyInfo,annualFS,quarterFS

def convert_to_space_upper(text):
    import re
    if re.match(r'^[A-Za-z0-9_]+$', text):
        if re.match(r'^[a-z]+(_[a-z]+)*$', text):
            return text.replace('_', ' ').upper()
        else:
            return re.sub(r'([a-z0-9])([A-Z])', r'\1 \2', text).upper()
    else:
        return text.upper()

def concatList(clist=[]):
    df=pd.concat(clist,ignore_index=True)
    df.columns = df.columns.map(convert_to_space_upper)
    return df

companyInfo,annualFS,quarterFS = EOD_Data(exchange="US",tickerList=["AAPL","MSFT"])
dfF=concatList(annualFS)
dfQ=concatList(quarterFS)
dfC=concatList(companyInfo)

#dfOff = Management - RAW DATA - API
#dfSh = Shareholder- RAW DATA - API

#dfM = MetricRef DataFrame - MANUAL CSV CREATED
#dfScols = Screnner Columns for AgGrid

#VARIABLES

sector = 'SECTOR'
industry = "INDUSTRY"
coName = "NAME"
year = "YEAR"
marketCap='MARKET CAPITALIZATION'
updatedTicker="YF TICKER"
country = "COUNTRY"
Date ="DATE"
roic = 'ROIC'
revenue = 'TOTAL REVENUE'
rev_type = "TOTAL REVENUE"
fcf = 'FREE CASH FLOW'
gm = 'GROSS PROFIT MARGIN'
ebitda_m = 'EBITDA MARGIN'
npm = 'NET PROFIT MARGIN'
nprofit = "NET INCOME"
gp = "GROSS PROFIT"
ebitda = "EBITDA"
assets = "TOTAL ASSETS"
liab="TOTAL LIAB"
equity="TOTAL STOCKHOLDER EQUITY"
cfo = "TOTAL CASH FROM OPERATING ACTIVITIES"
cfi = "TOTAL CASHFLOWS FROM INVESTING ACTIVITIES"
cff = "TOTAL CASH FROM FINANCING ACTIVITIES"
d_e = 'DEBT TO EQUITY'
c_r = 'CURRENT RATIO'
pe = 'PRICE TO EARNINGS RATIO (TTM)'
pcf = 'PRICE TO FREE CASH FLOW (TTM)'
prev = "PRICE TO REVENUE RATIO (TTM)"
st1 = "IS"
st2 = "CF"
st3 = "Ratio"
st4 = "Ratio"
IS = "IS"
BS = "BS"
CF = "CF"
OT = "Ratio "
mScale="MARKET CAP SCALE"
indexUS=["S&P500","NASDAQ100","DOW30"]
indexIND = ["SECTORIAL INDEX","MARKET CAP INDEX"]

#Changing Quarterly Financials to USD
def QFUSD(dFF=[],dfM=[]):
    dFF.columns = dFF.columns.str.lstrip()
    dFF["DATE"]=pd.to_datetime(dFF["DATE"],errors="coerce")

    dFF["YEAR"]=dFF["DATE"].dt.year
    dFF["QUARTER"]= dFF["DATE"].dt.quarter
    dFF["MONTH"] = dFF["DATE"].dt.month

    sd="1980-1-1"
    ed = datetime.date.today()

    currencyList=dFF["CURRENCY SYMBOL"].unique().tolist()
    currencyList.remove("USD")
    currencyList.remove(np.nan)
    currencies=currencyList
    currencyTick = []
    for currency in currencies:
        currencyTick.append(currency+"USD=X")

    currencyData = yf.download(currencyTick,start=sd,end=ed)
    exRates=currencyData["Close"]
    exQ = exRates.resample("Q").mean().reset_index()
    exQ["YEAR"] = exQ["Date"].dt.year
    exQ["QUARTER"] = exQ["Date"].dt.quarter
    exM =  exRates.resample("M").mean().reset_index()
    exM["YEAR"] = exM["Date"].dt.year

    exM["MONTH"] = exM["Date"].dt.month

    dFFex=dFF[dFF["CURRENCY SYMBOL"].isin(currencies)][["TICKER","DATE","YEAR","MONTH","QUARTER","CURRENCY SYMBOL"]]

    zlist = []
    for currency in currencies:
        cCode=currency+"USD=X"

        x=dFFex[dFFex["CURRENCY SYMBOL"]==currency]

        yFp = exQ[[cCode,"YEAR","QUARTER"]]
        yFp.columns = ["ExRate_forperiod","YEARfp","QUARTERfp"]

        yAd = exM[[cCode,"YEAR","MONTH"]]
        yAd.columns=["ExRate_asonDate","YEARas","MONTHas"]

        z=pd.merge(x,yFp,left_on=["YEAR","QUARTER"],right_on=["YEARfp","QUARTERfp"])
        zqq=pd.merge(z,yAd,left_on=["YEAR","MONTH"],right_on=["YEARas","MONTHas"])

        zlist.append(zqq)

    erT=pd.concat(zlist)

    erT.drop(["YEAR","MONTH","QUARTER","CURRENCY SYMBOL","YEARfp","QUARTERfp","YEARas","MONTHas"],axis=1,inplace=True)

    dFFex=pd.merge(dFF,erT,left_on=["TICKER","DATE"],right_on=["TICKER","DATE"])

    for met in dfM[dfM["Statement"]=="BS"]["title"]:
                dFFex[met]=dFFex[met]*dFFex["ExRate_asonDate"]

    for met in dfM[dfM["Statement"].isin(["CF","IS"])]["title"]:
                dFFex[met]=dFFex[met]*dFFex["ExRate_forperiod"]

    usdDF=dFF[~dFF["CURRENCY SYMBOL"].isin(currencies)]

    usdF=pd.concat([dFFex,usdDF])

    usdF.loc[usdF["CURRENCY SYMBOL"]=="USD","ExRate_asonDate"]=1
    usdF.loc[usdF["CURRENCY SYMBOL"]=="USD","ExRate_forperiod"]=1

    usdF.loc[usdF["CURRENCY SYMBOL"].isin([np.nan]),"ExRate_asonDate"]=1
    usdF.loc[usdF["CURRENCY SYMBOL"].isin([np.nan]),"ExRate_forperiod"]=1

    usdF.dropna(subset=["ExRate_forperiod"],inplace=True)

    return usdF

#Changing Annual Financials to USD
def AFUSD(dFF=[],dfM=[]):

    dFF.columns = dFF.columns.str.lstrip()

    dFF["DATE"]=pd.to_datetime(dFF["DATE"],errors="coerce")
    dFF["YEAR"] = dFF["DATE"].dt.year
    dFF["MONTH"] =  dFF["DATE"].dt.month
    dFF.loc[dFF["MONTH"]<6,"YEAR"] = dFF.loc[dFF["MONTH"]<6,"YEAR"] - 1
    dFF["YEAR BS"] = dFF["DATE"].dt.year
    dFF["LAST YEAR"]=dFF["YEAR BS"]-1

    currencyList=dFF["CURRENCY SYMBOL"].unique().tolist()
    currencyList.remove("USD")
    currencyList.remove(np.nan)
    currencies=currencyList
    currencyTick = []
    for currency in currencies:
        currencyTick.append(currency+"USD=X")

    sd="1980-1-1"
    ed = datetime.date.today()

    currencyData = yf.download(currencyTick,start=sd,end=ed)
    exRates=currencyData["Close"]
    exY=exRates.resample("Y").mean().reset_index()
    exY["Year"] = exY["Date"].dt.year

    exM=exRates.resample("M").mean().reset_index()
    exM["Year"] = exM["Date"].dt.year
    exM["Month"]=exM["Date"].dt.month

    dFFex=dFF[dFF["CURRENCY SYMBOL"].isin(currencies)][["TICKER","DATE","YEAR","YEAR BS","LAST YEAR","MONTH","CURRENCY SYMBOL"]]

    zlist = []
    for currency in currencies:
        cCode=currency+"USD=X"

        x=dFFex[dFFex["CURRENCY SYMBOL"]==currency]

        y=exY[[cCode,"Year"]]
        yL=exY[[cCode,"Year"]]
        m = exM[[cCode,"Year","Month"]]
        m.columns = ["ExRate_asonDate","YEARexasd","MONTHexasd"]

        y.columns = ["ExRate_currentYear","YEARexCY"]
        yL.columns = ["ExRate_lastYear","YEARexLY"]

        z=pd.merge(x,m,left_on=["YEAR BS","MONTH"],right_on=["YEARexasd","MONTHexasd"])
        zq=pd.merge(z,y,left_on=["YEAR BS"],right_on=["YEARexCY"])

        zqq=pd.merge(zq,yL,left_on=["LAST YEAR"],right_on=["YEARexLY"])
        zlist.append(zqq)

    erT=pd.concat(zlist)

    erT["YearFactor"] = erT["MONTH"]/12

    erT["ExRate_forperiod"]=(erT["YearFactor"]*erT["ExRate_currentYear"])+((1-erT["YearFactor"])*erT["ExRate_lastYear"])

    erT.drop(["CURRENCY SYMBOL","YEAR","YEAR BS","LAST YEAR","MONTH","YEARexasd","MONTHexasd","YEARexCY","YEARexLY"],axis=1,inplace=True)

    dFFex=pd.merge(dFF,erT,left_on=["TICKER","DATE"],right_on=["TICKER","DATE"])

    for met in dfM[dfM["Statement"]=="BS"]["title"]:
                dFFex[met]=dFFex[met]*dFFex["ExRate_asonDate"]

    for met in dfM[dfM["Statement"].isin(["CF","IS"])]["title"]:
                dFFex[met]=dFFex[met]*dFFex["ExRate_forperiod"]

    usdDF=dFF[~dFF["CURRENCY SYMBOL"].isin(currencies)]

    usdF=pd.concat([dFFex,usdDF])

    usdF.loc[usdF["CURRENCY SYMBOL"]=="USD","ExRate_asonDate"]=1
    usdF.loc[usdF["CURRENCY SYMBOL"]=="USD","ExRate_forperiod"]=1

    usdF.loc[usdF["CURRENCY SYMBOL"].isin([np.nan]),"ExRate_asonDate"]=1
    usdF.loc[usdF["CURRENCY SYMBOL"].isin([np.nan]),"ExRate_forperiod"]=1

    usdF.dropna(subset=["ExRate_forperiod"],inplace=True)
    return usdF

def Ratios(dfF=[]):
    #dfF = annual or quarterly financials

    numerics = ['int', 'float']
    colnumeric = dfF.select_dtypes(include=numerics).columns
    dfF[colnumeric]=dfF[colnumeric].fillna(0)

    debt=dfF['LONG TERM DEBT'] + dfF['SHORT LONG TERM DEBT']+dfF['CAPITAL LEASE OBLIGATIONS']

    #PER SHARE RATIOS
    dfF["EPS"] = dfF["NET INCOME"]/dfF["SHARES OUTSTANDING"]
    dfF["TOTAL REVENUE PER SHARE"] = dfF["TOTAL REVENUE"]/dfF["SHARES OUTSTANDING"]
    dfF["FREE CASH FLOW PER SHARE"] = dfF['FREE CASH FLOW']/dfF["SHARES OUTSTANDING"]
    dfF["EBITDA PER SHARE"] = dfF['EBITDA']/dfF["SHARES OUTSTANDING"]

    #VALUATION RELATED
    dfF['NON-OPERATIONS VALUE'] = dfF['CASH'] + dfF['SHORT TERM INVESTMENTS'] + dfF['LONG TERM INVESTMENTS'] - (dfF['MINORITY INTEREST']*-1) - debt
    dfF['FAIR VALUE (30)'] = ((dfF['FREE CASH FLOW']*30)+(dfF['NON-OPERATIONS VALUE']))/dfF['SHARES OUTSTANDING']
    dfF['FAIR VALUE (15)'] =((dfF['FREE CASH FLOW']*15)+(dfF['NON-OPERATIONS VALUE']))/dfF['SHARES OUTSTANDING']
    dfF['FAIR VALUE (45)'] =((dfF['FREE CASH FLOW']*45)+(dfF['NON-OPERATIONS VALUE']))/dfF['SHARES OUTSTANDING']
    dfF["Effective Interest Rate"] = dfF["INTEREST EXPENSE"]/(debt)
    dfF["Effective Tax Rate"] =dfF["INCOME TAX EXPENSE"]/dfF["INCOME BEFORE TAX"]
    dfF["DEBT %"] = debt/(debt+dfF["TOTAL STOCKHOLDER EQUITY"])
    dfF["Equity %"] = 1 - dfF["DEBT %"]

    #PROFIT MARGINS
    dfF['Net Profit Margin'] = dfF['NET INCOME']/dfF['TOTAL REVENUE']
    dfF['Operating Profit Margin'] = dfF['OPERATING INCOME']/dfF['TOTAL REVENUE']
    dfF['EBITDA Margin'] = dfF['EBITDA']/dfF['TOTAL REVENUE']
    dfF['Gross Profit Margin'] = dfF['GROSS PROFIT']/dfF['TOTAL REVENUE']
    dfF['DATE']=pd.to_datetime(dfF['DATE']).dt.date

    #ACTIVITY/TURNOVER RATIOS:
    dfF["INVENTORY TURNOVER"]=dfF["COST OF REVENUE"]/dfF["INVENTORY"]
    dfF["Days of inventory on hand (DOH)"]=365/dfF["INVENTORY TURNOVER"]
    dfF["RECEIVABLES TURNOVER"] = dfF["TOTAL REVENUE"]/dfF["NET RECEIVABLES"]
    dfF["Days of sales outstanding (DSO)"]=365/dfF["RECEIVABLES TURNOVER"]
    dfF["PAYABLES TURNOVER"] = dfF["COST OF REVENUE"]/dfF["ACCOUNTS PAYABLE"]
    dfF["Number of days of payables"] = 365/dfF["PAYABLES TURNOVER"]
    dfF["WORKING CAPITAL TURNOVER"] = dfF["TOTAL REVENUE"]/dfF["NET WORKING CAPITAL"]
    dfF["FIXED ASSET TURNOVER"] = dfF["TOTAL REVENUE"]/dfF["NET TANGIBLE ASSETS"]
    dfF["TOTAL ASSET TURNOVER"] = dfF["TOTAL REVENUE"]/dfF["TOTAL ASSETS"]

    #LIQUIDITY RATIOS
    dfF['CURRENT RATIO'] = dfF['TOTAL CURRENT ASSETS']/dfF['TOTAL CURRENT LIABILITIES']
    dfF['QUICK RATIO'] = (dfF['CASH'] + dfF['SHORT TERM INVESTMENTS'] + dfF["NET RECEIVABLES"])/dfF['TOTAL CURRENT LIABILITIES']
    dfF["CASH RATIO"]=  (dfF['CASH'] + dfF['SHORT TERM INVESTMENTS'])/dfF['TOTAL CURRENT LIABILITIES']
    dfF["Cash conversion cycle"] = dfF["Days of inventory on hand (DOH)"]+dfF["Days of sales outstanding (DSO)"]-dfF["Number of days of payables"]

    #SOLVENCY & COVERAGE RATIOS
    dfF['Debt to Equity'] = (debt)/dfF['TOTAL STOCKHOLDER EQUITY']
    dfF["DEBT TO ASSETS"] = dfF["TOTAL ASSETS"]/debt
    dfF["FINANCIAL LEVERAGE"]=dfF["TOTAL ASSETS"]/dfF['TOTAL STOCKHOLDER EQUITY']

    dfF["INTEREST COVERAGE"] = dfF["EBIT"]/dfF["INTEREST EXPENSE"]

    #RETURN ON CAPITAL RATIOS
    dfF['ROIC'] = dfF['EBIT']/(debt + dfF['TOTAL STOCKHOLDER EQUITY'])
    dfF["ROA"] = dfF["NET INCOME"]/dfF["TOTAL ASSETS"]
    dfF["Operating ROA"] = dfF["EBIT"]/dfF["TOTAL ASSETS"]
    dfF["ROE"] = dfF["NET INCOME"]/dfF['TOTAL STOCKHOLDER EQUITY']

    numerics = ['int', 'float']
    colnumeric = dfF.select_dtypes(include=numerics).columns
    dfF[colnumeric]=dfF[colnumeric].fillna(0)
    dfF.replace([np.inf,-np.inf],0,inplace=True)
    dfF[colnumeric] = round(dfF[colnumeric],4)
    dfF.columns = dfF.columns.str.upper()
    dfF.columns = dfF.columns.str.lstrip()

    return dfF

#SCREENER DATAFRAME - GROWTH AND RATINGS
#dfF = "Annual Financials"
def multidfC(dfF=[],dfC=[],dfM=[]):

    #GROWTH AND AVERAGE
    metdf1=dfF[dfF[year]==dfF[year].max()]
    metdf2=dfF[dfF[year]==(dfF[year].max()-1)]

    tickcy=metdf1["TICKER"].unique().tolist()
    tickly=metdf2["TICKER"].unique().tolist()

    ticknt = []
    for tick in tickly:
        if tick not in tickcy:
            ticknt.append(tick)
    metdf2=metdf2[metdf2["TICKER"].isin(ticknt)]
    metdf = pd.concat([metdf1,metdf2],axis=0)
    diff_cols = metdf.columns.difference(dfC.columns)

    #Filter out the columns that are different. You could pass in the df2[diff_cols] directly into the merge as well.
    selcols = diff_cols.tolist()+ ["TICKER"]
    selcolmetdf = metdf[selcols]
    metdfC = pd.merge(dfC,selcolmetdf,left_on="TICKER",right_on="TICKER",how="left")

    growth_cols = dfM[dfM['MultidfC']=="growth"]["title"].unique().tolist()

    avg_cols = dfM[dfM['MultidfC']=="average"]["title"].unique().tolist()
    colListg = growth_cols+[coName,year]
    colLista = avg_cols+[coName,year]

    year_list=dfF[year].unique().tolist()
    year_list.sort(reverse=True)

    dfFg = dfF[colListg]
    dfF10g=dfFg[dfFg[year].isin(year_list[:11])]
    dfF5g=dfFg[dfFg[year].isin(year_list[:6])]
    dfF3g=dfFg[dfFg[year].isin(year_list[:4])]
    dfF1g=dfFg[dfFg[year].isin(year_list[:2])]
    dfF1ge=dfFg[dfFg[year].isin(year_list[1:3])]
    grL = [dfF10g,dfF5g,dfF3g,dfF1g,dfF1ge]
    dfFa = dfF[colLista]
    dfF10a=dfFa[dfFa[year].isin(year_list[:10])]
    dfF5a=dfFa[dfFa[year].isin(year_list[:5])]
    dfF3a=dfFa[dfFa[year].isin(year_list[:4])]
    dfF1a=dfFa[dfFa[year].isin(year_list[:2])]
    dfF1ae=dfFa[dfFa[year].isin(year_list[1:3])]
    avL = [dfF10a,dfF5a,dfF3a,dfF1a,dfF1ae]

    colnameg = [" 10y-growth"," 5y-growth"," 3y-growth"," 1cy-growth"," 1ly-growth"]
    colnameav = [" 10y-average"," 5y-average"," 3y-average"," 1cy-average"," 1ly-average"]
    growthlist = []
    count = 0
    for yg in grL:
        yg=yg.pivot_table(index=coName,columns=year,values=growth_cols).groupby(level=0,axis=1).pct_change(axis=1)
        ayg=yg.groupby(level=0,axis=1).mean()
        col_list = ayg.columns.tolist()
        col_list =  [x + colnameg[count] for x in col_list]
        ayg.columns = col_list
        growthlist.append(ayg)
        count += 1

    averagelist = []
    countav=0
    for ya in avL:
        ya=ya.pivot_table(index=coName,columns=year,values=avg_cols)
        ayg=ya.groupby(level=0,axis=1).mean()
        col_list = ayg.columns.tolist()
        col_list =  [x + colnameav[countav] for x in col_list]
        ayg.columns = col_list
        averagelist.append(ayg)
        countav += 1

    multilist = growthlist + averagelist
    multiyeardfC = pd.concat(multilist,axis=1,join="inner").reset_index()
    multidfC = metdfC.merge(multiyeardfC,left_on=coName,right_on=coName,how="left")

    colcg=[col for col in multidfC.columns if '1cy-growth' in col]
    colclg=[col for col in multidfC.columns if '1ly-growth' in col]
    for acol in growth_cols:
        try:
            multidfC.loc[~multidfC[f'{acol} 1cy-growth'].isin([np.nan,0]),f'{acol} 1y-growth'] = multidfC.loc[~multidfC[f'{acol} 1cy-growth'].isin([np.nan,0]),f'{acol} 1cy-growth']
            multidfC.loc[multidfC[f'{acol} 1cy-growth'].isin([np.nan,0]),f'{acol} 1y-growth'] = multidfC.loc[multidfC[f'{acol} 1cy-growth'].isin([np.nan,0]),f'{acol} 1ly-growth']
            multidfC.drop([f'{acol} 1cy-growth',f'{acol} 1ly-growth'],axis=1,inplace=True)
        except:
            pass

    colca=[col for col in multidfC.columns if '1cy-average' in col]
    colcla=[col for col in multidfC.columns if '1ly-average' in col]
    for acol in avg_cols:
        multidfC.loc[~multidfC[f'{acol} 1cy-average'].isin([np.nan]),f'{acol} 1y-average']=multidfC.loc[~multidfC[f'{acol} 1cy-average'].isin([np.nan]),f'{acol} 1cy-average']
        multidfC.loc[multidfC[f'{acol} 1cy-average'].isin([np.nan]),f'{acol} 1y-average']=multidfC.loc[multidfC[f'{acol} 1cy-average'].isin([np.nan]),f'{acol} 1ly-average']
        multidfC.drop([f'{acol} 1cy-average',f'{acol} 1ly-average'],axis=1,inplace=True)

    #RATING
    met_list = [rev_type,fcf,roic,nprofit,gm,ebitda_m,npm,d_e,c_r]

    colnameg = [" 10y-growth"," 5y-growth"," 3y-growth"," 1y-growth"]
    colnameav = [" 10y-average"," 5y-average"," 3y-average"," 1y-average"]
    ratingdF = multidfC.copy()

    for metrics in met_list:
        for co in colnameg+colnameav:
            try:
                if metrics in [rev_type,fcf,roic,npm,nprofit]:
                        bins = [-100000,-0.1,0,0.07,0.15,0.3,100000]
                        label = [-2,-1,1,3,6,9]

                elif metrics == c_r:
                        bins = [0,0.05,0.2,0.75,1.5,2.5,100000]
                        label = [-2,-1,0,1,2,3]

                elif metrics ==d_e:
                        bins = [0,0.05,1,1.5,3,5,100000]
                        label = [-2,-1,0,1,2,3]

                else:
                    bins = [-100000,-0.1,0,0.1,0.25,0.5,100000]
                    label = [-2,-1,0,1,2,3]

                ratingdF[f'{metrics}{co}-scale'] = pd.cut(ratingdF[f'{metrics}{co}'],bins=bins,labels=label).astype("float")

            except:
                pass
    yL = ["10y","5y","3y","1y"]

    for y in yL :
        colsa=[col for col in ratingdF.columns if f'{y}-average-scale' in col]
        colsg = [col for col in ratingdF.columns if f'{y}-growth-scale' in col]
        cols = colsa + colsg
        ratingdF[cols]=ratingdF[cols].fillna(0)
        ratingdF[f'{y}-Overall Rating']=(ratingdF[cols].sum(axis=1)/57)*10
        ratingdF[f'{y}-Avg Rating']=(ratingdF[colsa].sum(axis=1)/30)*10
        ratingdF[f'{y}-Growth Rating']=(ratingdF[colsg].sum(axis=1)/27)*10
    ratingdF['Fundamental Growth Rating']=round((ratingdF['10y-Growth Rating'] + ratingdF['5y-Growth Rating']*2 + ratingdF['3y-Growth Rating']*3 + ratingdF['1y-Growth Rating']*4)/10,2)
    ratingdF['Fundamental Avg Rating']=round((ratingdF['10y-Avg Rating'] + ratingdF['5y-Avg Rating']*2 + ratingdF['3y-Avg Rating']*3 + ratingdF['1y-Avg Rating']*4)/10,2)

    ratingdF["Revenue Size"] = pd.cut(ratingdF[rev_type],bins=[-100000000000000,500000000,1000000000,20000000000,100000000000,100000000000000],labels=[0,0.25,0.5,0.75,1]).astype("float")
    ratingdF["Net Profit Size"] = pd.cut(ratingdF[rev_type],bins=[-100000000000000,50000000,100000000,2000000000,10000000000,100000000000000],labels=[0,0.25,0.5,0.75,1]).astype("float")
    ratingdF["EBITDA Size"] = pd.cut(ratingdF[rev_type],bins=[-100000000000000,200000000,400000000,4000000000,20000000000,100000000000000],labels=[0,0.25,0.5,0.75,1]).astype("float")
    ratingdF["ASSET Size"] =  pd.cut(ratingdF[rev_type],bins=[-100000000000000,1500000000,3000000000,60000000000,300000000000,100000000000000],labels=[0,0.25,0.5,0.75,1]).astype("float")
    ratingdF["Fundamental Size Rating"] = round(((ratingdF["Revenue Size"]+ratingdF["Net Profit Size"]+ratingdF["EBITDA Size"]+ratingdF["ASSET Size"])/4)*10,2)
    ratingdF["Fundamental Size Rating"].fillna(0,inplace=True)
    ratingdF['Fundamental Overall Rating']=round((ratingdF['10y-Overall Rating'] + ratingdF['5y-Overall Rating']*2 + ratingdF['3y-Overall Rating']*3 + ratingdF['1y-Overall Rating']*4)/10,2)
    ratingdF['Fundamental Overall Rating']=round((9*ratingdF['Fundamental Overall Rating'] + ratingdF["Fundamental Size Rating"])/10,2)

    rdf=ratingdF[['TICKER','Fundamental Growth Rating','Fundamental Avg Rating',"Fundamental Size Rating",'Fundamental Overall Rating']]

    multidfC=pd.merge(multidfC,rdf,left_on="TICKER",right_on="TICKER",how="left")
    multidfC.columns = multidfC.columns.str.upper()
    multidfC.columns = multidfC.columns.str.lstrip()

    return multidfC


import gspread
from google.oauth2.service_account import Credentials

import pandas as pd
import pdb

class HandleSpreadsheet:
    def __init__(self):
        self.secret_credentials_json_oath = 'src/keys/moneyproject-362103-c609fbcd77b0.json'
        self.spread_url = 'https://docs.google.com/spreadsheets/d/1hwEVP5fgU5ohwyRX_oatNmWczx_AJBxmF5DEn2qk4Sw/edit#gid=0'

    def _connect_spreadsheet(self):
        scopes = [
        'https://www.googleapis.com/auth/spreadsheets',
        'https://www.googleapis.com/auth/drive'
        ]

        credentials = Credentials.from_service_account_file(
            self.secret_credentials_json_oath,
            scopes=scopes
        )
        gc = gspread.authorize(credentials)
        workbook = gc.open_by_url(self.spread_url)

        return workbook

### 支出 ##################################################################
class BuyControlSheet(HandleSpreadsheet):
    """
    支出管理シートを扱う。支出のカテゴリーデータもここから。
    """
    def __init__(self):
        super().__init__()
        self.sheet_name = "支出管理"
        self.workbook = self._connect_spreadsheet()
        self.sheet = self.workbook.worksheet(self.sheet_name)

    def get_buy_ctl_data(self, col_range="ctg"):
        """
        col_range="ctg" -> カテゴリだけを取得
        col_range="ctl" -> 予算までを取得

        Returns
        -------
        _type_
            _description_
        """
        if col_range == "ctg":
            col_range_index = [0, 1, 2]
        elif col_range == "ctl":
            col_range_index = [0, 1, 2, 3, 4]
        data = pd.DataFrame(self.sheet.get_all_values())[col_range_index]
        columns = data.iloc[0, :].tolist()
        data = data.drop(0, axis=0)
        data.columns = columns

        return data

    def get_ctg_dict(self):
        data = self.get_buy_ctl_data()
        columns = data.columns.tolist()
        # data -> dict
        ctg_dict = {} # key : 0 value : {"固定変動" : 固定, "カテゴリー1" : 住居費, "カテゴリー2" : 家賃}
        index = 0
        for data_i in data.index:
            ctg_dict[index] = {col : data[col][data_i] for col in columns}
            index += 1

        return ctg_dict

class BuyDataSheet(HandleSpreadsheet):
    """
    支出データシートを扱う
    """
    def __init__(self):
        super().__init__()
        self.sheet_name = "支出データ"
        self.workbook = self._connect_spreadsheet()
        self.sheet = self.workbook.worksheet(self.sheet_name)

    def input_buy(self, date, ctg, amount):
        # make inserted data
        insert_data = [date]+ ctg.split('-') +[amount]
        # insert data
        index = len(self.sheet.get_all_values())
        self.sheet.insert_row(insert_data, index + 1)

    def get_buy_df(self):
        data = self.sheet.get_all_values()
        columns = data.pop(0)

        return pd.DataFrame(data, columns=columns)

### 収入 ##################################################################
class IncomeControlSheet(HandleSpreadsheet):
    """
    収入管理シートを扱う。支出のカテゴリーデータもここから。
    """
    def __init__(self):
        super().__init__()
        self.sheet_name = "収入カテゴリー"
        self.workbook = self._connect_spreadsheet()
        self.sheet = self.workbook.worksheet(self.sheet_name)

    def get_income_ctl_data(self, col_range="ctg"):
        """
        if col_range == "ctg":
            col_range_index = [0, 1, 2]
        elif col_range == "ctl":
            col_range_index = [0, 1, 2, 3, 4]
        """
        data = pd.DataFrame(self.sheet.get_all_values())
        # 列名を一行目の値に変更
        data.columns = data.iloc[0]
        data = data.drop(data.index[0])

        return data

    def get_income_ctg(self):
        data = self.get_income_ctl_data()
        ctg_list = data["収入カテゴリー"].tolist()[0:-1]

        return ctg_list

    def get_income_pay_day(self):
        data = self.get_income_ctl_data()
        ctg_list = data["収入カテゴリー"].tolist()[0:-1]
        payday_list = data["給料日"].tolist()[0:-1]
        payday_dict = {ctg : payday for ctg, payday in zip(ctg_list, payday_list)}

        return payday_dict

    def get_income_bonus_month(self):
        data = self.get_income_ctl_data()
        ctg_list = data["収入カテゴリー"].tolist()[0:-1]
        bonus_month_list = data["ボーナス月"].tolist()[0:-1]
        bonus_dict = {ctg : bonus_month for ctg, bonus_month in zip(ctg_list, bonus_month_list)}

        return bonus_dict

class IncomeDataSheet(HandleSpreadsheet):
    """
    収入データシートを扱う
    """
    def __init__(self):
        super().__init__()
        self.sheet_name = "収入データ"
        self.workbook = self._connect_spreadsheet()
        self.sheet = self.workbook.worksheet(self.sheet_name)

    def input_income(self, date, ctg, income_type, income, residual_income):
        # make inserted data
        insert_data = [date, ctg, income_type, income, residual_income]
        # insert data
        index = len(self.sheet.get_all_values())
        self.sheet.insert_row(insert_data, index + 1)

    def get_income_df(self):
        data = self.sheet.get_all_values()
        columns = data.pop(0)

        return pd.DataFrame(data, columns=columns)

### 貯金 ##################################################################
class SavingControlSheet(HandleSpreadsheet):
    """
    貯金管理シートを扱う。支出のカテゴリーデータもここから。
    """
    def __init__(self):
        super().__init__()
        self.sheet_name = "貯金カテゴリー"
        self.workbook = self._connect_spreadsheet()
        self.sheet = self.workbook.worksheet(self.sheet_name)

    def get_saving_ctl_data(self, col_range="ctg"):
        data = pd.DataFrame(self.sheet.get_all_values())
        # 列名を一行目の値に変更
        data.columns = data.iloc[0]
        data = data.drop(data.index[0])

        return data

    def get_saving_ctg(self):
        data = self.get_saving_ctl_data()
        ctg_list = (data["貯金項目"] + "-" + data["貯金方法"]).tolist()

        return ctg_list

class SavingDataSheet(HandleSpreadsheet):
    """
    貯金データシートを扱う
    """
    def __init__(self):
        super().__init__()
        self.sheet_name = "貯金データ"
        self.workbook = self._connect_spreadsheet()
        self.sheet = self.workbook.worksheet(self.sheet_name)

    def input_saving(self, date, ctg, how_to_save, amount):
        # make inserted data
        insert_data = [date, ctg, how_to_save, amount]
        # insert data
        index = len(self.sheet.get_all_values())
        self.sheet.insert_row(insert_data, index + 1)

    def get_saving_df(self):
        data = self.sheet.get_all_values()
        columns = data.pop(0)

        return pd.DataFrame(data, columns=columns)


'''
ROOM TAGS RELOCATE TO ROOM LOCATION
'''

__author__ = 'min.naung/mgjean @https://twentytwo.space/contact'
__twitter__ = '@_mgjean'
__version__ ='1.0.0'

# import common language runtime
import clr

# clr.AddReference loads and imports .net assembly(dll) as python module
# load RevitAPI.dll and RevitServices.dll
clr.AddReference("RevitAPI")
clr.AddReference("RevitServices")

# import Revit DB classes
from Autodesk.Revit.DB import *

# import document manager
from RevitServices.Persistence import DocumentManager
from RevitServices.Transactions import TransactionManager

# instantiate current document
doc = DocumentManager.Instance.CurrentDBDocument

# collect room tags from active view
collector = FilteredElementCollector(doc, doc.ActiveView.Id)
tags = collector.OfClass(SpatialElementTag).ToElements()

# start transaction
TransactionManager.Instance.EnsureInTransaction(doc)

# loop tags
for i in tags:
	# get room location
	room_loc = i.Room.Location.Point
	# location to move (room location - current tag location)
	new_loc = room_loc - i.Location.Point
	# move to new location
	i.Location.Move(new_loc)

# end transaction
TransactionManager.Instance.TransactionTaskDone()
# output
OUT = tags


from PyQt5.QtCore import QObject, pyqtSignal
import fileutils
import pathlib
from job import Job

class Project(QObject):
    job_update = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.jobs = list()

    def load_job(self, filepath):
        try:
            self.jobs += fileutils.load(filepath)
            self.job_update.emit()
        except Exception as e:
            filename = pathlib.Path(filepath).name
            print('Unable to load ' + filename + ', ' + str(e))

    def remove_jobs(self, jobs):
        self.jobs = [j for j in self.jobs if j not in jobs]
        self.job_update.emit()

    def generate_tasks(self, post_processor, dry_run):
        tasks = []
        for job in self.jobs:
            for i in job.cut_state_indices(Job.TODO):
                tasks.append(post_processor.generate(job, i, dry_run))
        if tasks:
            tasks.insert(0, post_processor.init_task())
        return tasks


def reformatDate(date: str) -> str:

    dates = date.split(' ')
    print(dates)
    months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
    m = months.index(dates[1]) + 1
    print(m)
    if len(str(m)) != 2:
        month = '0' + str(m)
    else:
        month = str(m)
    num = dates[0]
    d = num[0:2]
    # finalD = ''
    if not d.isdigit():
        finalD = '0' + d[0]
    else:
        finalD = d
    print(finalD)
    new = dates[2] + '-' + month + '-' + finalD
    print(new)

date = "6th Jun 1933"
# Output: "2052-10-20"
reformatDate(date)


# Given an array which consists of non-negative integers and an integer m,
# you can split the array into m non-empty continuous subarrays.
# Write an algorithm to minimize the largest sum among these m subarrays.
# Note:
# If n is the length of array, assume the following constraints are satisfied:
# 1 ≤ n ≤ 1000
# 1 ≤ m ≤ min(50, n)

# Examples:
# Input:
# nums = [7,2,5,10,8]
# m = 2
# Output:
# 18

# Explanation:
# There are four ways to split nums into two subarrays.
# The best way is to split it into [7,2,5] and [10,8],
# where the largest sum among the two subarrays is only 18.

class Solution(object):
    def splitArray(self, nums, m):
        """
        :type nums: List[int]
        :type m: int
        :rtype: int
        """
        # M1. 二分贪心 O(n)
        def valid(mid):
            cnt = 0
            current = 0
            for num in nums:
                current += num
                if current > mid:
                    cnt += 1
                    if cnt >= m:
                        return False
                    current = num
            return True

        l, r = max(nums), sum(nums)
        while l < r:
            mid = l + r >> 1
            if valid(mid):
                r = mid
            else:
                l = mid + 1
        return l

        # M2. DP O(m*n^2)
        n = len(nums)
        # dp[i][j] 表示将数组中前i个数字分成j组所能得到的最小的各个子数组中最大值
        dp = [[float('inf')] * (m+1) for _ in range(n+1)]
        dp[0][0] = 0

        pre_sum = [0] * (n+1)
        for i in range(n):
            pre_sum[i+1] = pre_sum[i] + nums[i]

        for i in range(1, n+1):
            for j in range(1, m+1):
                for k in range(i):
                    dp[i][j] = min(dp[i][j], max(dp[k][j-1], pre_sum[i] - pre_sum[k]))
        return dp[n][m]

LeetCode/Python3/DynamicProgramming/410. Split Array Largest Sum.py


import RPi.GPIO as GPIO
import time, math

COILA = 21
COILB = 22
COILC = 23

speed = 10
pwmSignal = [0,0,0,1,1,1]
phaseA = 0
phaseB = 2
phaseC = 4

GPIO.setmode(GPIO.BCM)
GPIO.setup(COILA,GPIO.OUT)
GPIO.setup(COILB,GPIO.OUT)
GPIO.setup(COILC,GPIO.OUT)

while True:
    if speed < 0:
        step = -1

    else:
        step = 1

    phaseA = phaseA + step
    if phaseA > 5:
        phaseA = 0
    if phaseA < 0:
        phaseA = 5

    phaseB = phaseB + step
    if phaseB > 5:
        phaseB = 0
    if phaseB < 0:
        phaseB = 5

    phaseC = phaseC + step
    if phaseC > 5:
        phaseC = 0
    if phaseC < 0:
        phaseC = 5

    GPIO.output(COILA,pwmSignal[phaseA])
    GPIO.output(COILB,pwmSignal[phaseB])
    GPIO.output(COILC,pwmSignal[phaseC])
    #print pwmSignal[phaseA], pwmSignal[phaseB], pwmSignal[phaseC]
    time.sleep(1/(math.sqrt(speed**2)))


num = input()[1:-1].split(',')
num = list(map(int,num))
num.sort()
if len(num)<2:
    print(0)
else:
    Max = 0
    for i in range(len(num)-1):
        j = i + 1
        Max = max(Max,abs(num[i]-num[j]))
    print(Max)


# This program prints out a random fruit.
# Author: Darren Miller

import  random
fruits= ('Apple', 'Orange', 'Banana', 'Pear')
index= random.randint (0,len(fruits)-1)

fruit= fruits[index]

print ("A random fruit: {}".format(fruit))