import pygame
from _draw              import *
from _utils import *

class gui():
    def __init__(self,
        white,
        screen,
        width,
        height,
        smallNokiaFont,
        hugeNokiaFont,
        font,
        bigFont,
        hugeFont,
        smallFont,
        nanoFont,
        themeColour,
        exitButton,
        nextButton,
        dialogue,
        sDialogue,
        smsDialogue,
        music,
        borderSlide,
        notificationDialogue,
        user_input,
        statusButton      ,
        inventoryButton  ,
        noteButton   ,
        nokiaFont    ,
        nanoNokiaFont    ,
        smsFont  ,
        musicFont,
        jumboFont  ,
        gameTime ,
        smsScrollDialogue,
        squareFont,
        squareFontH,
        debugSwitch = True,
        clicked=False,
        ):

        self.white                  = white
        self.screen                 = screen
        self.width                  = width
        self.height                 = height
        self.smallNokiaFont         = smallNokiaFont
        self.hugeNokiaFont          = hugeNokiaFont
        self.font                   = font
        self.bigFont                = bigFont
        self.hugeFont               = hugeFont
        self.smallFont              = smallFont
        self.nanoFont               = nanoFont
        self.themeColour            = themeColour
        self.exitButton             = exitButton
        self.nextButton             = nextButton
        self.dialogue               = dialogue
        self.sDialogue              = sDialogue
        self.smsDialogue            = smsDialogue
        self.music                  = music
        self.borderSlide            = borderSlide
        self.notificationDialogue   = notificationDialogue
        self.user_input             = user_input
        self.statusButton           = statusButton
        self.inventoryButton        = inventoryButton
        self.noteButton             = noteButton
        self.nokiaFont              = nokiaFont
        self.nanoNokiaFont          = nanoNokiaFont
        self.smsFont                = smsFont
        self.musicFont              = musicFont
        self.jumboFont              = jumboFont
        self.gameTime               = gameTime
        self.smsScrollDialogue      = smsScrollDialogue
        self.squareFont             = squareFont
        self.squareFontH            = squareFontH

        self.debugSwitch            = debugSwitch
        self.clicked                = clicked

        self.greenA        = (36,65,45)
        self.greenB        = (82,128,58)
        self.greenC        = (173,195,63)
        self.greenD        = (215,233,149)
        self.darkGreen     = (5,37,23)
        self.buttonGreen   = (47,75,45)
        self.offwhite      = (245,245,245)
        self.screenDefault = (201,221,126)
        self.screenColour  = (201,221,126)

        self.greenText     = (29,153,29)
        self.greenBorder   = (127,187,73)
        self.darkGrey      = (44,52,56)
        self.lightBlack    = (40,41,35)
        self.lightGrey     = (72,77,79)

        # ---------------Images

        self.signal             = pygame.image.load('pics/phoneLogos/signal.png')
        self.bottomNavMock      = pygame.image.load('pics/assets/mocks/navBottom.png')
        self.bottomNav          = pygame.image.load('pics/assets/nav/navBottom.png')
        self.nextDayBtn         = [pygame.image.load('pics/assets/nav/nextDay1.png'),pygame.image.load('pics/assets/nav/nextDay2.png')]
        self.tileBackground     = pygame.image.load('pics/assets/backgrounds/tile.png')
        self.gradientBackground = pygame.image.load('pics/assets/backgrounds/gradient.png')
        self.cubeBackground     = pygame.image.load('pics/assets/backgrounds/cube.png')

        # -------------widget images

        self.widgetNode         = [pygame.image.load('pics/assets/widgetNode/widgetNode1.png'),pygame.image.load('pics/assets/widgetNode/widgetNode2.png'),pygame.image.load('pics/assets/widgetNode/widgetNode3.png')]
        self.smallActiveWidget  = pygame.image.load('pics/assets/widgetNode/smallActiveWidget.png')

        self.medActiveWidget    = pygame.image.load('pics/assets/widgetNode/medActiveWidget.png')
        self.medActiveWidgetLab = pygame.image.load('pics/assets/widgetNode/widgetMedLabel.png')

        self.bigActiveWidget    = pygame.image.load('pics/assets/widgetNode/bigActiveWidget.png')

        # ----- Mech imgs
        self.mechBoxMed         = impFilesL('mechBoxMed1.png',tDir = 'pics/assets/mechBox/')
        self.mechBoxBig         = impFilesL('mechBoxBig1.png',tDir = 'pics/assets/mechBox/')
        self.mechBoxGreen       = impFilesL('mechBoxGreen1.png',tDir = 'pics/assets/mechBox/')
        self.mechBoxMedLight    = [pygame.image.load('pics/assets/mechBox/mechBoxMedLight1.png'),pygame.image.load('pics/assets/mechBox/mechBoxMedLight2.png'),pygame.image.load('pics/assets/mechBox/mechBoxMedLight3.png'),pygame.image.load('pics/assets/mechBox/mechBoxMedLight4.png')]
        self.mechBtnMed         = [pygame.image.load('pics/assets/buttons/mechBtnMed1.png'),pygame.image.load('pics/assets/buttons/mechBtnMed2.png')]
        self.mechPlainBtnMed    = [pygame.image.load('pics/assets/buttons/medMechBtn1.png'),pygame.image.load('pics/assets/buttons/medMechBtn2.png')]

        self.extendableBox      = [pygame.image.load('pics/assets/textBox/extendableDarkGreen1.png'),pygame.image.load('pics/assets/textBox/extendableDarkGreen2.png')]

        self.notitfyBtnSmall    = [pygame.image.load('pics/assets/buttons/buttonSmall1.png'),pygame.image.load('pics/assets/buttons/buttonSmall2.png')]
        self.notitfyBtnMed      = [pygame.image.load('pics/assets/buttons/buttonMed1.png'),pygame.image.load('pics/assets/buttons/buttonMed2.png')]

        self.signal          = pygame.image.load('pics/phoneLogos/signal.png')
        self.minis           = [pygame.image.load('pics/assets/minis/minibuttons1.png'),pygame.image.load('pics/assets/minis/minibuttons2.png'),pygame.image.load('pics/assets/minis/minibuttons3.png'),pygame.image.load('pics/assets/minis/minibuttons4.png'),pygame.image.load('pics/assets/minis/minibuttons5.png'),pygame.image.load('pics/assets/minis/minibuttons6.png'),pygame.image.load('pics/assets/minis/minibuttons7.png'),pygame.image.load('pics/assets/minis/minibuttons8.png'),pygame.image.load('pics/assets/minis/minibuttons9.png'),pygame.image.load('pics/assets/minis/minibuttons10.png')]

        # ------mouse

        self.mx     = 0
        self.my     = 0

        #buttons
        self.sell              = impFilesL('sell1.png',tDir = 'pics/assets/buttons/')
        self.bank              = impFilesL('bank1.png',tDir = 'pics/assets/buttons/')
        self.auto              = impFilesL('auto1.png',tDir = 'pics/assets/buttons/')
        self.selectMe          = impFilesL('selectme1.png',tDir = 'pics/assets/buttons/')
        self.increment         = impFilesL('increment1.png',tDir = 'pics/assets/buttons/')
        self.decrement         = impFilesL('decrement1.png',tDir = 'pics/assets/buttons/')

        self.menuBG = None
        self.hideExitButton    = False

    def border(self,colour=(128,0,0)):
        self.bx,self.by = 0.1*self.width,0.1*self.height
        self.bw,self.bh = 0.8*self.width,0.8*self.height
        rect = pygame.draw.rect(self.screen, colour, [self.bx, self.by,self.bw , self.bh],4)

    def mouseCollides(self,mousePos,x,y,w,h):
        if mousePos[0] > x and mousePos[0] < x + w:
            if mousePos[1] > y and mousePos[1] < y + h:
                return(True)
        return(False)

    def incrementableWidget(self,x,y,text,value,inc=1,cap=100,userInput=None,incrementKey=None,insta=False,instaMessage='Auto On'):
        """+ button and text to increment and return value
        """
        textx, texty = x+60,y+10

        #---------exit if auto on
        if(insta):
            drawSelectableImage(self.increment[0],self.increment[1],(x,y),self,trim=False)
            hov, tw,ty = drawText(self.screen,self.nanoNokiaFont, instaMessage,textx ,texty, self.greenD)
            xEnd,yEnd = textx + tw, y + self.minis[5].get_rect().h
            return(value,xEnd,yEnd)

        # --------- display text

        displayText = text + ' ' + str(value)

        selected = drawSelectableImage(self.increment[0],self.increment[1],(x,y),self,trim=False)
        if(userInput.upper() == incrementKey.upper()): selected = True

        if(selected):
            if(inc<=cap):
                value = value + inc
            else:
                value = value + cap

        hov, tw,ty = drawText(self.screen,self.nanoNokiaFont, displayText,textx ,texty, self.greenD)

        xEnd,yEnd = textx + tw, y + self.minis[5].get_rect().h

        return(value,xEnd,yEnd)

    def incDecWidgetAbsolute(self,x,y,text,value,inc=1,cap=100,userInput="none",incrementKey="notset"):
        """+ button and text to increment and return value
        """

        displayText = text + ' ' + str(value)

        selected = drawSelectableImage(self.decrement[0],self.decrement[1],(x,y),self,trim=False)
        if(userInput.upper() == incrementKey.upper()): selected = True
        if(selected):
            if((value - inc)>=0):
                value = value - inc
            else:
                value = 0

        x = x + self.decrement[0].get_rect().w
        plusSelected = drawSelectableImage(self.increment[0],self.increment[1],(x,y),self,trim=False)
        if(plusSelected):
            if((value + inc)<=cap):
                value = value + inc
            else:
                value = cap

        textx, texty = x+60,y+10
        hov, tw,ty = drawText(self.screen,self.nanoNokiaFont, displayText,textx ,texty, self.greenD)

        xEnd,yEnd = textx + tw, y + self.minis[5].get_rect().h

        return(value,xEnd,yEnd)

    def debug(self,debugMessage):
        if(self.debugSwitch):
            print(debugMessage)

    def debugDetailed(self,debugMessage):
        if(self.debugSwitch=='detailed'):
            print(debugMessage)

class notificationDialogue():
    def __init__(self):
        self.initialised = False
        self.origText    = ''
        self.origSource  = ''
        self.textArray   = []
        self.colour      = (0,0,0)
        self.y           = 0

        self.senPos      = 0

    def drawDialogue(self,gui,myfont, text,pos,maxWidth,maxHeight,clicked, colour=(255, 255, 255),skip=False,verticalSep=1.1,maxVerticleLines=80,displayNextButton=False,source=None):
        sx,sy = pos[0],pos[1]
        x,y        = sx,sy
        tRemaining = ""
        hovered    = gui.mouseCollides((gui.mx,gui.my),x,y,maxWidth,maxHeight)

        # reset if called by new function
        if(self.origText!= text or self.origSource!= source):
            self.initialised=False
            self.origText = text

        if(self.initialised== False):
            # format paragraph into array of fitted sentences
            self.origText    = text
            self.origSource  = source
            self.senPos      = 0
            dAr,para = [], ""
            for word in text.split(' '):
                pre   = para
                para += word + " "
                textsurface = myfont.render(para, True, colour)
                w = textsurface.get_rect().width
                if(w>= maxWidth):
                    dAr.append(pre)
                    para = word + " "
            dAr.append(para)

            self.textArray = dAr
            self.initialised = True

        hTotal = 0
        for sentence in range(0,len(self.textArray)):
            textsurface = myfont.render(self.textArray[sentence], True, colour)
            h = textsurface.get_rect().height
            gui.screen.blit(textsurface,(x,y))
            y = y + verticalSep*h
            hTotal = hTotal + verticalSep*h
            tRemaining = self.textArray[sentence+1:]

            # Condition: If lines exceed specified MAX LINES, break here
            if((sentence>=maxVerticleLines-1)): break

            # Condition: If lines exceed specified HEIGHT
            if(hTotal >= maxHeight): break

            #if(displayNextButton): nextP = gui.nextButton.display(gui,noBorder=False)

        # Condition: If lines remaining and clicked, go next page
        if(clicked and hovered and (len(tRemaining)>0)):
            self.textArray = tRemaining


from django.shortcuts import render, redirect
from .models import *
import os
from django.conf import settings
from django.http import HttpResponse
import json

# Create your views here.

def cargarInicio(request):
    productos = Producto.objects.all()
    producto_perros = Producto.objects.filter(categoria_id=1)
    producto_gatos = Producto.objects.filter(categoria_id=2)
    return render(request,"inicio.html",{"prod" : productos, "prod_dogs":producto_perros, "prod_cats":producto_gatos})

def cargarAgregarProducto(request):
    categorias = Categoria.objects.all()
    productos = Producto.objects.all()

    return render(request, "agregarProducto.html",{"cate":categorias,"prod":productos})

def agregarProducto(request):
    #print("AGREGANDO PRODUCTOS A LA BBDD",request.POST)
    v_sku = request.POST['txtSku']
    v_precio = request.POST['txtPrecio']
    v_nombre = request.POST['txtNombre']
    v_imagen = request.FILES['txtImagen']
    v_descripcion = request.POST['txtDescripcion']
    v_stock = request.POST['txtStock']

    v_categoria = Categoria.objects.get(id_categoria = request.POST['cmbCategoria'])

    Producto.objects.create(sku = v_sku, precio = v_precio, nombre = v_nombre,imagen = v_imagen,descripcion = v_descripcion,stock = v_stock, categoria_id = v_categoria)

    return redirect('/agregarProducto')

def cargarEditarProducto(request,sku):
    producto = Producto.objects.get(sku = sku)
    categorias = Categoria.objects.all()
    return render(request,"editarProducto.html",{"prod":producto,"cate":categorias})

def editarProducto(request):
    v_sku = request.POST['txtSku']
    productoBD = Producto.objects.get(sku = v_sku)
    v_precio = request.POST['txtPrecio']
    v_nombre = request.POST['txtNombre']
    v_descripcion = request.POST['txtDescripcion']
    v_stock = request.POST['txtStock']
    v_categoria = Categoria.objects.get(id_categoria = request.POST['cmbCategoria'])

    try:
        v_imagen = request.FILES['txtImagen']
        ruta_img = os.path.join(settings.MEDIA_ROOT,str(productoBD.imagen))
        os.remove(ruta_img)

    except:
        v_imagen = productoBD.imagen

    productoBD.nombre = v_nombre
    productoBD.precio = v_precio
    productoBD.imagen = v_imagen
    productoBD.descripcion = v_descripcion
    productoBD.stock = v_stock
    productoBD.categoria_id = v_categoria

    productoBD.save()

    return redirect('/agregarProducto')

def eliminarProducto(request,sku):
    producto = Producto.objects.get(sku = sku)
    ruta_img = os.path.join(settings.MEDIA_ROOT,str(producto.imagen))
    os.remove(ruta_img)
    producto.delete()

    return redirect('/agregarProducto')

def carrito(request):
    #print("CARRITO",request.body)
    productos = json.loads(request.body)
    for p in productos:
        print("SKU",p['sku'])
        print("CANTIDAD",p['cantidad'])
    return HttpResponse("OK!")


from django.conf.urls import url

from network.views import views_auth
from network.views import views_app

urlpatterns = [

    # Main page
    url(r'^home/(?P<msg>.*)$', views_auth.main_page, name="Home"),
    # url(r'$', views_auth.main_page, name="Home"),
    # Auth urls
    url(r'^login/(?P<info>.*)$', views_auth.login_page, name="Login"),
    url(r'^logout', views_auth.logout_page, name="Logout"),
    url(r'^registration', views_auth.registration_page),

    # App urls
    url(r'^userpage/(?P<usr_id>.[0-9])', views_app.user_page, name="UserPage"),
    url(r'^userpage/wall/new_record', views_app.new_wall_record, name="NewWallRecord"),
    url(r'^userpage/wall/new_like', views_app.new_like, name="AddLike"),
    url(r'^userpage/wall/new_comment', views_app.new_comment, name="AddComment"),
    url(r'^userpage/wall/delete_post', views_app.delete_post, name="DeletePost"),

    url(r'^error/', views_app.error_page, name="UserPage"),
    url(r'^im/', views_app.mail_page, name="UserMail"),
    url(r'^send_msg/(?P<user_id>.[0-9])', views_app.send_msg, name="SendMessage"),
    url(r'^friend_request/', views_app.send_friend_request, name="FriendRequest"),
    url(r'^friends/', views_app.user_friends, name="Friends"),
    url(r'^delete_friend/', views_app.delete_friend, name="Delete Friend"),

    url(r'^sent/', views_app.user_sent_msgs, name="Sent msgs"),

    url(r'^requests/', views_app.user_requests, name="Friend requests"),

    url(r'^accept_request/', views_app.accept_request, name="Accept_request"),
    url(r'^decline_request/', views_app.decline_request, name="Decline_request")

]


from __future__ import annotations

import traceback

from PySide6 import QtWidgets

from randovania.games.prime2.patcher.claris_randomizer import ClarisRandomizerExportError

def create_box_for_exception(val: Exception) -> QtWidgets.QMessageBox:
    box = QtWidgets.QMessageBox(
        QtWidgets.QMessageBox.Critical,
        "An exception was raised",
        (
            f"An unhandled Exception occurred:\n{val}\n\n"
            "When reporting, make sure to paste the entire contents of the following box."
            "\nIt has already be copied to your clipboard."
        ),
        QtWidgets.QMessageBox.Ok,
    )
    from randovania.gui.lib import common_qt_lib

    common_qt_lib.set_default_window_icon(box)

    detailed_exception = "".join(traceback.format_exception(val))
    if isinstance(val, ClarisRandomizerExportError):
        detailed_exception += "\n\n"
        detailed_exception += val.detailed_text()

    box.setDetailedText(detailed_exception)

    common_qt_lib.set_clipboard(detailed_exception)

    # Expand the detailed text
    for button in box.buttons():
        if box.buttonRole(button) == QtWidgets.QMessageBox.ActionRole:
            button.click()
            break

    box_layout: QtWidgets.QGridLayout = box.layout()
    box_layout.addItem(
        QtWidgets.QSpacerItem(600, 0, QtWidgets.QSizePolicy.Policy.Minimum, QtWidgets.QSizePolicy.Policy.Expanding),
        box_layout.rowCount(),
        0,
        1,
        box_layout.columnCount(),
    )
    return box


from django.shortcuts import render
from .forms import getData, getTraningInfo
import requests
from bs4 import BeautifulSoup
from datetime import date, datetime
import folium
import geocoder
# Create your views here.

runs = {}

def calculate_difference(key):
    run_date_str = runs[key]["date"][:10] + " 0:0:0"
    today = datetime.now()

    run_day = datetime.strptime(run_date_str, "%Y-%m-%d %H:%M:%S")
    difference = (run_day - today).days
    minus = 0
    if today.weekday() != 0:
        minus += 7 - today.weekday()

    return difference - minus

def calculate_distance(key):
    distance = ''
    i = 0
    while runs[key]["distance"][i] != "k":
        distance += runs[key]["distance"][i]
        i += 1
    return int(distance)

def calculate_speed(hour, minutes, distance):
    minutes += hour*60
    speed = minutes / distance
    return speed

def speed_to_str(speed):
    minutes = speed // 1
    sek = speed - minutes
    sek *= 60
    if sek < 10:
        sek = "0" + str(round(sek))
    else:
        sek = str(round(sek))
    minutes = str(round(minutes))
    return minutes + ":" + sek

def basic_introduction(weeks):

    plan4 = {
        '1': {'pon': 'odpoczynek', 'wt': 'bieg 10 min', 'sr': 'opdoczynek', 'czw': 'bieg 10 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 15 min'},
        '2': {'pon': 'odpoczynek', 'wt': 'bieg 15 min', 'sr': 'opdoczynek', 'czw': 'bieg 15 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 20 min'},
        '3': {'pon': 'odpoczynek', 'wt': 'bieg 20 min', 'sr': 'opdoczynek', 'czw': 'bieg 25 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 25 min'},
        '4': {'pon': 'odpoczynek', 'wt': 'bieg 25 min', 'sr': 'opdoczynek', 'czw': 'bieg 30 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 30 min'}}

    plan5 = {
        '1': {'pon': 'odpoczynek', 'wt': 'bieg 10 min', 'sr': 'opdoczynek', 'czw': 'bieg 10 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 10 min'},
        '2': {'pon': 'odpoczynek', 'wt': 'bieg 15 min', 'sr': 'opdoczynek', 'czw': 'bieg 15 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 15 min'},
        '3': {'pon': 'odpoczynek', 'wt': 'bieg 20 min', 'sr': 'opdoczynek', 'czw': 'bieg 20 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 20 min'},
        '4': {'pon': 'odpoczynek', 'wt': 'bieg 25 min', 'sr': 'opdoczynek', 'czw': 'bieg 25 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 25 min'},
        '5': {'pon': 'odpoczynek', 'wt': 'bieg 30 min', 'sr': 'opdoczynek', 'czw': 'bieg 30 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 30 min'}}

    plan2 = {
        '1': {'pon': 'odpoczynek', 'wt': 'bieg 10 min', 'sr': 'opdoczynek', 'czw': 'bieg 15 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 15 min'},
        '2': {'pon': 'odpoczynek', 'wt': 'bieg 15 min', 'sr': 'opdoczynek', 'czw': 'bieg 20 min', 'pt': 'odpoczynek',
              'weekend': 'bieg 30 min'},
    }
    if weeks == 4:
        return plan4, weeks - 4, 5
    elif weeks == 5:
        return plan5, weeks - 5, 6
    else:
        print("tu jestem")

        return plan2, 2, 3

def introduction(weeks, actual_week, distance, week, mode, plan, weeks_for_introduction=0):

    mins = 0
    if mode == "Basic":
        # pocztatkowy dystans dla basic
        mins = 2
        if distance < 11:
            if weeks_for_introduction == 0:
                # ilosc tygodni na dostosowanie dystansu
                weeks_for_introduction = 4

        elif distance < 22:
            if weeks_for_introduction == 0:
                weeks_for_introduction = 10

        else:
            if weeks_for_introduction == 0:
                weeks_for_introduction = 15
            # jako ze dystans jest bardzo duzy trening odbywa sie na max 3/4 jego wartosic
            distance *= 0.75

    if mode == "Medium":
        mins = 5
        if distance < 22:
            if weeks_for_introduction == 0:
                weeks_for_introduction = 4
        else:
            if weeks_for_introduction == 0:
                weeks_for_introduction = 10
            distance *= 0.75

    if mode == "Advance":
        mins = 10
        if weeks_for_introduction == 0:
            weeks_for_introduction = 10
        distance *= 0.75

    # ilosc kilometrow jaka zwiekszamy co kazdy tydzien
    jump = (distance - mins) / (weeks_for_introduction - 1)

    # iterowanie przez kazdy tydzien traningu wprowadzajacego
    for i in range(actual_week, actual_week + weeks_for_introduction):
        plan[str(i)] = {}
        weeks -= 1
        # iterowanie przez kazdy dzien tygodnia (weekend jako 1 dzien czyli mozna se wybrac sob lub nd)
        for day in range(0, len(week)):
            if day % 2 == 0:
                plan[str(i)][week[day]] = "odpoczynek"
            elif (day == 1 or day == 3) and mins > 5:
                plan[str(i)][week[day]] = "bieg na " + str(round(mins / 2)) + "km"
            else:
                plan[str(i)][week[day]] = "bieg na " + str(round(mins)) + "km"
        mins += jump

    #aktualizowanie aktualnego tygonia
    actual_week += weeks_for_introduction

    return plan, weeks, actual_week

def full_training(weeks, actual_week, distance, week, mode, plan, speed):
    # range (actual_week, actual_week + weeks)

    if mode == "Basic":

        # min predkosc po introduction ktora jest zwiekszana z klejnymi tygodniami
        min_speed = 10
        if distance >= 22:
            distance *= 0.75

    elif mode == "Medium":
        min_speed = 8
        if distance >= 22:
            distance *= 0.75

    else:
        min_speed = 7
        if distance >= 22:
            distance *= 0.75

    # zwiekszanie predkosci co tydzien o jump
    jump = (min_speed - speed) / weeks
    for i in range(actual_week, actual_week + weeks):
        plan[str(i)] = {}
        min_speed -= jump
        weeks -= 1
        actual_week += 1
        for day in range(0, len(week)):
            if day % 2 == 0:
                plan[str(i)][week[day]] = "odpoczynek"
            elif day == 1 and 5 < distance < 11:
                plan[str(i)][week[day]] = "bieg na " + str(round(distance / 2)) + "km w czasie " + \
                                          speed_to_str(min_speed * 0.7) + " min/km"
            elif day == 1 and 5 < distance < 22:
                plan[str(i)][week[day]] = "bieg na " + str(round(distance / 2)) + "km w czasie " + \
                                          speed_to_str(min_speed * 0.8) + " min/km"
            elif day == 1 and 5 < distance:
                plan[str(i)][week[day]] = "bieg na " + str(round(distance / 2)) + "km w czasie " + \
                                          speed_to_str(min_speed * 0.9) + " min/km"
            elif day == 3 and mode != "Advance":
                plan[str(i)][week[day]] = "bieg interwalowy: 5x (bieg 1.5 min na maksimum mozliwosci + " \
                                          "2 min wolnego truchtu) + wybiganie na " + str(round(distance / 2)) + "km"
            elif day == 3:
                plan[str(i)][week[day]] = "bieg interwalowy: 5x (bieg 1.5 min na maksimum mozliwosci pod gorke + " \
                                          "2 min wolnego truchtu z gorki) + wybiganie na " + \
                                          str(round(distance / 2)) + "km"
            else:
                plan[str(i)][week[day]] = "bieg na " + str(distance) + "km w czasie " + speed_to_str(min_speed) + \
                                          " min/km"

    return plan, weeks, actual_week

def home(request):
    global runs
    runs = {}
    if request.method == "POST":
        runs = {}

        # pobranie danych z forms po wcisnieciu przycisku
        form = getData(request.POST)

        # ustawienie zmiennej na global w celu modyfikacji dict runs
        if form.is_valid():

            # pobieranie danych
            city = form.cleaned_data["city"]
            date_from_wrong = form.cleaned_data["date_from"]
            date_to_wrong = form.cleaned_data["date_to"]
            distance_from = form.cleaned_data["distance_from"]
            distance_to = form.cleaned_data["distance_to"]

            # zamiana daty
            if date_from_wrong is not None:
                date_from_correct = str(date_from_wrong.year) + "-" + str(date_from_wrong.month) + "-" + \
                                    str(date_from_wrong.day)
            else:
                date_from_correct = ""

            if date_to_wrong is not None:
                date_to_correct = str(date_to_wrong.year) + "-" + str(date_to_wrong.month) + "-" + \
                                    str(date_to_wrong.day)
            else:
                date_to_correct = ""

            # wyczyszczenie input-ow
            form = getData()

            # pobranie danych ze strony
            url = "https://run-log.com/events/?terms=" + city + "&date_from=" + date_from_correct + \
                  "&date_to=" + date_to_correct + "&distance_from=" + str(distance_from) + \
                  "&distance_to=" + str(distance_to) + "&location_radius=&action="
            website = requests.get(url)
            result = website.text
            doc = BeautifulSoup(result, "html.parser")
            table = doc.tbody
            trs = table.contents
            i = 0

            # iterowanie po kazdym elemenecie tabeli z danymi zawodow
            for tr in trs:
                i += 1

                # sprawdzenie czy w tabeli istenieja biegi oraz czy nie sprawdzania jest pusty wiersz
                if i % 2 == 0 and i <= 10 and len(tr.contents) >= 10:
                    run = {}
                    date, name, distance, shit, location = tr.contents[1::2]
                    run["date"] = date.text
                    run["distance"] = distance.text.strip()
                    run["location"] = location.text
                    run["number"] = i/2
                    name = name.a.string

                    # wyszukiwanie linkow do obrazu dla kazdego miasta w ktorym jest bieg
                    r = requests.get(
                        'https://commons.wikimedia.org/w/index.php?search=' + run["location"]
                        + '&title=Special:MediaSearch&go=Go&type=image')
                    result = r.text
                    doc = BeautifulSoup(result, "html.parser")
                    images = doc.find('a', {'class': 'sdms-image-result'})
                    print(images)
                    if not images:
                        run["image"] = "#"
                    else:
                        r = requests.get(images['href'])
                        result = r.text
                        doc = BeautifulSoup(result, "html.parser")
                        doc2 = doc.find('div', {'class': 'mw-body-content'})
                        image = doc2.find('img')
                        run["image"] = image['src']

                        # w wypadku wystapnie biegu z taka sama nazwa dodanie numerka do nazyw
                        if name in runs:
                            runs[name+" ("+str(i/2)[0]+")"] = run
                        else:
                            runs[name] = run
    else:
        form = getData()
    return render(request, "runsite/home.html", {"Data": form, "Runs": runs})

def run_plan(request):

    # pobranie url storny (zawiera index dictionary z dpowiednimi zawodami)
    url = int(request.build_absolute_uri()[22])
    key = list(runs.keys())[url-1]
    working = 1
    # oblicznie ile dni oraz tygodni jest do zawodow
    days = calculate_difference(key)
    weeks = days//7
    week = ['pon', 'wt', 'sr', 'cw', 'pt', 'weekend']
    plan = {}
    # konwertowanie dystansu ze slownika na typ int (pomijanie metrow)
    distance = calculate_distance(key)

    # generowanie mapy ze znacznikiem lokalizacji biegu
    try:
        location = geocoder.location(runs[key]['location'])
        lat = location.lat
        lng = location.lng
        mapa = folium.Map(location=[lat, lng], zoom_start=12)
        folium.Marker([lat, lng]).add_to(mapa)
    except:
        location = geocoder.osm('PL')
        lat = location.lat
        lng = location.lng
        mapa = folium.Map(location=[lat, lng], zoom_start=12)

    mapa = mapa._repr_html_()
    if request.method == "POST":
        working = 1

        # pobranie danych z forms po wcisnieciu przycisku
        form = getTraningInfo(request.POST)

        if form.is_valid():
            type_of_training = form.cleaned_data["type"]
            time_hours = form.cleaned_data["time_hours"]
            if time_hours:
                try:
                    time_hours = int(time_hours)
                except ValueError:
                    working = 0
                    time_hours = 0
            else:
                time_hours = 0
            time_minutes = form.cleaned_data["time_minutes"]
            if time_minutes:
                try:
                    time_minutes = int(time_minutes)
                except ValueError:
                    working = 0
                    time_minutes = 0
            else:
                time_minutes = 0
            speed = calculate_speed(time_hours, time_minutes, distance)
            if time_minutes < 0 or time_hours < 0 or speed < 2.5:
                working = 0

            form = getTraningInfo()
            if type_of_training == "Basic":
                speed *= 1.2
                if weeks <= 3:
                    print("nie da sie wygnerowa traningu1")
                    working = 0
                elif weeks <= 20:

                    #pierwszy tryb (najkrotrze zawody)
                    if distance < 11:
                        if weeks < 6:
                            plan, weeks, actual_week = basic_introduction(weeks)
                        elif weeks >= 6:
                            # pamietaj 6 - 2(basic_introduction)
                            # pamietaj ze full_training() z tych weekow co zostaly po introduction
                            plan, dif, actual_week = basic_introduction(2)
                            print(weeks)
                            # odjecie od pozostalych tygodni juz wykorzystanych
                            weeks -= dif
                            plan, weeks, actual_week = introduction(weeks, actual_week, distance, week,
                                                                    type_of_training, plan)
                            print(plan)
                            print(weeks)
                            print(actual_week)
                            plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                     type_of_training, plan, speed)
                            print(plan)
                            print(weeks)
                            print(actual_week)

                    #drugi tryb (srednio dlugie zaowdy)
                    elif distance < 22:
                        if weeks < 12:
                            print("nie da sie wygenerowac treningu2")
                            working = 0
                        elif weeks >= 12:

                            plan, dif, actual_week = basic_introduction(2)
                            weeks -= dif
                            print(weeks)
                            plan, weeks, actual_week = introduction(weeks, actual_week, distance, week,
                                                                    type_of_training, plan)
                            print(plan)
                            print(weeks)
                            print(actual_week)
                            plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                     type_of_training, plan, speed)
                            print(plan)
                            print(weeks)
                            print(actual_week)

                    #trzeci tryb(dlugie zawody)
                    else:
                        if weeks < 17:
                            print("nie da sie wygenerowac treningu2")
                            working = 0
                        if weeks >= 17:
                            plan, dif, actual_week = basic_introduction(2)
                            weeks -= dif
                            plan, weeks, actual_week = introduction(weeks, actual_week, distance, week,
                                                                    type_of_training, plan)

                            print(plan)
                            print(weeks)
                            print(actual_week)
                            plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                     type_of_training, plan, speed)
                            print(plan)
                            print(weeks)
                            print(actual_week)
                # ----------------------------------
                else:
                    if distance < 11:

                        # wyliczenie na korym tygoniu konczy sie introducion (+2 by uwzglednic basic_introdution)
                        weeks_for_introduction = round((weeks * 0.2)//1 + 2)
                        plan, dif, actual_week = basic_introduction(2)
                        weeks -= dif
                        plan, weeks, actual_week = introduction(weeks, actual_week, distance, week,
                                                                type_of_training, plan, weeks_for_introduction)

                        print(plan)
                        print(weeks)
                        print(actual_week)
                        plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                 type_of_training, plan, speed)
                        print(plan)
                        print(weeks)
                        print(actual_week)
                    elif distance < 22:
                        weeks_for_introduction = round((weeks * 0.5) // 1 + 2)
                        plan, dif, actual_week = basic_introduction(2)
                        weeks -= dif
                        plan, weeks, actual_week = introduction(weeks, actual_week, distance, week,
                                                                type_of_training, plan, weeks_for_introduction)

                        print(plan)
                        print(weeks)
                        print(actual_week)
                        plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                 type_of_training, plan, speed)
                        print(plan)
                        print(weeks)
                        print(actual_week)
                    else:
                        weeks_for_introduction = round((weeks * 0.75) // 1 + 2)

                        plan, dif, actual_week = basic_introduction(2)
                        weeks -= dif
                        plan, weeks, actual_week = introduction(weeks, actual_week, distance, week,
                                                                type_of_training, plan, weeks_for_introduction)
                        print(plan)
                        print(weeks)
                        print(actual_week)
                        plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                 type_of_training, plan, speed)
                        print(plan)
                        print(weeks)
                        print(actual_week)
            elif type_of_training == "Medium":
                if weeks <= 3:
                    print("nie da sie wygnerowa traningu1")
                    working = 0
                elif distance < 11:

                    # dla malego dystansu w trybie medium nie ma introduction
                    plan, weeks, actual_week = full_training(weeks, 1, distance, week,
                                                             type_of_training, {}, speed)
                    print(plan)
                    print(weeks)
                    print(actual_week)

                elif weeks <= 20:

                    if distance < 22:
                        if weeks < 4:
                            print("nie da sie wygnerowa traningu2")
                            working = 0
                        else:
                            plan, weeks, actual_week = introduction(weeks, 1, distance, week,
                                                                    type_of_training, {})
                            print(plan)
                            print(weeks)
                            print(actual_week)
                            plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                     type_of_training, plan, speed)
                            print(plan)
                            print(weeks)
                            print(actual_week)
                    else:
                        if weeks < 10:
                            print("nie da sie wygnerowa traningu2")
                            working = 0
                        else:
                            plan, weeks, actual_week = introduction(weeks, 1, distance, week,
                                                                    type_of_training, {})
                            print(plan)
                            print(weeks)
                            print(actual_week)
                            print(speed)
                            plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                     type_of_training, plan, speed)
                            print(plan)
                            print(weeks)
                            print(actual_week)
                else:
                    if distance < 22:
                        weeks_for_introduction = round((weeks * 0.2) // 1)
                        plan, weeks, actual_week = introduction(weeks, 1, distance, week,
                                                                type_of_training, {}, weeks_for_introduction)
                        print(plan)
                        print(weeks)
                        print(actual_week)
                        plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                 type_of_training, plan, speed)
                        print(plan)
                        print(weeks)
                        print(actual_week)
                    else:
                        weeks_for_introduction = round((weeks * 0.5) // 1)
                        plan, weeks, actual_week = introduction(weeks, 1, distance, week,
                                                                type_of_training, {}, weeks_for_introduction)
                        print(plan)
                        print(weeks)
                        print(actual_week)
                        plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                                 type_of_training, plan, speed)
                        print(plan)
                        print(weeks)
                        print(actual_week)

            else:
                speed *= 0.9
                if weeks <= 3:
                    print("nie da sie wygnerowa traningu1")
                    working = 0
                elif distance < 22:

                    # dla malego dystansu oraz sredniego w trybie advance nie ma introduction
                    plan, weeks, actual_week = full_training(weeks, 1, distance, week,
                                                             type_of_training, {}, speed)
                    print(plan)
                    print(weeks)
                    print(actual_week)

                elif weeks < 10:
                    print("nie da sie wygnerowa traningu3")
                    working = 0
                elif weeks <= 20:

                    plan, weeks, actual_week = introduction(weeks, 1, distance, week,
                                                            type_of_training, {})

                    #print(plan)
                    print(weeks)
                    print(actual_week)
                    print(speed)
                    plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                             type_of_training, plan, speed)
                    print(plan)
                    print(weeks)
                    print(actual_week)
                else:
                    weeks_for_introduction = round((weeks * 0.5) // 1)
                    plan, weeks, actual_week = introduction(weeks, 1, distance, week,
                                                            type_of_training, {}, weeks_for_introduction)
                    #print(plan)
                    print(weeks)
                    print(actual_week)
                    print(speed)
                    plan, weeks, actual_week = full_training(weeks, actual_week, distance, week,
                                                             type_of_training, plan, speed)
                    print(plan)
                    print(weeks)
                    print(actual_week)

        if working == 0:
            plan = {}
        for name, values in plan.items():
            print(name)
            print(values)
    else:
        form = getTraningInfo()
    return render(request, "runsite/runPlan.html", {"Forms": form, "Key": key, "Run": runs[key], "Mapa": mapa,
                                                    "Plan": plan, "Working": working})


from typing import Dict, List, Optional, Tuple, Union

from flask import (
    abort, g, jsonify, render_template, request, make_response, Response
)
from werkzeug.exceptions import (
    BadRequest, Forbidden, HTTPException, InternalServerError, NotFound
    )
from plot_weather import (BAD_REQUEST_IMAGE_DATA,
                          INTERNAL_SERVER_ERROR_IMAGE_DATA, DebugOutRequest,
                          app, app_logger, app_logger_debug)
from plot_weather.dao.weathercommon import WEATHER_CONF
from plot_weather.dao.weatherdao import WeatherDao
from plot_weather.dao.devicedao import DeviceDao, DeviceRecord
from plot_weather.db.sqlite3conv import DateFormatError, strdate2timestamp
from plot_weather.plotter.plotterweather import (
    ImageDateType, gen_plot_image, ImageDateParams, ParamKey
)
from werkzeug.datastructures import Headers, MultiDict
import psycopg2
from psycopg2.pool import SimpleConnectionPool
from psycopg2.extensions import connection
import plot_weather.util.dateutil as date_util

APP_ROOT: str = app.config["APPLICATION_ROOT"]

# エラーメッセージの内容 ※messages.confで定義
MSG_REQUIRED: str = app.config["MSG_REQUIRED"]
MSG_INVALID: str = app.config["MSG_INVALID"]
MSG_NOT_FOUND: str = app.config["MSG_NOT_FOUND"]
# ヘッダー
# トークン ※携帯端末では必須, 一致 ※ない場合は不一致とみなす
# messages.conf で定義済み
# 端末サイズ情報 ※携帯端末では必須, 形式は 幅x高さx密度
MSG_PHONE_IMG: str = "phone image size"
REQUIRED_PHONE_IMG: str = f"401,{MSG_PHONE_IMG} {MSG_REQUIRED}"
INVALID_PHONE_IMG: str = f"402,{MSG_PHONE_IMG} {MSG_INVALID}"
# リクエストパラメータ
PARAM_DEVICE: str = "device_name"
PARAM_START_DAY: str = "start_day"
PARAM_BOFORE_DAYS: str = "before_days"
PARAM_YEAR_MONTH: str = "year_month"
# リクエストパラメータエラー時のコード: 421番台以降
# デバイス名: 必須, 長さチェック (1-20byte), 未登録
DEVICE_LENGTH: int = 20
#  デバイスリスト取得クリエスと以外の全てのリクエスト
REQUIRED_DEVICE: str = f"421,{PARAM_DEVICE} {MSG_REQUIRED}"
INVALIDD_DEVICE: str = f"422,{PARAM_DEVICE} {MSG_INVALID}"
DEVICE_NOT_FOUND: str = f"423,{PARAM_DEVICE} {MSG_NOT_FOUND}"
# 期間指定画像取得リクエスト
#  (1)検索開始日["start_day"]: 任意 ※未指定ならシステム日付を検索開始日とする
#     日付形式(ISO8601: YYYY-mm-dd), 10文字一致
INVALID_START_DAY: str = f"431,{PARAM_START_DAY} {MSG_INVALID}"
#  (2)検索開始日から N日前 (1,2,3,7日): 必須
REQUIRED_BOFORE_DAY: str = f"433,{PARAM_BOFORE_DAYS} {MSG_REQUIRED}"
INVALID_BOFORE_DAY: str = f"434,{PARAM_BOFORE_DAYS} {MSG_INVALID}"
# 月間指定画像取得リクエスト
#   年月: 必須, 形式(YYYY-mm), 7文字一致
REQUIRED_YEAR_MONTH: str = f"435,{PARAM_YEAR_MONTH} {MSG_REQUIRED}"
INVALID_YEAR_MONTH: str = f"436,{PARAM_YEAR_MONTH} {MSG_INVALID}"

# エラーメッセージを格納する辞書オブジェクト定義
MSG_DESCRIPTION: str = "error_message"
# 固定メッセージエラー辞書オブジェクト
ABORT_DICT_UNMATCH_TOKEN: Dict[str, str] = {MSG_DESCRIPTION: app.config["UNMATCH_TOKEN"]}
# 可変メッセージエラー辞書オブジェクト: ""部分を置き換える
ABORT_DICT_BLANK_MESSAGE: Dict[str, str] = {MSG_DESCRIPTION: ""}

def get_connection() -> connection:
    if 'db' not in g:
        conn_pool: SimpleConnectionPool = app.config["postgreSQL_pool"]
        g.db: connection = conn_pool.getconn()
        g.db.set_session(readonly=True, autocommit=True)
        if app_logger_debug:
            app_logger.debug(f"g.db:{g.db}")
    return g.db

@app.teardown_appcontext
def close_connection(exception=None) -> None:
    db: connection = g.pop('db', None)
    if app_logger_debug:
        app_logger.debug(f"db:{db}")
    if db is not None:
        app.config["postgreSQL_pool"].putconn(db)

@app.route(APP_ROOT, methods=["GET"])
def index() -> str:
    """本日データ表示画面 (初回リクエストのみ)

    :return: 本日データ表示HTMLページ (matplotlibでプロットした画像含む)
    """
    if app_logger_debug:
        app_logger.debug(request.path)
    try:
        conn: connection = get_connection()
        # 年月日リスト取得
        dao = WeatherDao(conn, logger=app_logger)
        yearMonthList: List[str] = dao.getGroupbyMonths(
            device_name=WEATHER_CONF["DEVICE_NAME"],
            start_date=WEATHER_CONF["STA_YEARMONTH"],
        )
        if app_logger_debug:
            app_logger.debug(f"yearMonthList:{yearMonthList}")
        # 本日データプロット画像取得
        image_date_params = ImageDateParams(ImageDateType.TODAY)
        img_base64_encoded: str = gen_plot_image(
            conn, image_date_params=image_date_params, logger=app_logger
        )
    except Exception as exp:
        app_logger.error(exp)
        abort(InternalServerError.codde, InternalServerError(original_exception=exp))

    strToday: str = app.config.get("STR_TODAY", "")
    titleSuffix: str = app.config.get("TITLE_SUFFIX", "")
    defaultMainTitle: str = strToday + titleSuffix
    return render_template(
        "showplotweather.html",
        ip_host=app.config["SERVER_NAME"],
        app_root_url=APP_ROOT,
        path_get_today="/gettoday",
        path_get_month="/getmonth/",
        str_today=strToday,
        title_suffix=titleSuffix,
        info_today_update_interval=app.config.get("INFO_TODAY_UPDATE_INTERVAL"),
        default_main_title=defaultMainTitle,
        year_month_list=yearMonthList,
        img_src=img_base64_encoded,
    )

@app.route("/plot_weather/gettoday", methods=["GET"])
def getTodayImage() -> Response:
    """本日データ取得リクエスト(2回以降) JavaScriptからのリクエスト想定

    :return: jSON形式(matplotlibでプロットした画像データ(形式: png)のbase64エンコード済み文字列)
         (出力内容) jSON('data:image/png;base64,... base64encoded data ...')
    """
    if app_logger_debug:
        app_logger.debug(request.path)
    try:
        conn: connection = get_connection()
        # 本日データプロット画像取得
        image_date_params = ImageDateParams(ImageDateType.TODAY)
        img_base64_encoded: str = gen_plot_image(
            conn, image_date_params, logger=app_logger
        )
    except psycopg2.Error as db_err:
        app_logger.error(db_err)
        abort(InternalServerError.code, _set_errormessage(f"559,{db_err}"))
    except Exception as exp:
        app_logger.error(exp)
        return _createErrorImageResponse(InternalServerError.code)

    return _createImageResponse(img_base64_encoded)

@app.route("/plot_weather/getmonth/<yearmonth>", methods=["GET"])
def getMonthImage(yearmonth) -> Response:
    """要求された年月の月間データ取得

    :param yearmonth str: 年月 (例) 2022-01
    :return: jSON形式(matplotlibでプロットした画像データ(形式: png)のbase64エンコード済み文字列)
         (出力内容) jSON('data:image/png;base64,... base64encoded data ...')
    """
    if app_logger_debug:
        app_logger.debug(request.path)
    try:
        # リクエストパラメータの妥当性チェック: "YYYY-mm" + "-01"
        chk_yyyymmdd = yearmonth + "-01"
        # 日付チェック(YYYY-mm-dd): 日付不正の場合例外スロー
        strdate2timestamp(chk_yyyymmdd, raise_error=True)
        conn: connection = get_connection()
        # 指定年月(year_month)データプロット画像取得
        image_date_params = ImageDateParams(ImageDateType.YEAR_MONTH)
        param: Dict[ParamKey, str] = image_date_params.getParam()
        param[ParamKey.YEAR_MONTH] = yearmonth
        image_date_params.setParam(param)
        img_base64_encoded: str = gen_plot_image(
            conn, image_date_params, logger=app_logger
        )
    except DateFormatError as dfe:
        # BAD Request
        app_logger.warning(dfe)
        return _createErrorImageResponse(BadRequest.code)
    except psycopg2.Error as db_err:
        # DBエラー
        app_logger.error(db_err)
        abort(InternalServerError.code, _set_errormessage(f"559,{db_err}"))
    except Exception as exp:
        # バグ, DBサーバーダウンなど想定
        app_logger.error(exp)
        return _createErrorImageResponse(InternalServerError.code)

    return _createImageResponse(img_base64_encoded)

@app.route("/plot_weather/getlastdataforphone", methods=["GET"])
def getLastDataForPhone() -> Response:
    """最新の気象データを取得する (スマートホン専用)
       [仕様変更] 2023-09-09
         (1) リクエストパラメータ追加
            device_name: デバイス名 ※必須

    :param: request parameter: device_name="xxxxx"
    """
    if app_logger_debug:
        app_logger.debug(request.path)
        # Debug output request.headers or request.arg or both
        _debugOutRequestObj(request, debugout=DebugOutRequest.HEADERS)

    # トークン必須
    headers: Headers = request.headers
    if not _matchToken(headers):
        abort(Forbidden.code, ABORT_DICT_UNMATCH_TOKEN)

    # デバイス名必須
    param_device_name: str = _checkDeviceName(request.args)
    try:
        conn: connection = get_connection()
        # 現在時刻時点の最新の気象データ取得
        dao = WeatherDao(conn, logger=app_logger)
        rec_count: int
        row: Optional[Tuple[str, float, float, float, float]]
        # デバイス名に対応する最新のレコード取得
        row = dao.getLastData(device_name=param_device_name)
        if row:
            rec_count = 1
            measurement_time, temp_out, temp_in, humid, pressure = row
            return _responseLastDataForPhone(
                measurement_time, temp_out, temp_in, humid, pressure, rec_count)
        else:
            # デバイス名に対応するレコード無し
            rec_count = 0
            return _responseLastDataForPhone(None, None, None, None, None, rec_count)
    except psycopg2.Error as db_err:
        app_logger.error(db_err)
        abort(InternalServerError.code, _set_errormessage(f"559,{db_err}"))
    except Exception as exp:
        app_logger.error(exp)
        abort(InternalServerError.code, description=str(exp))

@app.route("/plot_weather/getfirstregisterdayforphone", methods=["GET"])
def getFirstRegisterDayForPhone() -> Response:
    """デバイスの観測データの初回登録日を取得する (スマートホン専用)
       [仕様追加] 2023-09-13

           :param: request parameter: device_name="xxxxx"
    """
    if app_logger_debug:
        app_logger.debug(request.path)
        # Debug output request.headers or request.arg or both
        _debugOutRequestObj(request, debugout=DebugOutRequest.HEADERS)

    # トークン必須
    headers: Headers = request.headers
    if not _matchToken(headers):
        abort(Forbidden.code, ABORT_DICT_UNMATCH_TOKEN)

    # デバイス名必須
    param_device_name: str = _checkDeviceName(request.args)
    try:
        conn: connection = get_connection()
        dao = WeatherDao(conn, logger=app_logger)
        # デバイス名に対応する初回登録日取得
        first_register_day: Optional[str] = dao.getFisrtRegisterDay(param_device_name)
        if app_logger_debug:
            app_logger.debug(f"first_register_day[{type(first_register_day)}]: {first_register_day}")
        if first_register_day:
            return _responseFirstRegisterDayForPhone(first_register_day, 1)
        else:
            # デバイス名に対応するレコード無し
            return _responseFirstRegisterDayForPhone(None, 0)
    except psycopg2.Error as db_err:
        app_logger.error(db_err)
        abort(InternalServerError.code, _set_errormessage(f"559,{db_err}"))
    except Exception as exp:
        app_logger.error(exp)
        abort(InternalServerError.code, description=str(exp))

@app.route("/plot_weather/gettodayimageforphone", methods=["GET"])
def getTodayImageForPhone() -> Response:
    """本日データ画像取得リクエスト (スマートホン専用)
       [仕様変更] 2023-09-09
         (1) リクエストパラメータ追加
            device_name: デバイス名 ※必須
         (2) レスポンスにレコード件数を追加 ※0件エラーの抑止

    :param: request parameter: device_name="xxxxx"
    :return: jSON形式(matplotlibでプロットした画像データ(形式: png)のbase64エンコード済み文字列)
         (出力内容) jSON('data:': 'img_src':'image/png;base64,... base64encoded data ...',
                         'rec_count':xxx)
    """
    if app_logger_debug:
        app_logger.debug(request.path)
        _debugOutRequestObj(request, debugout=DebugOutRequest.HEADERS)

    # トークン必須
    headers: Headers = request.headers
    if not _matchToken(headers):
        abort(Forbidden.code, ABORT_DICT_UNMATCH_TOKEN)

    # デバイス名必須
    param_device_name: str = _checkDeviceName(request.args)

    # 表示領域サイズ+密度は必須: 形式(横x縦x密度)
    str_img_size: str = _checkPhoneImageSize(headers)
    try:
        conn: connection = get_connection()
        image_date_params = ImageDateParams(ImageDateType.TODAY)
        param: Dict[ParamKey, str] = image_date_params.getParam()
        param[ParamKey.PHONE_SIZE] = str_img_size
        image_date_params.setParam(param)
        rec_count: int
        img_base64_encoded: str
        rec_count, img_base64_encoded = gen_plot_image(
            conn, param_device_name, image_date_params, logger=app_logger
        )
        return _responseImageForPhone(rec_count, img_base64_encoded)
    except psycopg2.Error as db_err:
        app_logger.error(db_err)
        abort(InternalServerError.code, _set_errormessage(f"559,{db_err}"))
    except Exception as exp:
        app_logger.error(exp)
        abort(InternalServerError.code, description=str(exp))

@app.route("/plot_weather/getbeforedaysimageforphone", methods=["GET"])
def getBeforeDateImageForPhone() -> Response:
    """過去経過日指定データ画像取得リクエスト (スマートホン専用)
       [仕様変更] 2023-09-09
         (1) リクエストパラメータ追加
            device_name: デバイス名 ※必須
            start_day: 検索開始日(iso8601形式) ※任意
         (2) レスポンスにレコード件数を追加 ※0件エラーの抑止

    :param: request parameter: ?device_name=xxxxx&start_day=2023-05-01&before_days=(2|3|7)
    :return: jSON形式(matplotlibでプロットした画像データ(形式: png)のbase64エンコード済み文字列)
         (出力内容) jSON('data:': 'img_src':'image/png;base64,... base64encoded data ...',
                         'rec_count':xxx)
    """
    if app_logger_debug:
        app_logger.debug(request.path)
        _debugOutRequestObj(request, debugout=DebugOutRequest.BOTH)

    # トークン必須
    headers = request.headers
    if not _matchToken(headers):
        abort(Forbidden.code, ABORT_DICT_UNMATCH_TOKEN)

    # デバイス名 ※必須チェック
    param_device_name: str = _checkDeviceName(request.args)
    # 検索開始日 ※任意、指定されている場合はISO8601形式チェック
    str_start_day: Optional[str] = _checkStartDay(request.args)
    if str_start_day is None:
        # 検索開始日がない場合は当日を設定
        str_start_day = date_util.getTodayIsoDate()
    # Check before_days query parameter
    str_before_days: str = _checkBeforeDays(request.args)

    # 表示領域サイズ+密度は必須: 形式(横x縦x密度)
    str_img_size: str = _checkPhoneImageSize(headers)
    try:
        conn: connection = get_connection()
        image_date_params = ImageDateParams(ImageDateType.RANGE)
        param: Dict[ParamKey, str] = image_date_params.getParam()
        param[ParamKey.START_DAY] = str_start_day
        param[ParamKey.BEFORE_DAYS] = str_before_days
        param[ParamKey.PHONE_SIZE] = str_img_size
        image_date_params.setParam(param)
        rec_count: int
        img_base64_encoded: str
        rec_count, img_base64_encoded = gen_plot_image(
            conn, param_device_name, image_date_params, logger=app_logger
        )
        return _responseImageForPhone(rec_count,img_base64_encoded)
    except psycopg2.Error as db_err:
        app_logger.error(db_err)
        abort(InternalServerError.code, _set_errormessage(f"559,{db_err}"))
    except Exception as exp:
        app_logger.error(exp)
        abort(InternalServerError.code, description=str(exp))

@app.route("/plot_weather/get_devices", methods=["GET"])
def getDevices() -> Response:
    """センサーディバイスリスト取得リクエスト

    :return: JSON形式(idを除くセンサーディバイスリスト)
         (出力内容) JSON({"data":{"devices":[...]}')
    """
    if app_logger_debug:
        app_logger.debug(request.path)

    devices_with_dict: List[Dict]
    try:
        conn: connection = get_connection()
        dao: DeviceDao = DeviceDao(conn, logger=app_logger)
        devices: List[DeviceRecord] = dao.get_devices()
        devices_with_dict = DeviceDao.to_dict_without_id(devices)
        resp_obj: Dict[str, Dict] = {
            "data": {"devices": devices_with_dict},
            "status": {"code": 0, "message": "OK"}
        }
        return _make_respose(resp_obj, 200)
    except psycopg2.Error as db_err:
        app_logger.error(db_err)
        abort(InternalServerError.code, _set_errormessage(f"559,{db_err}"))
    except Exception as exp:
        app_logger.error(exp)
        abort(InternalServerError.code, description=str(exp))

def _debugOutRequestObj(request, debugout=DebugOutRequest.ARGS) -> None:
    if debugout == DebugOutRequest.ARGS or debugout == DebugOutRequest.BOTH:
        app_logger.debug(f"reqeust.args: {request.args}")
    if debugout == DebugOutRequest.HEADERS or debugout == DebugOutRequest.BOTH:
        app_logger.debug(f"request.headers: {request.headers}")

def _matchToken(headers: Headers) -> bool:
    """トークン一致チェック
    :param headers: request header
    :return: if match token True, not False.
    """
    token_value: str = app.config.get("HEADER_REQUEST_PHONE_TOKEN_VALUE", "!")
    req_token_value: Optional[str] = headers.get(
        key=app.config.get("HEADER_REQUEST_PHONE_TOKEN_KEY", "!"),
        type=str,
        default=""
    )
    if req_token_value != token_value:
        app_logger.warning("Invalid request token!")
        return False
    return True

def _checkPhoneImageSize(headers: Headers) -> str:
    """
    ヘッダーに表示領域サイズ+密度([width]x[height]x[density])をつけてくる
    ※1.トークンチェックを通過しているのでセットされている前提で処理
    ※2.途中でエラー (Androidアプリ側のBUG) ならExceptionで補足されJSONでメッセージが返却される
    :param headers: request header
    :return: (imageWidth, imageHeight, density)
    """
    img_size: str = headers.get(
        app.config.get("HEADER_REQUEST_IMAGE_SIZE_KEY", ""), type=str, default=""
    )
    if app_logger_debug:
        app_logger.debug(f"Phone imgSize: {img_size}")
    if len(img_size) == 0:
        abort(BadRequest.code, _set_errormessage(REQUIRED_PHONE_IMG))

    sizes: List[str] = img_size.split("x")
    try:
        img_wd: int = int(sizes[0])
        img_ht: int = int(sizes[1])
        density: float = float(sizes[2])
        if app_logger_debug:
            app_logger.debug(f"imgWd: {img_wd}, imgHt: {img_ht}, density: {density}")
        return img_size
    except Exception as exp:
        # ログには例外メッセージ
        app_logger.warning(f"[phone image size] {exp}")
        abort(BadRequest.code, _set_errormessage(INVALID_PHONE_IMG))

def _checkBeforeDays(args: MultiDict) -> str:
    # QueryParameter: before_days in (1,2,3,7)
    # before_days = args.get("before_days", default=-1, type=int)
    # args.get(key): keyが無い場合も キーが有る場合で数値以外でも -1 となり必須チェックができない
    # before_days = args.pop("before_days"): TypeError: 'ImmutableMultiDict' objects are immutable
    if len(args.keys()) == 0 or PARAM_BOFORE_DAYS not in args.keys():
        abort(BadRequest.code, _set_errormessage(REQUIRED_BOFORE_DAY))

    before_days = args.get(PARAM_BOFORE_DAYS, default=-1, type=int)
    if before_days not in [1,2,3,7]:
        abort(BadRequest.code, _set_errormessage(INVALID_BOFORE_DAY))

    return  str(before_days)

def _checkDeviceName(args: MultiDict) -> str:
    """デバイス名チェック
        パラメータなし: abort(BadRequest)
        該当レコードなし: abort(NotFound)
    return デバイス名
    """
    # 必須チェック
    if len(args.keys()) == 0 or PARAM_DEVICE not in args.keys():
        abort(BadRequest.code, _set_errormessage(REQUIRED_DEVICE))

    # 長さチェック: 1 - 20
    param_device_name: str = args.get(PARAM_DEVICE, default="", type=str)
    chk_size: int = len(param_device_name)
    if chk_size < 1 or chk_size > DEVICE_LENGTH:
        abort(BadRequest.code, _set_errormessage(INVALIDD_DEVICE))

    # 存在チェック
    if app_logger_debug:
        app_logger.debug("requestParam.device_name: " + param_device_name)

    exists: bool = False
    try:
        conn: connection = get_connection()
        dao: DeviceDao = DeviceDao(conn, logger=app_logger)
        exists = dao.exists(param_device_name)
    except Exception as exp:
        app_logger.error(exp)
        abort(InternalServerError.code, description=str(exp))

    if exists is True:
        return param_device_name
    else:
        abort(BadRequest.code, _set_errormessage(DEVICE_NOT_FOUND))

def _checkStartDay(args: MultiDict) -> Optional[str]:
    """検索開始日の形式チェック
        パラメータなし: OK
        パラメータ有り: ISO8601形式チェック
    return 検索開始日 | None
    """
    if len(args.keys()) == 0 or PARAM_START_DAY not in args.keys():
        return None

    # 形式チェック
    param_start_day: str = args.get(PARAM_START_DAY, default="", type=str)
    if app_logger_debug:
        app_logger.debug(f"start_day: {param_start_day}")
    valid: bool = date_util.checkIso8601Date(param_start_day)
    if valid is True:
        return param_start_day
    else:
        # 不正パラメータ
        abort(BadRequest.code, _set_errormessage(INVALID_START_DAY))

def _createImageResponse(img_src: str) -> Response:
    """画像レスポンスを返却する (JavaScript用)"""
    resp_obj = {"status": "success", "data": {"img_src": img_src}}
    return _make_respose(resp_obj, 200)

def _createErrorImageResponse(err_code) -> Response:
    """エラー画像レスポンスを返却する (JavaScript用)"""
    resp_obj = {"status": "error", "code": err_code}
    if err_code == BadRequest.code:
        resp_obj["data"] = {"img_src": BAD_REQUEST_IMAGE_DATA}
    elif err_code == InternalServerError.code:
        resp_obj["data"] = {"img_src": INTERNAL_SERVER_ERROR_IMAGE_DATA}
    return _make_respose(resp_obj, err_code)

def _responseLastDataForPhone(
        mesurement_time: str,
        temp_out: float,
        temp_in: float,
        humid: float,
        pressure: float,
        rec_count: int
        ) -> Response:
    """気象データの最終レコードを返却する (スマホアプリ用)"""
    resp_obj: Dict[str, Dict[str, Union[str, float]]] = {
        "status":
            {"code": 0, "message": "OK"},
        "data": {
            "measurement_time": mesurement_time,
            "temp_out": temp_out,
            "temp_in": temp_in,
            "humid": humid,
            "pressure": pressure,
            "rec_count": rec_count
        }
    }
    return _make_respose(resp_obj, 200)

def _responseFirstRegisterDayForPhone(
        first_day: Optional[str],
        rec_count: int
        ) -> Response:
    """気象データの初回登録日を返却する (スマホアプリ用)"""
    resp_obj: Dict[str, Dict[str, Union[str, int]]] = {
        "status":
            {"code": 0, "message": "OK"},
        "data": {
            "first_register_day": first_day,
            "rec_count": rec_count
        }
    }
    return _make_respose(resp_obj, 200)

def _responseImageForPhone(rec_count: int, img_src: str) -> Response:
    """Matplotlib生成画像を返却する (スマホアプリ用)
       [仕様変更] 2023-09-09
         レスポンスにレコード件数を追加 ※0件エラーの抑止
    """
    resp_obj: Dict[str, Dict[str, Union[int, str]]] = {
        "status": {"code": 0, "message": "OK"},
        "data": {
            "img_src": img_src,
            "rec_count": rec_count
         }
    }
    return _make_respose(resp_obj, 200)

def _set_errormessage(message: str) -> Dict:
    ABORT_DICT_BLANK_MESSAGE[MSG_DESCRIPTION] = message
    return ABORT_DICT_BLANK_MESSAGE

# Request parameter check error.
@app.errorhandler(BadRequest.code)
# Token error.
@app.errorhandler(Forbidden.code)
# Device not found.
@app.errorhandler(NotFound.code)
@app.errorhandler(InternalServerError.code)
def error_handler(error: HTTPException) -> Response:
    app_logger.warning(f"error_type:{type(error)}, {error}")
    # Bugfix: 2023-09-06
    err_msg: str
    if isinstance(error.description, dict):
        # アプリが呼び出すabort()の場合は辞書オブジェクト
        err_msg = error.description["error_message"]
    else:
        # Flaskが出す場合は HTTPException)
        err_msg = error.description
    resp_obj: Dict[str, Dict[str, Union[int, str]]] = {
        "status": {"code": error.code, "message": err_msg}
    }
    return _make_respose(resp_obj, error.code)

def _make_respose(resp_obj: Dict, resp_code) -> Response:
    response = make_response(jsonify(resp_obj), resp_code)
    response.headers["Content-Type"] = "application/json"
    return response


from django.db import models
from django_countries.fields import CountryField
from product.models import product, product_version
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth import get_user_model
User = get_user_model()
from decimal import Decimal
from django.conf import settings

class Order(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='order_user')
    full_name = models.CharField(max_length=50)
    address1 = models.CharField(max_length=250)
    address2 = models.CharField(max_length=250)
    city = models.CharField(max_length=100)
    phone = models.CharField(max_length=100)
    post_code = models.CharField(max_length=20)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    total_paid = models.DecimalField(max_digits=5, decimal_places=2)
    order_key = models.CharField(max_length=200)
    billing_status = models.BooleanField(default=False)

    class Meta:
        ordering = ('-created',)

    def __str__(self):
        return str(self.created)

class OrderItem(models.Model):
    order = models.ForeignKey(Order,
                              related_name='items',
                              on_delete=models.CASCADE)
    product = models.ForeignKey(product,
                                related_name='order_items',
                                on_delete=models.CASCADE)
    price = models.DecimalField(max_digits=5, decimal_places=2)
    quantity = models.PositiveIntegerField(default=1)

    def __str__(self):
        return str(self.id)  # type: ignore

class Basket(models.Model):
    ...
    # """
    # A base Basket class, providing some default behaviors that
    # can be inherited or overrided, as necessary.
    # """

    # def __init__(self, request):
    #     self.session = request.session
    #     basket = self.session.get(settings.BASKET_SESSION_ID)
    #     if settings.BASKET_SESSION_ID not in request.session:
    #         basket = self.session[settings.BASKET_SESSION_ID] = {}
    #     self.basket = basket

    # def add(self, product, qty):
    #     """
    #     Adding and updating the users basket session data
    #     """
    #     product_id = str(product.id)

    #     if product_id in self.basket:
    #         self.basket[product_id]["qty"] = qty
    #     else:
    #         self.basket[product_id] = {"price": str(product.regular_price), "qty": qty}

    #     self.save()

    # def __iter__(self):
    #     """
    #     Collect the product_id in the session data to query the database
    #     and return products
    #     """
    #     product_ids = self.basket.keys()
    #     products = product.objects.filter(id__in=product_ids)  # type: ignore
    #     basket = self.basket.copy()

    #     for product in products:
    #         basket[str(product.id)]["product"] = product

    #     for item in basket.values():
    #         item["price"] = Decimal(item["price"])
    #         item["total_price"] = item["price"] * item["qty"]
    #         yield item

    # def __len__(self):
    #     """
    #     Get the basket data and count the qty of items
    #     """
    #     return sum(item["qty"] for item in self.basket.values())

    # def update(self, product, qty):
    #     """
    #     Update values in session data
    #     """
    #     product_id = str(product)
    #     if product_id in self.basket:
    #         self.basket[product_id]["qty"] = qty
    #     self.save()

    # def get_subtotal_price(self):
    #     return sum(Decimal(item["price"]) * item["qty"] for item in self.basket.values())

    # def get_total_price(self):

    #     subtotal = sum(Decimal(item["price"]) * item["qty"] for item in self.basket.values())

    #     shipping = Decimal(0.00) if subtotal == 0 else Decimal(11.50)
    #     return subtotal + Decimal(shipping)

    # def delete(self, product):
    #     """
    #     Delete item from session data
    #     """
    #     product_id = str(product)

    #     if product_id in self.basket:
    #         del self.basket[product_id]
    #         self.save()

    # def clear(self):
    #     # Remove basket from session
    #     del self.session[settings.BASKET_SESSION_ID]
    #     self.save()

    # def save(self):
    #     self.session.modified = True

class WishList(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE,null=True)
    item = models.ForeignKey(product_version, on_delete=models.CASCADE,blank=True, null=True)

    class Meta(object):
          verbose_name = 'WishList'
          verbose_name_plural = 'WishLists'

    def __str__(self):
          return f"{self.user}"

class CheckoutBilling(models.Model):
    first_name = models.CharField(max_length=50,verbose_name='First Name', help_text='Max 255 character')
    last_name = models.CharField(max_length=50,verbose_name='Last Name', help_text='Max 255 character')
    company = models.TextField(verbose_name='Company')
    email = models.EmailField(verbose_name='Email Address')
    address = models.TextField(verbose_name='Street Address')
    country = CountryField(max_length=255, verbose_name='Country')
    telephone = models.CharField(max_length=25 ,verbose_name='Telephone')
    fax = models.CharField(max_length=50, verbose_name='Fax')
    user_id = models.ForeignKey(User, on_delete=models.CASCADE, null=True)

    class Meta:
        verbose_name = "Checkout Billing"
        verbose_name_plural = "Checkout Billings"

    def __str__(self):
        return self.first_name

class Checkout(models.Model):
      address=models.CharField(max_length=100)
      created_at=models.DateField(auto_now_add=True)
      updated_at=models.DateField(auto_now=True)

      def __str__(self):
        return self.address

class CheckoutShipping(models.Model):
    first_name = models.CharField(max_length=50,verbose_name='First Name', help_text='Max 255 character')
    last_name = models.CharField(max_length=50,verbose_name='Last Name', help_text='Max 255 character')
    company = models.TextField(verbose_name='Company')
    email = models.EmailField(verbose_name='Email Address')
    address = models.TextField(verbose_name='Street Address')
    country = CountryField(max_length=255, verbose_name='Country')
    telephone = models.CharField(max_length=25 ,verbose_name='Telephone')
    fax = models.CharField(max_length=50, verbose_name='Fax')
    user_id = models.ForeignKey(User, on_delete=models.CASCADE, null=True)

    class Meta:
        verbose_name = "Checkout Shipping"
        verbose_name_plural = "Checkout Shipping"

    def __str__(self):
        return self.first_name

class ShoppingCart(models.Model):
    product_name=models.CharField(max_length=200)
    img = models.ImageField(upload_to = "images/")
    unit_price=models.CharField(max_length=10)
    qty=models.CharField(max_length=20)
    subtotal=models.CharField(max_length=25)
    coupon=models.CharField(max_length=20)
    zip_code=models.CharField(max_length=20)
    state=models.TextField()
    country=models.TextField(blank=False)

    class Meta:
        verbose_name = "Shopping Cart"
        verbose_name_plural = "Shopping Cart"

    def __str__(self):
        return self.product_name


import pandas as pd
import pathlib as pl
import numpy as np
import RootPath
from abc import abstractmethod
from Utils.Data.Features.RawFeatures import *
from Utils.Data.Dictionary.MappingDictionary import *

def map_column_single_value(series, dictionary):
    mapped_series = series.map(dictionary).astype(np.int32)
    return pd.DataFrame(mapped_series)

def map_column_array(series, dictionary):
    mapped_series = series.map(
        lambda x: np.array([dictionary[y] for y in x.split('\t')], dtype=np.int32) if x is not pd.NA else None)
    return pd.DataFrame(mapped_series)

class MappedFeaturePickle(Feature):
    """
    Abstract class representing a dictionary that works with pickle file.
    """

    def __init__(self, feature_name: str, dataset_id: str):
        super().__init__(feature_name, dataset_id)
        self.pck_path = pl.Path(f"{Feature.ROOT_PATH}/{self.dataset_id}/mapped/{self.feature_name}.pck.gz")
        self.csv_path = pl.Path(f"{Feature.ROOT_PATH}/{self.dataset_id}/mapped/{self.feature_name}.csv.gz")

    def has_feature(self):
        return self.pck_path.is_file()

    def load_feature(self):
        assert self.has_feature(), f"The feature {self.feature_name} does not exists. Create it first."
        df = pd.read_pickle(self.pck_path, compression="gzip")
        # Renaming the column for consistency purpose
        df.columns = [self.feature_name]
        return df

    @abstractmethod
    def create_feature(self):
        pass

    def save_feature(self, dataframe: pd.DataFrame):
        # Changing column name
        dataframe.columns = [self.feature_name]
        self.pck_path.parent.mkdir(parents=True, exist_ok=True)
        dataframe.to_pickle(self.pck_path, compression='gzip')
        # For backup reason
        # self.csv_path.parent.mkdir(parents=True, exist_ok=True)
        # dataframe.to_csv(self.csv_path, compression='gzip', index=True)

class MappedFeatureTweetLanguage(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_tweet_language", dataset_id)

    def create_feature(self):
        feature = RawFeatureTweetLanguage(self.dataset_id)
        dataframe = feature.load_or_create()
        dictionary = MappingLanguageDictionary().load_or_create()
        mapped_dataframe = map_column_single_value(dataframe[feature.feature_name], dictionary)

        self.save_feature(mapped_dataframe)

class MappedFeatureGroupedTweetLanguage(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_grouped_tweet_language", dataset_id)

        self.group_id_dict = {}
        self.current_mapping = 0

    def get_grouped_id(self, language_id):
        # ??? inglese misto altre cose
        if language_id == 16 or language_id == 18 or language_id == 20:
            return 16
        # [UNK]
        elif language_id == 26 or language_id == 56 or language_id == 57 or language_id == 58 or language_id == 59 or language_id == 61:
            return 26
        # ???
        elif language_id == 28 or language_id == 36 or language_id == 37 or language_id == 43 or language_id == 45 or language_id == 46:
            return 28
        # persian / pashto
        elif language_id == 25 or language_id == 44 or language_id == 41:
            return 25
        # lingue indiane
        elif language_id == 8 or language_id == 32 or language_id == 34 or language_id == 35 or language_id == 47 or language_id == 48 or language_id == 49 or language_id == 50 or language_id == 52 or language_id == 53 or language_id == 54 or language_id == 60 or language_id == 62:
            return 8
        # lingue est europa
        elif language_id == 14 or language_id == 23 or language_id == 24 or language_id == 55:
            return 14
        # lingue nord europa
        elif language_id == 21 or language_id == 31 or language_id == 38 or language_id == 39:
            return 21
        # lingue centro europa / balcani
        elif language_id == 29 or language_id == 40 or language_id == 42:
            return 29
        # others (vietnamita, birmano, armeno, georgiano, uiguro)
        elif language_id == 30 or language_id == 51 or language_id == 63 or language_id == 64 or language_id == 65:
            return 30
        else:
            return language_id

    def remap_language_id(self, group_id):
        if group_id not in self.group_id_dict:
            self.group_id_dict[group_id] = self.current_mapping
            self.current_mapping += 1
        return self.group_id_dict[group_id]

    def create_feature(self):
        feature = MappedFeatureTweetLanguage(self.dataset_id)
        dataframe = feature.load_or_create()

        #dataframe = dataframe.head()
        grouped_dataframe = pd.DataFrame(dataframe["mapped_feature_tweet_language"].map(lambda x: self.get_grouped_id(x)))
        #print(grouped_dataframe)
        mapped_dataframe = pd.DataFrame(dataframe["mapped_feature_tweet_language"].map(lambda x: self.remap_language_id(x)))
        #print(mapped_dataframe)

        self.save_feature(mapped_dataframe)

class MappedFeatureTweetId(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_tweet_id", dataset_id)

    def create_feature(self):
        feature = RawFeatureTweetId(self.dataset_id)
        dataframe = feature.load_or_create()
        dictionary = MappingTweetIdDictionary().load_or_create()
        mapped_dataframe = map_column_single_value(dataframe[feature.feature_name], dictionary)

        self.save_feature(mapped_dataframe)

class MappedFeatureCreatorId(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_creator_id", dataset_id)

    def create_feature(self):
        feature = RawFeatureCreatorId(self.dataset_id)
        dataframe = feature.load_or_create()
        dictionary = MappingUserIdDictionary().load_or_create()
        mapped_dataframe = map_column_single_value(dataframe[feature.feature_name], dictionary)

        self.save_feature(mapped_dataframe)

class MappedFeatureEngagerId(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_engager_id", dataset_id)

    def create_feature(self):
        feature = RawFeatureEngagerId(self.dataset_id)
        dataframe = feature.load_or_create()
        dictionary = MappingUserIdDictionary().load_or_create()
        mapped_dataframe = map_column_single_value(dataframe[feature.feature_name], dictionary)

        self.save_feature(mapped_dataframe)

class MappedFeatureTweetHashtags(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_tweet_hashtags", dataset_id)

    def create_feature(self):
        feature = RawFeatureTweetHashtags(self.dataset_id)
        dataframe = feature.load_or_create()
        dictionary = MappingHashtagDictionary().load_or_create()
        mapped_dataframe = map_column_array(dataframe[feature.feature_name], dictionary)

        self.save_feature(mapped_dataframe)

class MappedFeatureTweetLinks(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_tweet_links", dataset_id)

    def create_feature(self):
        feature = RawFeatureTweetLinks(self.dataset_id)
        dataframe = feature.load_or_create()
        dictionary = MappingLinkDictionary().load_or_create()
        mapped_dataframe = map_column_array(dataframe[feature.feature_name], dictionary)

        self.save_feature(mapped_dataframe)

class MappedFeatureTweetDomains(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_tweet_domains", dataset_id)

    def create_feature(self):
        feature = RawFeatureTweetDomains(self.dataset_id)
        dataframe = feature.load_or_create()
        dictionary = MappingDomainDictionary().load_or_create()
        mapped_dataframe = map_column_array(dataframe[feature.feature_name], dictionary)

        self.save_feature(mapped_dataframe)

class MappedFeatureTweetMedia(MappedFeaturePickle):

    def __init__(self, dataset_id: str):
        super().__init__("mapped_feature_tweet_media", dataset_id)

    def create_feature(self):
        feature = RawFeatureTweetMedia(self.dataset_id)
        dataframe = feature.load_or_create()
        dictionary = MappingMediaDictionary().load_or_create()
        mapped_dataframe = map_column_array(dataframe[feature.feature_name], dictionary)

        self.save_feature(mapped_dataframe)