# -*- coding: utf-8 -*-
"""
Created on Fri Mar  4 13:24:57 2022

@author: Haochen Tu

Angry Plants Game Manager
"""
import pygame
from .basicManager import BasicManager
import pygame
import os
import random
import json
import sys
import time, threading
from modules.vector2D import Vector2
from modules.frameManager import FrameManager
from modules.buttonManager import ButtonManager
from modules.soundManager import SoundManager
from modules.sunflower import Sunflower
from modules.animals import Fox, Otter, Cat, Panda, Dragon, AbstractAnimal
from modules.weapons import FoxWeapon, OtterWeapon, CatWeapon, PandaWeapon, DragonWeapon
from modules.enemy import Cactus, Mushroom, Pumpkin, Flower, Dandelion
from .drawable import Drawable

class GameManager(BasicManager):

   def __init__(self, screenSize, loadFile):

       self._loadFile = loadFile
       self._screenSize = screenSize

   def load(self):

       filePtr = open(os.path.join("resources", "levels", self._loadFile))
       info = json.load(filePtr)
       filePtr.close()

       self._cactusAppearTimes = info["cactus"]
       self._pumpkinAppearTime = info["pumpkins"]
       self._mushroomAppearTime = info["mushrooms"]
       self._flowerAppearTime = info["flowers"]
       self._dandelionAppearTime = info["dandelions"]

       self._fm = FrameManager().getInstance()

       if self._loadFile == "jsonLevel0.txt":
           self._background = self._fm.getFrame("background.png")

       elif self._loadFile == "jsonLevel1.txt":
            self._background = self._fm.getFrame("background1.png")

       elif self._loadFile == "jsonLevel2.txt":
            self._background = self._fm.getFrame("background2.png")

       self._sunflower1 = Sunflower(Vector2(386,15))
       self._sunflower2 = Sunflower(Vector2(386,85))
       self._sunflower3 = Sunflower(Vector2(386,155))

       self._bm = ButtonManager()

       self._drawObj = None

       # animals list
       self._foxes = []
       self._otters = []
       self._cats = []
       self._pandas = []
       self._dragons = []
       self._buttons = []
       self._animalCollection = []
       self._animalCount = len(self._foxes)+ len(self._otters) + len(self._cats) + len(self._pandas) + len(self._dragons)

       self._skulls = []

       # weapons list
       self._foxWeapon = []
       self._otterWeapon = []
       self._catWeapon = []
       self._pandaWeapon = []
       self._dragonWeapon = []
       self._allWeapon = []

       # enemy list
       self._cactuses  = []
       self._mushrooms = []
       self._pumpkins = []
       self._flowers = []
       self._dandelions = []

       self._dx = 0

       self._enemyCollection = []

       self._enemyNumber = 0

       self._totalTime = 0

       self._enemyCount = len(self._cactusAppearTimes) + len(self._pumpkinAppearTime) + len(self._mushroomAppearTime) + len(self._flowerAppearTime) + len(self._dandelionAppearTime)

       self._enemyCountB = self._enemyCount

       self._progress = self._enemyNumber/self._enemyCountB

       self._moving = False

       # money left
       if self._loadFile == "jsonLevel2.txt":
           self._money = 2000
       else:
           self._money = 1500
       self._font = pygame.font.Font("freesansbold.ttf", 12)

       if self._loadFile == "jsonLevel2.txt":
           self._foxMoney = self._font.render("150" , True, (255,255,0))
           self._otterMoney = self._font.render("100" , True, (255,255,0))
           self._catMoney = self._font.render("200" , True, (255,255,0))
           self._pandaMoney = self._font.render("350" , True, (255,255,0))
           self._dragonMoney = self._font.render("500" , True, (255,255,0))

       else:
       #self._moneyleft = self._font.render("Money: " + str(self._money), True, (2,7,93))
           self._foxMoney = self._font.render("150" , True, (2,7,93))
           self._otterMoney = self._font.render("100" , True, (2,7,93))
           self._catMoney = self._font.render("200" , True, (2,7,93))
           self._pandaMoney = self._font.render("350" , True, (2,7,93))
           self._dragonMoney = self._font.render("500" , True, (2,7,93))

       #sound effect
       self._bulletNoise = SoundManager.getInstance()

       #bgm
       self._bgm = SoundManager.getInstance()

       #level star
       self._star = Drawable ("star.png", (160,90))

       self._winStar = False

   def draw(self, drawSurface):
      # draw everything
      drawSurface.blit(self._background, (0,0))
      self._percentage = "{:.0%}".format(self._progress)

      if self._loadFile == "jsonLevel2.txt":
          self._moneyleft = self._font.render("Money: " + str(self._money), True, (255,255,0))
          self._moneyReserve = self._font.render("(Max: 1500) ", True, (255,255,204))
          self._animalCounts = self._font.render("Denfenders: " + str(self._animalCount), True, (255, 0, 0))
          self._animalCap = self._font.render("(Max: 25) ", True, (255,255,204))
          self._printPercentage = self._font.render("Progress: " + str(self._percentage), True, (255,0,0))

      else:
          self._moneyleft = self._font.render("Money: " + str(self._money), True, (2,7,93))
          self._moneyReserve = self._font.render("(Max: 1500) ", True, (153,255,255))
          self._animalCounts = self._font.render("Denfenders: " + str(self._animalCount), True, (2,7,93))
          self._animalCap = self._font.render("(Max: 25) ", True, (153,255,255))
          self._printPercentage = self._font.render("Progress: " + str(self._percentage), True, (2,7,93))

      drawSurface.blit(self._moneyleft, (360, 252))
      drawSurface.blit(self._moneyReserve, (362, 265))
      drawSurface.blit(self._animalCounts, (145, 5))
      drawSurface.blit(self._animalCap, (235, 5))
      drawSurface.blit(self._printPercentage, (350, 5))
      drawSurface.blit(self._foxMoney, (61, 296))
      drawSurface.blit(self._otterMoney, (16, 296))
      drawSurface.blit(self._catMoney, (110, 296))
      drawSurface.blit(self._pandaMoney, (157, 296))
      drawSurface.blit(self._dragonMoney, (206, 296))

      self._sunflower1.draw(drawSurface)
      self._sunflower2.draw(drawSurface)
      self._sunflower3.draw(drawSurface)

      # initiate the buttom manager so it can draw the animals buttoms
      self._bm.draw(drawSurface)

      if self._moving:
          self._drawObj.draw(drawSurface)

      # animals
      for fightFox in self._foxes:
          fightFox.draw(drawSurface)
      for fightOtter in self._otters:
          fightOtter.draw(drawSurface)
      for fightCat in self._cats:
          fightCat.draw(drawSurface)
      for fightPanda in self._pandas:
          fightPanda.draw(drawSurface)
      for fightDragon in self._dragons:
          fightDragon.draw(drawSurface)

      # weapons
      for bullet in self._foxWeapon:
          bullet.draw(drawSurface)
      for bullet in self._otterWeapon:
          bullet.draw(drawSurface)
      for bullet in self._catWeapon:
          bullet.draw(drawSurface)
      for bullet in self._pandaWeapon:
          bullet.draw(drawSurface)
      for bullet in self._dragonWeapon:
          bullet.draw(drawSurface)

      # enemy
      for cactus in self._cactuses:
          cactus.draw(drawSurface)
      for mushroom in self._mushrooms:
          mushroom.draw(drawSurface)
      for pumpkin in self._pumpkins:
          pumpkin.draw(drawSurface)
      for flower in self._flowers:
          flower.draw(drawSurface)
      for dandelion in self._dandelions:
          dandelion.draw(drawSurface)

      if self._enemyCount == 0 and self._loadFile != "jsonLevel2.txt":
          self._star.draw(drawSurface)

   def handleEvent(self, event):
       # when clicked down the buttom, the correct animal image will be created
       SCALE = 2

       if event.type == pygame.MOUSEBUTTONDOWN and self._money > 0 and self._animalCount <25:

              adjustedPos = list([int(x / SCALE) for x in event.pos])
              result = self._bm.handleEvent(event, adjustedPos, self._money)
              if result != None:
                  self._moving = True
                  self._buttons.append(result)
                  self._drawObj = result

              elif self._star.getCollisionRect().collidepoint(adjustedPos):
                  self._winStar = True

       #when the user lift the mouse, we will create a new image at the place where the drawObj is
       elif event.type == pygame.MOUSEBUTTONUP and self._animalCount <25:
             self._moving = False

             if type(self._drawObj) == Fox and self._drawObj.getPosition()[1] > 0 and self._drawObj.getPosition()[1] < 200:
                 self._foxes.append(Fox(self._drawObj.getPosition()))
                 self._money = self._money - 150
                 self._foxes[-1]._animate = True
                 self._foxes[-1].snap()

             elif type(self._drawObj) == Otter and self._drawObj.getPosition()[1] > 0 and self._drawObj.getPosition()[1] < 200:
                 self._otters.append(Otter(self._drawObj.getPosition()))
                 self._money = self._money - 100
                 self._otters[-1]._animate = True
                 self._otters[-1].snap()

             elif type(self._drawObj) == Cat and self._drawObj.getPosition()[1] > 0 and self._drawObj.getPosition()[1] < 200:
                 self._cats.append(Cat(self._drawObj.getPosition()))
                 self._money = self._money - 200
                 self._cats[-1]._animate = True
                 self._cats[-1].snap()

             elif type(self._drawObj) == Panda and self._drawObj.getPosition()[1] > 0 and self._drawObj.getPosition()[1] < 200:
                 self._pandas.append(Panda(self._drawObj.getPosition()))
                 self._money = self._money - 350
                 self._pandas[-1]._animate = True
                 self._pandas[-1].snap()

             elif type(self._drawObj) == Dragon and self._drawObj.getPosition()[1] > 0 and self._drawObj.getPosition()[1] < 200:
                 self._dragons.append(Dragon(self._drawObj.getPosition()))
                 self._money = self._money - 500
                 self._dragons[-1]._animate = True
                 self._dragons[-1].snap()

             # make drawObj disappear so only one animal is at that place
             self._drawObj = None

       # when the mouse is moving, the animal will move with the mouse
       elif event.type == pygame.MOUSEMOTION and self._moving:
             adjustedPos = list([int(x / SCALE) for x in event.pos])
             if type(self._drawObj) == Fox:
                 self._drawObj.setPosition(Vector2(adjustedPos[0] - 45//2,
                                            adjustedPos[1] - 54//2))
             elif type(self._drawObj) == Otter:
                 self._drawObj.setPosition(Vector2(adjustedPos[0] - 38//2,
                                              adjustedPos[1] - 54//2))
             elif type(self._drawObj) == Cat:
                 self._drawObj.setPosition(Vector2(adjustedPos[0] - 68//2,
                                              adjustedPos[1] - 56//2))
             elif type(self._drawObj) == Panda:
                 self._drawObj.setPosition(Vector2(adjustedPos[0] - 65//2,
                                              adjustedPos[1] - 53//2))

             elif type(self._drawObj) == Dragon:
                 self._drawObj.setPosition(Vector2(adjustedPos[0] - 51//2,
                                              adjustedPos[1] - 62//2))

   def update(self, seconds, screenSize):

       # update sunflower to get it moving
       self._sunflower1.update(seconds)
       self._sunflower2.update(seconds)
       self._sunflower3.update(seconds)

       self._animalCollection = self._foxes + self._otters + self._cats + self._pandas + self._dragons

       self._animalCount = len(self._foxes)+ len(self._otters) + len(self._cats) + len(self._pandas) + len(self._dragons)

       # update animals to make them move and create bullet
       for fightFox in self._foxes:
           fightFox.update(seconds)
           if fightFox._canMakeBullet:
               bulletPosition = fightFox.getPosition()
               fightFox._canMakeBullet = False
               fightFox._coolDown = 3
               self._foxWeapon.append(FoxWeapon(Vector2(bulletPosition[0] + 17, bulletPosition[1] + 11)))
               self._bulletNoise.playSound("bullet.wav")

           for enemy in self._enemyCollection:
               enemy_rect = enemy.getCollisionRect()
               if fightFox.getSCollisionRect().colliderect(enemy_rect):
                   fightFox._life -= enemy._attack/500
                   enemy_rect = (0,0,0,0)
                   if fightFox._life <0:
                       self._foxes.remove(fightFox)
                   break

       for otter in self._otters:
           otter.update(seconds)
           if otter._canMakeBullet:

               bulletPosition = otter.getPosition()
               otter._canMakeBullet = False
               otter._coolDown = 4
               self._otterWeapon.append(OtterWeapon(Vector2(bulletPosition[0] + 5,bulletPosition[1] + 14)))
               self._bulletNoise.playSound("otter.wav")

           for enemy in self._enemyCollection:
               enemy_rect = enemy.getCollisionRect()
               if otter.getSCollisionRect().colliderect(enemy_rect):
                   otter._life -= enemy._attack/500
                   enemy_rect = (0,0,0,0)
                   if otter._life <0:
                       self._otters.remove(otter)
                   break

       for cat in self._cats:
           cat.update(seconds)
           if cat._canMakeBullet:
               bulletPosition = cat.getPosition()
               cat._canMakeBullet = False
               cat._coolDown = 3
               self._catWeapon.append(CatWeapon(Vector2(bulletPosition[0] + 5, bulletPosition[1] + 24)))
               self._bulletNoise.playSound("catSword.wav")

           for enemy in self._enemyCollection:
               enemy_rect = enemy.getCollisionRect()
               if cat.getSCollisionRect().colliderect(enemy_rect):
                   cat._life -= enemy._attack/500
                   enemy_rect = (0,0,0,0)
                   if cat._life <0:
                       self._cats.remove(cat)
                   break

       for panda in self._pandas:
           panda.update(seconds)
           if panda._canMakeBullet:
               bulletPosition = panda.getPosition()
               panda._canMakeBullet = False
               panda._coolDown = 4.5
               self._pandaWeapon.append(PandaWeapon(Vector2(bulletPosition[0] + 8, bulletPosition[1] + 11)))
               self._bulletNoise.playSound("panda.wav")

           for enemy in self._enemyCollection:
               enemy_rect = enemy.getCollisionRect()
               if panda.getSCollisionRect().colliderect(enemy_rect):
                   panda._life -= enemy._attack/500
                   enemy_rect = (0,0,0,0)
                   if panda._life <0:
                       self._pandas.remove(panda)
                   break

       for dragon in self._dragons:
           dragon.update(seconds)
           if dragon._canMakeBullet:
               bulletPosition = dragon.getPosition()
               dragon._canMakeBullet = False
               dragon._coolDown = 4.5
               self._dragonWeapon.append(DragonWeapon(Vector2(bulletPosition[0] - 5, bulletPosition[1] + 16)))
               self._bulletNoise.playSound("fireball.wav")

           for enemy in self._enemyCollection:
               enemy_rect = enemy.getCollisionRect()
               if dragon.getSCollisionRect().colliderect(enemy_rect):
                   dragon._life -= enemy._attack/500
                   enemy_rect = (0,0,0,0)

                   if dragon._life <0:
                       self._dragons.remove(dragon)

                   break

       self._allWeapon = self._foxWeapon + self._otterWeapon + self._catWeapon + self._pandaWeapon + self._dragonWeapon

      #check if skull should be removed
       for skull in self._skulls:
           if skull._life <0:
               self._skulls.remove(skull)

       # update the weapons to make them move and if it collide with animals it will disappear
       for bullet in self._foxWeapon:
           bullet.update(seconds)
           for enemy in self._enemyCollection:
             enemy_rect = enemy.getCollisionRect()
             if bullet.getCollisionRect().colliderect(enemy_rect) and type(enemy) != Dandelion:
                 self._foxWeapon.remove(bullet)
                 break
       for bullet in self._otterWeapon:
           bullet.update(seconds)
           for enemy in self._enemyCollection:
             enemy_rect = enemy.getCollisionRect()
             if bullet.getCollisionRect().colliderect(enemy_rect) and type(enemy) != Dandelion:
                 self._otterWeapon.remove(bullet)
                 break
       for bullet in self._catWeapon:
           bullet.update(seconds)
           for enemy in self._enemyCollection:
             enemy_rect = enemy.getCollisionRect()
             if bullet.getCollisionRect().colliderect(enemy_rect) and type(enemy) != Dandelion:
                 self._catWeapon.remove(bullet)
                 break
       for bullet in self._pandaWeapon:
           bullet.update(seconds)
           for enemy in self._enemyCollection:
             enemy_rect = enemy.getCollisionRect()
             if bullet.getCollisionRect().colliderect(enemy_rect) and type(enemy) != Dandelion:
                 self._pandaWeapon.remove(bullet)
                 break
       for bullet in self._dragonWeapon:
           bullet.update(seconds, self._dandelions)
           for enemy in self._enemyCollection:
             enemy_rect = enemy.getCollisionRect()
             if bullet.getCollisionRect().colliderect(enemy_rect):
                 self._dragonWeapon.remove(bullet)
                 break

       self._totalTime += seconds

       if len(self._cactusAppearTimes) > 0 and self._totalTime> self._cactusAppearTimes[0]:
           cactus_random = random.choice([20, 65, 108, 152, 196])
           self._cactuses.append(Cactus(Vector2(0, cactus_random)))
           self._enemyNumber += 1
           self._cactusAppearTimes.pop(0)

       if len(self._mushroomAppearTime) > 0 and self._totalTime> self._mushroomAppearTime[0]:
           mushroom_random = random.choice([27, 74, 118, 162, 204])
           self._mushrooms.append(Mushroom(Vector2(0,mushroom_random)))
           self._enemyNumber += 1
           self._mushroomAppearTime.pop(0)

       if len(self._pumpkinAppearTime) > 0 and self._totalTime > self._pumpkinAppearTime[0]:
           pumpkin_random = random.choice([13, 60, 104, 148, 192])
           self._pumpkins.append(Pumpkin(Vector2(0, pumpkin_random)))
           self._enemyNumber += 1
           self._pumpkinAppearTime.pop(0)

       if len(self._flowerAppearTime) > 0 and self._totalTime > self._flowerAppearTime[0]:
           flower_random = random.choice([22, 69, 113, 157, 200])
           self._flowers.append(Flower(Vector2(0, flower_random)))
           self._enemyNumber += 1
           self._flowerAppearTime.pop(0)

       if len(self._dandelionAppearTime) > 0 and self._totalTime > self._dandelionAppearTime[0]:
           dandelion_random = random.randint(60, 170)
           self._dandelions.append(Dandelion(Vector2(0, dandelion_random)))
           self._enemyNumber += 1
           self._dandelionAppearTime.pop(0)

       # update the enemy to make them move
       for cactus in self._cactuses:
             cactus_rect = cactus.getCollisionRect()
             collided = False
             if cactus._lifeValue <=0:
                 self._cactuses.remove(cactus)
                 self._bulletNoise.playSound("enemyDeath.wav")
                 if (self._money + 100) <= 1500:
                     self._money += 100
                 self._enemyCount -= 1
             for animal in self._animalCollection:
                 animal_rect = animal.getSCollisionRect()
                 if cactus_rect.colliderect(animal_rect):
                     collided = True
                     break

             if collided:
                 cactus.froze()
             else:
                 cactus.recover()

             cactus.update(seconds)

       for dandelion in self._dandelions:
             if dandelion._lifeValue <=0:
                 self._dandelions.remove(dandelion)
                 self._bulletNoise.playSound("enemyDeath.wav")
                 if (self._money + 450) <= 1500:
                     self._money += 450

                 self._enemyCount -= 1

             dandelion.update(seconds, self._dx)
             self._dx += 1

       for mushroom in self._mushrooms:
             mushroom_rect = mushroom.getCollisionRect()
             collided = False
             if mushroom._lifeValue <=0:
                 self._mushrooms.remove(mushroom)
                 self._bulletNoise.playSound("enemyDeath.wav")
                 if (self._money + 130) <= 1500:
                     self._money += 130

                 self._enemyCount -= 1
             for animal in self._animalCollection:
                 animal_rect = animal.getSCollisionRect()
                 if mushroom_rect.colliderect(animal_rect):
                     collided = True
                     break
             if collided:
                 mushroom.froze()
             else:
                 mushroom.recover()

             mushroom.update(seconds)

       for pumpkin in self._pumpkins:
             pumpkin_rect = pumpkin.getCollisionRect()
             collided = False
             for animal in self._animalCollection:
                 animal_rect = animal.getSCollisionRect()
                 if pumpkin_rect.colliderect(animal_rect):
                     collided = True
                     break
             if collided:
                 pumpkin.froze()
             else:
                 pumpkin.recover()

             pumpkin.update(seconds)

             if pumpkin._state.getState() == "died":
                 self._pumpkins.remove(pumpkin)
                 self._bulletNoise.playSound("enemyDeath.wav")
                 if (self._money + 180) <= 1500:
                     self._money += 180
                 self._enemyCount -= 1

       for flower in self._flowers:
             flower_rect = flower.getCollisionRect()
             collided = False
             for animal in self._animalCollection:
                 animal_rect = animal.getSCollisionRect()
                 if flower_rect.colliderect(animal_rect):
                     collided = True
                     break
             if collided:
                 flower.froze()
             else:
                 flower.recover()

             flower.update(seconds)

             if flower._state.getState() == "died":
                 self._flowers.remove(flower)
                 self._bulletNoise.playSound("enemyDeath.wav")
                 if (self._money + 80) <= 1500:
                     self._money += 80
                 self._enemyCount -= 1

       self._enemyCollection = self._cactuses + self._mushrooms + self._pumpkins + self._flowers + self._dandelions

       for enemy in self._enemyCollection:
           if enemy.getCollisionRect().colliderect(self._sunflower1.getCollisionRect()):
               return "dead"
           elif enemy.getCollisionRect().colliderect(self._sunflower2.getCollisionRect()):
               return "dead"
           elif enemy.getCollisionRect().colliderect(self._sunflower3.getCollisionRect()):
               return "dead"

       # check if bullet hit any enemy, if does, the enemy's life value will drop accordingly
       for bullet in self._allWeapon:
         bullet_rect = bullet.getCollisionRect()
         for enemy in self._enemyCollection:
             enemy_rect = enemy.getCollisionRect()
             if bullet_rect.colliderect(enemy_rect):
                if type(enemy) == Dandelion and type(bullet) == DragonWeapon:
                    enemy._lifeValue -= bullet.power
                elif type(enemy) == Dandelion and type(bullet) != DragonWeapon:
                    enemy._lifeValue -= 0
                else:
                    enemy._lifeValue -= bullet.power

       if self._loadFile == "jsonLevel2.txt" and self._enemyCount == 0:
           return "nextLevel"
       elif self._enemyCount == 0 and self._winStar == True:
           return "nextLevel"

       self._progress = self._enemyNumber/self._enemyCountB


from django.urls import path
from . import views

urlpatterns = [
 path('', views.index, name="index"), # /app
 path('registration/', views.register, name="register"), #/app/registration
 path('daily/', views.daily, name="daily"),
 path('models/', views.models, name="models"),
 path('keydates/', views.keydates, name="keydates"),
 path('about/', views.about, name="about")
]


import numpy as np
import os
import json

import torch
import torch.nn as nn
import torch.nn.functional as F

from lib.embedding import load_full_embedding_with_vocab
from lib.train import BaseClassifier
from lib.transformer import MultiHeadedAttention, PositionwiseFeedForward, PositionalEncoding

class SelfAttentionCnnClassifier(BaseClassifier):
    def __init__(self, cuda_device=None, words_embed=None, len_vocab=0, len_embed=0,
                 out_channels=100, conv_width=5, hidden_size=200, out_size=2, dropout=0.5,
                 h=8):
        super(SelfAttentionCnnClassifier, self).__init__(cuda_device=cuda_device,
                    words_embed=words_embed, len_vocab=len_vocab, len_embed=len_embed)

        self.pos_enc = PositionalEncoding(self.len_embed, dropout)

        self.q_mha = MultiHeadedAttention(h=h, d_model=self.len_embed, dropout=dropout)
        self.q_layer_norms = nn.ModuleList([nn.LayerNorm([self.len_embed]) for _ in range(2)])

        self.a_mha = MultiHeadedAttention(h=h, d_model=self.len_embed, dropout=dropout)
        self.a_layer_norms = nn.ModuleList([nn.LayerNorm([self.len_embed]) for _ in range(2)])

        self.q_conv = nn.Conv1d(self.len_embed, out_channels, conv_width, padding=(conv_width//2))
        self.a_conv = nn.Conv1d(self.len_embed, out_channels, conv_width, padding=(conv_width//2))

        self.hidden1 = nn.Linear(out_channels * 2, hidden_size)
        self.dropout = nn.Dropout(dropout)
        self.hidden2 = nn.Linear(hidden_size, out_size)

        if self.cuda_device is not None:
            self.q_mha = self.q_mha.cuda(self.cuda_device)
            self.q_layer_norms = self.q_layer_norms.cuda(self.cuda_device)
            self.a_mha = self.a_mha.cuda(self.cuda_device)
            self.a_layer_norms = self.a_layer_norms.cuda(self.cuda_device)
            self.q_conv = self.q_conv.cuda(self.cuda_device)
            self.a_conv = self.a_conv.cuda(self.cuda_device)
            self.hidden1 = self.hidden1.cuda(self.cuda_device)
            self.dropout = self.dropout.cuda(self.cuda_device)
            self.hidden2 = self.hidden2.cuda(self.cuda_device)

        nn.init.kaiming_uniform_(self.hidden1.weight, nonlinearity='relu')

    @classmethod
    def load(cls, config_path):
        with open(config_path, 'r') as fread:
            config_dict = json.load(fread)

        path_config = config_dict['Path']
        model_config = config_dict['Model']

        words_embed, words_vocab = load_full_embedding_with_vocab(path_config['embed_dir'])

        model = cls(words_embed=words_embed, out_channels=model_config['out_channels'],
                    conv_width=model_config['conv_width'], hidden_size=model_config['hidden_size'],
                    cuda_device=model_config['cuda_device'], dropout=model_config['dropout'],
                    h=model_config['h'])

        model.load_state_dict(torch.load(os.path.join(path_config['model_dir'], 'net.pt')))
        model.eval()
        return model

    def forward(self, *input):
        if self.cuda_device is not None:
            input = [data.cuda(self.cuda_device) for data in input]

        # words: (batch size, sentence length)
        q_words, a_words = input

        # mask: (batch size, 1, sentence length)
        # if word is special pad token (so have to be ignored in multi-head attention) or not
        q_mask = (q_words != 1).unsqueeze(-2)
        a_mask = (a_words != 1).unsqueeze(-2)

        # sent_mat: (batch size, sentence length, embedding dimension)
        # we added Layer Normalization layer after embedding
        q_sent_mat = self.q_layer_norms[0](self.embed(q_words))
        a_sent_mat = self.a_layer_norms[0](self.embed(a_words))

        # mha_out: (batch size, sentence length, embedding dimension)
        # multi-head attention output
        # self.q_mha = MultiHeadedAttention(h=h, d_model=self.len_embed, dropout=dropout)
        q_mha_out = self.q_mha(q_sent_mat, a_sent_mat, a_sent_mat, a_mask)
        q_mha_out = self.q_layer_norms[1](q_mha_out + q_sent_mat)

        a_mha_out = self.a_mha(a_sent_mat, q_sent_mat, q_sent_mat, q_mask)
        a_mha_out = self.a_layer_norms[1](a_mha_out + a_sent_mat)

        # conv_out: (batch size, out channels, sentence length)
        q_conv_out = self.q_conv(q_mha_out.transpose(2,1))
        # pool out: (batch size, out_channels)
        # we used max over time pooling
        q_pool_out, _ = torch.max(q_conv_out, dim=-1)

        a_conv_out = self.a_conv(a_mha_out.transpose(2, 1))
        a_pool_out, _ = torch.max(a_conv_out, dim=-1)

        # qa_repr: (batch size, out channels * 2)
        # joined representation
        qa_repr = torch.cat([q_pool_out, a_pool_out], dim=-1)

        # hidden layer and softmax layer
        hidden1_out = self.hidden1(qa_repr)
        hidden2_out = self.hidden2(self.dropout(F.relu(hidden1_out)))

        return hidden2_out


import logging

# ============ mapping helpers start ============
# The mapping here are for ad hoc testing. The real mappings will be different
# from project to project and will be added in future CLs.

def FindBuganizerComponentId(monorail_component):
  # temp. will have real mapping.
  del monorail_component
  return 1325852

def FindBuganizerComponents(monorail_project_name):
  """return a list of components in buganizer based on the monorail project

  The current implementation is ad hoc as the component mappings are not
  fully set up on buganizer yet.
  """
  if monorail_project_name == 'MigratedProject':
    return ['1325852']
  return []

def FindMonorailProject(buganizer_component_id):
  if buganizer_component_id == '1325852':
    return 'MigratedProject'
  return ''

def FindBuganizerHotlists(monorail_labels):
  hotlists = []
  for label in monorail_labels:
    hotlist = _FindBuganizerHotlist(label)
    if hotlist:
      hotlists.append(hotlist)
  logging.debug(
    '[PerfIssueService] labels (%s) -> hotlists (%s)',
    monorail_labels, hotlists)
  return hotlists

def _FindBuganizerHotlist(monorail_label):
  if monorail_label == 'chromeperf-test':
    return '5141966'
  elif monorail_label == 'chromeperf-test-2':
    return '5142065'
  return None

def _FindMonorailLabel(buganizer_hotlist):
  if buganizer_hotlist == '5141966':
    return 'chromeperf-test'
  elif buganizer_hotlist == '5142065':
    return 'chromeperf-test-2'
  return None

def _FindMonorailStatus(buganizer_status):
  if buganizer_status == 'NEW':
    return 'Unconfirmed'
  elif buganizer_status == 'ASSIGNED':
    return "Assigned"
  elif buganizer_status == 'ACCEPTED':
    return 'Started'
  elif buganizer_status == 'FIXED':
    return 'Fixed'
  elif buganizer_status == 'VERIFIED':
    return 'Verified'
  return 'Untriaged'

def FindBuganizerStatus(monorail_status):
  if monorail_status == 'Unconfirmed':
    return 'NEW'
  elif monorail_status == 'Assigned':
    return "ASSIGNED"
  elif monorail_status == 'Started':
    return 'ACCEPTED'
  elif monorail_status == 'Fixed':
    return 'FIXED'
  elif monorail_status == 'Verified':
    return 'VERIFIED'
  return 'STATUS_UNSPECIFIED'

# ============ mapping helpers end ============

def GetBuganizerStatusUpdate(issue_update, status_enum):
  ''' Get the status from an issue update

  In Buganizer, each update event is saved in an issueUpdate struct like:
    {
      "author": {
        "emailAddress": "wenbinzhang@google.com"
      },
      "timestamp": "2023-07-28T22: 17: 34.721Z",
      ...
      "fieldUpdates": [
        {
          "field": "status",
          "singleValueUpdate": {
            "oldValue": {
              "@type": "type.googleapis.com/google.protobuf.Int32Value",
              "value": 2
            },
            "newValue": {
              "@type": "type.googleapis.com/google.protobuf.Int32Value",
              "value": 4
            }
          }
        }
      ],
      ...
      "version": 5,
      "issueId": "285172796"
    }
  Noticed that each update on the UI can have multiple operations, but each of
  operations is a single issue update. So far we only use the 'status' value,
  thus the other updates like priority change are not returned here.
  '''
  for field_update in issue_update.get('fieldUpdates', []):
    if field_update.get('field') == 'status':
      new_status = field_update.get('singleValueUpdate').get('newValue').get('value')

      # The current status returned from issueUpdate is integer, which is
      # different from the definition in the discovery doc.
      # I'm using  service._schema.get() to load the schema in JSON.
      if isinstance(new_status, int):
        new_status = status_enum[new_status]

      status_update = {
        'status': _FindMonorailStatus(new_status)
      }
      return status_update
  return None

def LoadPriorityFromMonorailLabels(monorail_labels):
  ''' Load the priority from monorail labels if it is set

  In Monorail, a label like 'Pri-X' is used set the issue's priority to X.
  X can be range from 0 to 4. Currently Monorail scan the labels in order
  and set the priority as it sees any. Thus, the last X will be kept. Here
  we changed the logic to keep the highest level (lowest value.)

  Args:
    monorail_labels: a list of labels in monorail fashion.

  Returns:
    the priority from the label if any, otherwise 2 by default.
  '''
  priority = 99
  for label in monorail_labels:
    if label.startswith('Pri-'):
      label_priority = int(label[4])
      priority = min(priority, label_priority)
  if priority == 99:
    return 2
  return priority

def ReconcileBuganizerIssue(buganizer_issue):
  '''Reconcile a Buganizer issue into the Monorail format

  During the Buganizer migration, we try to minimize the changes on the
  consumers of the issues. Thus, we will reconcile the results into the
  exising Monorail format before returning the results.
  '''
  monorail_issue = {}
  issue_state = buganizer_issue.get('issueState')

  monorail_issue['projectId'] = FindMonorailProject(issue_state['componentId'])

  monorail_issue['id'] = buganizer_issue['issueId']

  buganizer_status = issue_state['status']
  if buganizer_status in ('NEW', 'ASSIGNED', 'ACCEPTED'):
    monorail_issue['state'] = 'open'
  else:
    monorail_issue['state'] = 'closed'

  monorail_issue['status'] = _FindMonorailStatus(buganizer_status)

  monorail_author = {
    'name': issue_state['reporter']
  }
  monorail_issue['author'] = monorail_author

  monorail_issue['summary'] = issue_state['title']

  monorail_issue['owner'] = issue_state.get('assignee', None)

  hotlist_ids = buganizer_issue.get('hotlistIds', [])
  label_names = [_FindMonorailLabel(hotlist_id) for hotlist_id in hotlist_ids]
  monorail_issue['labels'] = [label for label in label_names if label]

  return monorail_issue


import pyglet
from src import Constants, CommonHelpers, Snake
from src.ui import AbstractUI

class NiceUI(AbstractUI.AbstractUI):
    def __init__(self, x, y, width, height, square_size):
        super().__init__(x, y, width, height, square_size, 2)
        self.draw_loading_screen()
        self.game_width = self.square_size * self.num_squares_width
        self.game_height = self.square_size * self.num_squares_height
        self.load_images()
        self.prepare_cover_squares()
        # enable transparency
        pyglet.gl.glEnable(pyglet.gl.GL_BLEND)
        pyglet.gl.glBlendFunc(pyglet.gl.GL_SRC_ALPHA, pyglet.gl.GL_ONE_MINUS_SRC_ALPHA)

    def draw_loading_screen(self):
        CommonHelpers.draw_colored_rectangle(self.screen_x, self.screen_y, self.width, self.height, 65, 51, 101)
        pyglet.text.Label(text="Loading...",
                          x=(self.screen_x + self.width / 2), y=(self.screen_y + self.height / 2),
                          anchor_x="center", anchor_y="center",
                          font_name="Bangers", font_size=60).draw()
        pyglet.gl.glFlush()

    """ IMAGE LOADING """

    def load_images(self):
        self.load_grass()
        self.load_score_background()
        self.load_snake()
        self.load_bush()
        self.load_apple()

    def load_grass(self):
        grass_image = pyglet.resource.image("NiceUI/grass.jpg")
        CommonHelpers.scale_image(grass_image, self.width - 2,
                                  self.height - 2)  # minus few pixels so the boundary is visible
        CommonHelpers.center_image(grass_image)
        self.grass = pyglet.sprite.Sprite(img=grass_image,
                                          x=self.screen_x + (self.width / 2), y=self.screen_y + (self.height / 2))

    def load_score_background(self):
        score_background_image = pyglet.resource.image("NiceUI/score_background.png")
        CommonHelpers.scale_image(score_background_image, self.game_width, self.square_size * self.score_squares)
        CommonHelpers.center_image(score_background_image)
        self.score_background = pyglet.sprite.Sprite(img=score_background_image,
                                                     x=self.game_x + self.game_width / 2,
                                                     y=self.game_y + self.game_height + (score_background_image.height / 2))

    def load_snake(self):
        snake_head_image = pyglet.resource.image("NiceUI/snake_head.png")
        snake_body_image = pyglet.resource.image("NiceUI/snake_body.png")
        snake_corner_image = pyglet.resource.image("NiceUI/snake_corner.png")
        snake_tail_image = pyglet.resource.image("NiceUI/snake_tail.png")
        snake_head_dead_image = pyglet.resource.image("NiceUI/snake_head_dead.png")

        CommonHelpers.scale_image(snake_head_image, self.square_size, self.square_size)
        CommonHelpers.scale_image(snake_body_image, self.square_size, self.square_size)
        CommonHelpers.scale_image(snake_corner_image, self.square_size, self.square_size)
        CommonHelpers.scale_image(snake_tail_image, self.square_size, self.square_size)
        CommonHelpers.scale_image(snake_head_dead_image, self.square_size, self.square_size)

        CommonHelpers.center_image(snake_head_image)
        CommonHelpers.center_image(snake_body_image)
        CommonHelpers.center_image(snake_corner_image)
        CommonHelpers.center_image(snake_tail_image)
        CommonHelpers.center_image(snake_head_dead_image)

        self.snake_head = pyglet.sprite.Sprite(img=snake_head_image)
        self.snake_body = pyglet.sprite.Sprite(img=snake_body_image)
        self.snake_corner = pyglet.sprite.Sprite(img=snake_corner_image)
        self.snake_tail = pyglet.sprite.Sprite(img=snake_tail_image)
        self.snake_head_dead = pyglet.sprite.Sprite(img=snake_head_dead_image)

    def load_bush(self):
        bush_image = pyglet.resource.image("NiceUI/bush2.png")
        CommonHelpers.scale_image(bush_image, self.square_size, self.square_size)
        CommonHelpers.center_image(bush_image)
        self.bush = pyglet.sprite.Sprite(img=bush_image)

    def load_apple(self):
        apple_image = pyglet.resource.image("NiceUI/apple.png")
        CommonHelpers.scale_image(apple_image, self.square_size, self.square_size)
        CommonHelpers.center_image(apple_image)
        self.apple = pyglet.sprite.Sprite(img=apple_image)

    """ OTHER PREPARATIONS """

    def prepare_cover_squares(self):
        grass = pyglet.resource.image("NiceUI/grass.png")
        self.cover_squares = []
        for i in range(self.num_squares_height):
            row = []
            for j in range(self.num_squares_width):
                square = grass.get_region(x=(self.game_x + j * self.square_size),
                                          y=(self.game_y + i * self.square_size),
                                          width=self.square_size, height=self.square_size)
                row.append(pyglet.sprite.Sprite(square,
                                                x=(self.game_x + j * self.square_size),
                                                y=(self.game_y + i * self.square_size)))
            self.cover_squares.append(row)

    """ GETTERS """

    def get_num_squares_height(self):
        return self.num_squares_height

    def get_num_squares_width(self):
        return self.num_squares_width

    """ DRAWING """

    def prepare_game(self, snake):
        self.draw_background()
        self.draw_score(0)
        self.draw_game_field()
        self.draw_boundary()
        self.draw_snake(snake)
        pyglet.gl.glFlush()

    def draw_snake_eat(self, snake: Snake.Snake):
        self.draw_square(snake.head.next_n.x, snake.head.next_n.y)
        self.draw_snake_head(snake.head)
        self.draw_snake_part(snake.head.next_n)
        pyglet.gl.glFlush()

    def draw_snake_move(self, snake: Snake.Snake, prev_tail):
        self.draw_square(prev_tail[0], prev_tail[1])
        self.draw_square(snake.tail.x, snake.tail.y)
        self.draw_square(snake.head.next_n.x, snake.head.next_n.y)
        self.draw_snake_head(snake.head)
        self.draw_snake_part(snake.head.next_n)
        self.draw_snake_tail(snake.tail)
        pyglet.gl.glFlush()

    def draw_apple(self, x, y):
        self.apple.x = self.game_x + x * self.square_size + self.square_size / 2
        self.apple.y = self.game_y + y * self.square_size + self.square_size / 2
        self.apple.draw()
        pyglet.gl.glFlush()

    def draw_snake(self, snake: Snake.Snake):
        for part in snake:
            self.draw_snake_part(part)
        pyglet.gl.glFlush()

    def draw_snake_dead(self, snake: Snake.Snake):
        self.draw_snake_head_dead(snake.head)
        pyglet.gl.glFlush()

    def draw_snake_part(self, part: Snake.ListNode):
        if Snake.is_head(part):
            self.draw_snake_head(part)
        elif Snake.is_tail(part):
            self.draw_snake_tail(part)
        elif Snake.is_body(part):
            self.draw_snake_body(part)
        elif Snake.is_corner(part):
            self.draw_snake_corner(part)
        pyglet.gl.glFlush()

    def draw_snake_head(self, part: Snake.ListNode):
        self.snake_head.x = self.game_x + (part.x * self.square_size) + (self.square_size / 2)
        self.snake_head.y = self.game_y + (part.y * self.square_size) + (self.square_size / 2)
        direction = Snake.heads_direction(part)
        if direction is Constants.UP:
            self.snake_head.rotation = 0
        elif direction is Constants.RIGHT:
            self.snake_head.rotation = 90
        elif direction is Constants.DOWN:
            self.snake_head.rotation = 180
        elif direction is Constants.LEFT:
            self.snake_head.rotation = 270
        self.snake_head.draw()
        pyglet.gl.glFlush()

    def draw_snake_head_dead(self, part: Snake.ListNode):
        self.snake_head_dead.x = self.game_x + (part.x * self.square_size) + (self.square_size / 2)
        self.snake_head_dead.y = self.game_y + (part.y * self.square_size) + (self.square_size / 2)
        direction = Snake.heads_direction(part)
        if direction is Constants.UP:
            self.snake_head_dead.rotation = 0
        elif direction is Constants.RIGHT:
            self.snake_head_dead.rotation = 90
        elif direction is Constants.DOWN:
            self.snake_head_dead.rotation = 180
        elif direction is Constants.LEFT:
            self.snake_head_dead.rotation = 270
        self.snake_head_dead.draw()
        pyglet.gl.glFlush()

    def draw_snake_tail(self, part: Snake.ListNode):
        self.snake_tail.x = self.game_x + (part.x * self.square_size) + (self.square_size / 2)
        self.snake_tail.y = self.game_y + (part.y * self.square_size) + (self.square_size / 2)
        direction = Snake.rest_direction(part)
        if direction is Constants.UP:
            self.snake_tail.rotation = 0
        elif direction is Constants.RIGHT:
            self.snake_tail.rotation = 90
        elif direction is Constants.DOWN:
            self.snake_tail.rotation = 180
        elif direction is Constants.LEFT:
            self.snake_tail.rotation = 270
        self.snake_tail.draw()
        pyglet.gl.glFlush()

    def draw_snake_body(self, part: Snake.ListNode):
        self.snake_body.x = self.game_x + (part.x * self.square_size) + (self.square_size / 2)
        self.snake_body.y = self.game_y + (part.y * self.square_size) + (self.square_size / 2)
        direction = Snake.heads_direction(part)
        if direction is Constants.UP or direction is Constants.DOWN:
            self.snake_body.rotation = 0
        elif direction is Constants.LEFT or direction is Constants.RIGHT:
            self.snake_body.rotation = 90
        self.snake_body.draw()
        pyglet.gl.glFlush()

    def draw_snake_corner(self, part: Snake.ListNode):
        self.snake_corner.x = self.game_x + (part.x * self.square_size) + (self.square_size / 2)
        self.snake_corner.y = self.game_y + (part.y * self.square_size) + (self.square_size / 2)
        direction = Snake.corner_type(part)
        if direction[0] is Constants.UP and direction[1] is Constants.RIGHT:
            self.snake_corner.rotation = 270
        elif direction[0] is Constants.UP and direction[1] is Constants.LEFT:
            self.snake_corner.rotation = 180
        elif direction[0] is Constants.DOWN and direction[1] is Constants.RIGHT:
            self.snake_corner.rotation = 0
        elif direction[0] is Constants.DOWN and direction[1] is Constants.LEFT:
            self.snake_corner.rotation = 90
        self.snake_corner.draw()
        pyglet.gl.glFlush()

    def draw_game_field(self):
        for i in range(1, self.num_squares_height - 1):
            for j in range(1, self.num_squares_width - 1):
                self.draw_square(j, i)
        self.draw_bushes()
        pyglet.gl.glFlush()

    def draw_square(self, x, y):
        self.cover_squares[y][x].draw()
        x1 = self.game_x + (x * self.square_size)
        y1 = self.game_y + (y * self.square_size)
        x2 = x1
        y2 = y1 + self.square_size
        x3 = x1 + self.square_size
        y3 = y1 + self.square_size
        x4 = x1 + self.square_size
        y4 = y1
        if y % 2:
            if x % 2:
                pyglet.graphics.draw(4, pyglet.gl.GL_QUADS,
                                     ("v2f", (x1, y1, x2, y2, x3, y3, x4, y4)),
                                     ("c4B", ((11, 102, 35, 110) * 4)))
            else:
                pyglet.graphics.draw(4, pyglet.gl.GL_QUADS,
                                     ("v2f", (x1, y1, x2, y2, x3, y3, x4, y4)),
                                     ("c4B", ((137, 173, 111, 80) * 4)))
        else:
            if x % 2:
                pyglet.graphics.draw(4, pyglet.gl.GL_QUADS,
                                     ("v2f", (x1, y1, x2, y2, x3, y3, x4, y4)),
                                     ("c4B", ((137, 173, 111, 80) * 4)))
            else:
                pyglet.graphics.draw(4, pyglet.gl.GL_QUADS,
                                     ("v2f", (x1, y1, x2, y2, x3, y3, x4, y4)),
                                     ("c4B", ((11, 102, 35, 110) * 4)))
        pyglet.gl.glFlush()

    def draw_bushes(self):
        for i in range(self.num_squares_width):
            self.bush.x = self.game_x + i * self.square_size + self.square_size / 2
            self.bush.y = self.game_y + self.square_size / 2
            self.bush.draw()
            self.bush.y = self.game_y + (self.num_squares_height - 1) * self.square_size + self.square_size / 2
            self.bush.draw()
        for i in range(self.num_squares_height):
            self.bush.x = self.game_x + self.square_size / 2
            self.bush.y = self.game_y + i * self.square_size + self.square_size / 2
            self.bush.draw()
            self.bush.x = self.game_x + (self.num_squares_width - 1) * self.square_size + self.square_size / 2
            self.bush.draw()
        pyglet.gl.glFlush()

    def draw_score(self, score):
        self.score_background.draw()
        pyglet.text.Label(text=f"Score: {score}", x=self.score_background.x, y=self.score_background.y,
                          anchor_x="center", anchor_y="center",
                          font_name="Bangers", font_size=self.score_background.height // 3
                          ).draw()
        pyglet.gl.glFlush()

    def draw_background(self):
        self.grass.draw()
        pyglet.gl.glFlush()

    """ DRAW GAME STATES """

    def redraw(self, snake: Snake.Snake, score, identification, num_runs, apple):
        self.draw_snake(snake)
        self.draw_score(score)
        self.draw_apple(apple[0], apple[1])

    def draw_game_over(self):
        pyglet.text.Label(text="GAME OVER",
                          x=(self.game_x + self.game_width / 2),
                          y=(self.game_y + ((self.game_height / 3) * 2)),
                          anchor_x="center", anchor_y="center",
                          font_name="Bangers", font_size=self.game_height // 10).draw()
        pyglet.text.Label(text="[click to go back to menu]",
                          x=(self.game_x + self.game_width / 2),
                          y=(self.game_y + self.game_height / 3),
                          anchor_x="center", anchor_y="center",
                          font_name="Bangers", font_size=self.game_height // 20).draw()
        pyglet.gl.glFlush()

    def draw_game_won(self):
        pyglet.text.Label(text="YOU WIN! CONGRATULATION!",
                          x=(self.game_x + self.game_width / 2),
                          y=(self.game_y + ((self.game_height / 3) * 2)),
                          anchor_x="center", anchor_y="center",
                          font_name="Bangers", font_size=self.game_height // 10).draw()
        pyglet.text.Label(text="[click to go back to menu]",
                          x=(self.game_x + self.game_width / 2),
                          y=(self.game_y + self.game_height / 3),
                          anchor_x="center", anchor_y="center",
                          font_name="Bangers", font_size=self.game_height // 20).draw()
        pyglet.gl.glFlush()

    def draw_boundary(self):
        pyglet.graphics.draw(8, pyglet.gl.GL_LINES,
                             ("v2f", (self.screen_x, self.screen_y,
                                      self.screen_x, self.screen_y + self.height,
                                      self.screen_x, self.screen_y + self.height,
                                      self.screen_x + self.width, self.screen_y + self.height,
                                      self.screen_x + self.width, self.screen_y + self.height,
                                      self.screen_x + self.width, self.screen_y,
                                      self.screen_x + self.width, self.screen_y,
                                      self.screen_x, self.screen_y)))


import json
import os
import pandas as pd
from services.db_service import engine
from dotenv import load_dotenv
from services.json_unpack_service import UnPackObj

file_path = os.path.join(os.path.dirname(__file__), "..")
load_dotenv(os.path.join(file_path, ".env"))

def _get_marketfact_exclusion(crop, spatial_hiearchy_type, spatial_hiearchy_value):
    exclude_definition_id = str(os.getenv("EXCLUDE_DEFINITION_ID", ""))
    if crop:
        crop_filter = f"and crop = '{crop}'"
    else:
        crop_filter = ""
    sql = f"select grid_id, product_dimension from datafactory.market_facts where definition_id = '{exclude_definition_id}' " \
           f"{crop_filter} and {spatial_hiearchy_type} = '{spatial_hiearchy_value}'"
    return pd.read_sql(sql, engine)

def _filter_productdimension(market_facts, product_dimension):
    market_facts_json = market_facts.to_json(orient="records")
    market_facts_data = json.loads(market_facts_json)
    market_facts_irrigation_types = []
    irrigation_types = []
    for fact in market_facts_data:
        if not isinstance(fact['product_dimension'], str):
            for dim in product_dimension:
                if dim['type'] in fact['product_dimension'].values():
                    prod_dim = UnPackObj().extract_obj(product_dimension).get(dim['type'], None)
                    if isinstance(prod_dim, list):
                        irrigation_types.extend(prod_dim)
                    else:
                        irrigation_types.append(prod_dim)
                    market_facts_irrigation_types.append(fact['product_dimension'].get('value',None))
        else:
            return True

    if set(irrigation_types) == set(market_facts_irrigation_types):
        return True

def exclude(gridcell, crop, product_dimension):
    # Check adminlevel1
    market_facts_level_1 = _get_marketfact_exclusion(crop, "adminlevel1_code", gridcell['country_code'])
    if not market_facts_level_1.empty and _filter_productdimension(market_facts_level_1, product_dimension):
        return True

    # Check grid_id
    market_facts_level_2 = _get_marketfact_exclusion(None, "grid_id", gridcell['grid_id'])
    if not market_facts_level_2.empty and _filter_productdimension(market_facts_level_2, product_dimension):
        return True
    else:
        return False


import cv2
import numpy as np
import math

def compute_hsv_white_yellow_binary(img):
    """
    Returns a binary thresholded image produced retaining only white and yellow elements on the picture
    The provided image should be in RGB format
    """
    rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    hsv_img = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2HSV)

    # Compute a binary thresholded image where yellow is isolated from HLS components
    img_hls_yellow_bin = np.zeros_like(hsv_img[:, :, 0])
    img_hls_yellow_bin[((hsv_img[:, :, 0] >= 40 / 360 * 255) & (hsv_img[:, :, 0] <= 50 / 360 * 255))
                       & ((hsv_img[:, :, 1] >= 80 / 100 * 255) & (hsv_img[:, :, 1] <= 255))
                       & ((hsv_img[:, :, 2] >= 90 / 100 * 255) & (hsv_img[:, :, 2] <= 255))
                       ] = 1

    # Compute a binary thresholded image where white is isolated from HLS components
    img_hls_white_bin = np.zeros_like(hsv_img[:, :, 0])
    img_hls_white_bin[((hsv_img[:, :, 0] >= 0) & (hsv_img[:, :, 0] <= 255))
                      & ((hsv_img[:, :, 1] >= 0) & (hsv_img[:, :, 1] <= 25 / 100 * 255))
                      & ((hsv_img[:, :, 2] >= 90 / 100 * 255) & (hsv_img[:, :, 2] <= 255))
                      ] = 1

    # Now combine both
    img_hls_white_yellow_bin = np.zeros_like(hsv_img[:, :, 0])
    img_hls_white_yellow_bin[(img_hls_yellow_bin == 1) | (img_hls_white_bin == 1)] = 1
    img_hls_white_yellow_bin = cv2.normalize(src=img_hls_white_yellow_bin, dst=None, alpha=0, beta=255,
                                             norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)
    return img_hls_white_yellow_bin

def compute_hsv_white_binary(img):
    """
    Returns a binary thresholded image produced retaining only white and yellow elements on the picture
    The provided image should be in RGB format
    """
    rgb_img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    hsv_img = cv2.cvtColor(rgb_img, cv2.COLOR_RGB2HSV)

    # Compute a binary thresholded image where white is isolated from HLS components
    img_hls_white_bin = np.zeros_like(hsv_img[:, :, 0])
    img_hls_white_bin[((hsv_img[:, :, 0] >= 0) & (hsv_img[:, :, 0] <= 255))
                      & ((hsv_img[:, :, 1] >= 0) & (hsv_img[:, :, 1] <= 10 / 100 * 255))
                      & ((hsv_img[:, :, 2] >= 50 / 100 * 255) & (hsv_img[:, :, 2] <= 255))
                      ] = 1

    img_hls_white_bin = cv2.normalize(src=img_hls_white_bin, dst=None, alpha=0, beta=255,
                                      norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)
    return img_hls_white_bin

def apply_mask(img, partition):
    h, w = img.shape[:2]
    mask = np.zeros(img.shape)
    mask[int(h * partition):, :] = 1
    masked = mask * img / 255
    masked = cv2.normalize(src=masked, dst=None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)
    return masked

def line_groups(lines):
    lines2 = []
    for line in lines:
        for rho, theta in line:
            lines2.append(tuple((rho, theta)))
    lines2.sort()

    sorted_lines = [[]]
    group = 0
    hor_deg_thresh = 20
    ver_deg_thresh = 20
    group_thresh = 100

    can_continue = False
    lines = lines2
    i = 0
    while not can_continue:
        if (np.deg2rad(90 + hor_deg_thresh) < lines[i][1] < np.deg2rad(180 + ver_deg_thresh)) or (np.deg2rad(ver_deg_thresh) < lines[i][1] < np.deg2rad(90 - hor_deg_thresh)):
            sorted_lines[group].append([lines[i]])
            can_continue = True
            prev_line = lines[i]
        i += 1
        if i == len(lines):
            return sorted_lines

    for line in lines[i+1:]:
        if (np.deg2rad(90 + hor_deg_thresh) < line[1] < np.deg2rad(180 + ver_deg_thresh)) or (
                np.deg2rad(ver_deg_thresh) < line[1] < np.deg2rad(90 - hor_deg_thresh)):
            if np.abs(line[0] - prev_line[0]) < group_thresh:
                sorted_lines[group].append([line])
            else:
                group += 1
                sorted_lines.append([])
                sorted_lines[group].append([line])
            prev_line = line

    # if lines[0, 0, 1] > np.deg2rad(90 + deg_thresh) or lines[0, 0, 1] < np.deg2rad(90 - deg_thresh):
    #     sorted_lines[group].append([lines[0, 0]])
    #
    # if lines.shape[0] > 1:
    #     for i in range(1, lines.shape[0]):
    #         if lines[i, 0, 1] > np.deg2rad(90 + deg_thresh) or lines[i, 0, 1] < np.deg2rad(90 - deg_thresh):
    #             if np.abs(lines[i, 0, 0] - lines[i - 1, 0, 0]) < group_thresh:
    #                 sorted_lines[group].append([lines[i, 0]])
    #             else:
    #                 group += 1
    #                 sorted_lines.append([])
    #                 sorted_lines[group].append([lines[i, 0]])
    return sorted_lines

def best_two_avg_lines(avg_lines):
    if len(avg_lines[0]) < 2:
        best_two = [[300,0], [300,0]]
    else:
        min_diff1 = 100
        min_diff2 = 200
        best_line1 = avg_lines[0][0]
        best_line2 = avg_lines[0][0]
        for line in avg_lines[0]:
            theta_diff_from_vertical = min(line[1], np.pi - line[1])
            if theta_diff_from_vertical < min_diff1:
                min_diff1 = theta_diff_from_vertical
                best_line1 = line
            elif theta_diff_from_vertical < min_diff2:
                min_diff2 = theta_diff_from_vertical
                best_line2 = line
        best_two = [best_line1, best_line2]
        best_two.sort(key=lambda x:x[0])
    return best_two

def avg_of_line_groups(lines):
    sorted_lines = line_groups(lines)
    avg_lines = [[]]
    for line_group in sorted_lines:
        if len(line_group) != 0:
            rho_sum = 0
            theta_sin_sum = 0
            theta_cos_sum = 0
            theta_sum = 0
            for line in line_group:
                rho_sum += line[0][0]
                theta_sin_sum += np.sin(line[0][1])
                theta_cos_sum += np.cos(line[0][1])
                theta_sum += line[0][1]
            rho_avg = rho_sum / len(line_group)
            n = len(line_group)
            # rho_avg = line_group[n // 2][0]
            line_group.sort(key=lambda x:x[0][1])
            theta_median = line_group[n // 2][0][1]
            # theta_avg = math.atan2((1 / n) * theta_sin_sum, (1 / n) * theta_cos_sum)
            # theta_avg = theta_sum / len(line_group)
            avg_lines[0].append([rho_avg, theta_median])

    return avg_lines, best_two_avg_lines(avg_lines)

def draw_lines(img, lines, color=(0,0,255)):
    img_lines = img.copy()
    if lines != [[]]:
        points = []
        for line in lines:
            a = np.cos(line[1])
            b = np.sin(line[1])
            x0 = a * line[0]
            y0 = b * line[0]
            x1 = int(x0 + 4000 * (-b))
            y1 = int(y0 + 4000 * (a))
            x2 = int(x0 - 4000 * (-b))
            y2 = int(y0 - 4000 * (a))
            m = (y2 - y1) / (x2 - x1 + 0.001)
            b = y1 - (m * x1)
            points.append((m, b))
            cv2.line(img_lines, (x1, y1), (x2, y2), color, 2)
    return img_lines

def draw_lane_rect(img, best_two):
    img_rect = img.copy()
    points = []
    for line in best_two:
        a = np.cos(line[1])
        b = np.sin(line[1])
        x0 = a * line[0]
        y0 = b * line[0]
        x1 = int(x0 + 1000 * (-b))
        y1 = int(y0 + 1000 * (a))
        x2 = int(x0 - 4000 * (-b))
        y2 = int(y0 - 4000 * (a))
        m = (y2 - y1) / (x2 - x1 + 0.000000001)
        b = y1 - (m * x1)
        points.append((m, b))
        # cv2.line(img_rect, (x1, y1), (x2, y2), (0, 0, 255), 2)

    p1 = [int((img.shape[0] - points[0][1]) // points[0][0]), int(img.shape[0])]
    p2 = [int((img.shape[0] - points[1][1]) // points[1][0]), int(img.shape[0])]
    xc = int((points[1][1] - points[0][1]) // (points[0][0] - points[1][0] +0.000001))
    yc = int(points[0][0] * xc + points[0][1])
    p3 = [xc, yc]
    pts = np.array([p1, p2, p3])
    road_mask = img_rect.copy()
    cv2.drawContours(road_mask, [pts], 0, (0, 255, 0, 100), -1)
    road_mask[0:yc+int(img.shape[0]*0.05), :] = img[0:yc+int(img.shape[0]*0.05), :]
    img_rect = img_rect*0.8 + road_mask*0.2
    img_rect = cv2.normalize(src=img_rect, dst=None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)
    return img_rect

def LaneDetection(img, prev_best_two=None):
    # img = cv2.GaussianBlur(img, (5,5), -1)
    hsv = compute_hsv_white_binary(img)
    edges = cv2.Canny(hsv, 50, 200, apertureSize=3)
    mask = cv2.imread('img/mask4.png',0)
    mask = cv2.resize(mask, (img.shape[1], img.shape[0]))
    edges = (mask / 255) * edges
    edges = cv2.normalize(src=edges, dst=None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)
    lines = cv2.HoughLines(edges, 1, np.pi / 180, 50)
    if lines is None:
        return img, None
    avg_lines, best_two = avg_of_line_groups(lines)
    prob = 0.5
    if prev_best_two is not None:
        diff_best_two = 0
        for i in range(2):
            diff_best_two += np.abs(prev_best_two[i][0] - best_two[i][0])
        if diff_best_two > 100:
            best_two = prev_best_two
        else:
            for i in range(2):
                best_two[i] = [best_two[i][0] * (1-prob) + prev_best_two[i][0] * prob, best_two[i][1] * (1-prob) + prev_best_two[i][1] * prob]
    img_lines = draw_lines(img, best_two)
    img_rect = draw_lane_rect(img, best_two)
    # mask = cv2.imread('img/mask4.png')
    # mask = cv2.resize(mask, (img.shape[1], img.shape[0]))
    # img_rect = img_rect * mask
    return img_rect, best_two

# def create_mask(img):
#

if __name__ == '__main__':
    cap = cv2.VideoCapture('vids/test4.mp4')
    # out = cv2.VideoWriter('project.mp4', cv2.VideoWriter_fourcc(*'MP42'), 15, (1280, 720))

    best_two = None
    while (cap.isOpened()):
        ret, frame = cap.read()
        if ret is True:
            if best_two is None:
                frame, best_two = LaneDetection(frame)
            else:
                frame, best_two = LaneDetection(frame, best_two)

            # mask = cv2.imread('img/mask2.png')
            # mask = cv2.resize(mask, (frame.shape[1], frame.shape[0]))
            # frame = (mask / 255) * frame
            # frame = cv2.normalize(src=frame, dst=None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8UC1)

            cv2.imshow('frame', frame)
            # out.write(frame)

            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
        else:
            cap.release()
            # out.release()

    cv2.destroyAllWindows()


from django import forms
from django.forms import ModelForm, Textarea
from tally_ho.apps.tally.models.center import Center

disable_copy_input = {
    'onCopy': 'return false;',
    'onDrag': 'return false;',
    'onDrop': 'return false;',
    'onPaste': 'return false;',
    'autocomplete': 'off',
    'class': 'form-control'
}

class CreateCenterForm(ModelForm):
    class Meta:
        model = Center
        fields = localized_fields = [
            'center_type',
            'code',
            'latitude',
            'longitude',
            'mahalla',
            'name',
            'tally',
            'office',
            'region',
            'village',
        ]

        widgets = {
            "tally": forms.HiddenInput(),
            'mahalla': Textarea(attrs={'cols': 60, 'rows': 2}),
            'name': Textarea(attrs={'cols': 60, 'rows': 2}),
            'region': Textarea(attrs={'cols': 60, 'rows': 2}),
            'village': Textarea(attrs={'cols': 60, 'rows': 2}),
        }


import argparse
import logging
import sys
from pathlib import Path
from typing import Any, List, Optional, Sequence, Union

import pydicom

from dcmannotate import readers, serialization

from dcmannotate.annotations import AnnotationsParsed
from dcmannotate.serialization import AnnotationEncoder
from . import DicomVolume
from .utils import annotation_format

log = logging.getLogger(f"{__package__}.{__name__}")

def log_config() -> logging.Logger:
    log.setLevel(logging.INFO)
    FORMAT = "%(levelname)s: %(message)s"
    formatter = logging.Formatter(FORMAT)
    # 2 handlers for the same logger:
    h1 = logging.StreamHandler(sys.stdout)
    h1.setLevel(logging.DEBUG)
    # filter out everything that is above INFO level (WARN, ERROR, ...)
    h1.addFilter(lambda record: record.levelno <= logging.INFO)
    h1.setFormatter(formatter)
    log.addHandler(h1)
    h2 = logging.StreamHandler(sys.stderr)
    # take only warnings and error logs
    h2.setLevel(logging.WARNING)
    h2.setFormatter(formatter)
    log.addHandler(h2)
    return log

def maybe_glob(path_list: List[Path]) -> List[Path]:
    """Takes a list of Paths and returns a list of Paths.

    If handed a list with length one, expands it with Path.glob. Otherwise it returns it unchanged."""
    if len(path_list) != 1:
        for p in path_list:
            if not p.exists():
                log.fatal(f"File '{p}' not found.")
                exit(1)
        return path_list

    path = path_list[0]
    if str(path)[0] == "/":  # Janky support for globbing absolute paths
        result = list(Path("/").glob(str(path.relative_to("/"))))
    else:
        result = list(Path(".").glob(str(path)))
    if not result:
        log.fatal(f'Pattern "{str(path)}" did not match any files.')
        exit(1)
    return result

def write(args: Any) -> List[Path]:
    if not args.volume_files:
        log.fatal("No input volume files provided.")
        exit(1)

    in_files = maybe_glob(args.volume_files)
    volume = DicomVolume(in_files)

    if not args.annotations:
        annotations = "\n".join(sys.stdin.readlines())
    else:
        annotations = args.annotations
    annotation_set = serialization.read_annotations_from_json(volume, annotations)
    volume.annotate_with(annotation_set)

    try:
        if args.format == "sc":
            result_files = volume.write_sc(args.destination, force=args.force)
        elif args.format == "sr":
            result_files = volume.write_sr(args.destination, force=args.force)
        elif args.format == "visage":
            result_files = [volume.write_visage(args.destination, force=args.force)]
        elif args.format == "png":
            result_files = volume.write_png(args.destination, force=args.force)
        else:
            log.error(f"Unsupported format {args.format}")
            exit(1)
    except FileExistsError as e:
        log.error(e.args[0] + " Use --force to overwrite existing files.")
        exit(1)
    log.info(f"Wrote {len(result_files)} files.")
    return result_files

def read(args: Any) -> str:
    if not args.annotation_files:
        log.fatal("No annotation files provided.")
        exit(1)
    in_files = maybe_glob(args.annotation_files)
    datasets = [pydicom.dcmread(f) for f in in_files]

    format = annotation_format(datasets)

    if format is None:
        log.fatal("Unable to detect annotation format. This may not be a dcmannotate file.")
        exit(1)

    annotations: Any = []

    if format == "sr":
        for d in datasets:
            measurements, sop_id = readers.sr.get_measurements(d)
            annotations.append(AnnotationsParsed(measurements, sop_id))

    elif format == "sc":
        for d in datasets:
            a = readers.sc.get_measurements(d)
            if a:
                annotations.append(a)
    elif format == "visage":
        if not args.volume_files:
            log.fatal(
                "Input appears to be a Visage PR. For these files, you must pass the original volume with -v"
            )
            exit(1)
        in_volume = DicomVolume(maybe_glob(args.volume_files))
        annotations = readers.visage.read_annotations(in_volume, in_files[0])
    k = AnnotationEncoder()
    result = k.encode(annotations)
    print(result)
    return result

def make_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        "dcmannotate",
        description="Command-line interface to dcmannotate.",
        epilog="""Examples:
        dcmannotate read -i ./slice_sr.*.dcm
        dcmannotate read -i visage_pr.dcm -v slice.[0-9].dcm
        """,
        formatter_class=argparse.RawTextHelpFormatter,
    )

    parser.set_defaults(func=lambda x: log.info(parser.format_help()))

    subparsers = parser.add_subparsers()

    write_parser = subparsers.add_parser("write", help="Write dicom annotations.")
    write_parser.add_argument(
        "format",
        choices=["sr", "sc", "visage", "png"],
        help="Output format: Structured Report, Secondary Capture, or Visage",
    )
    write_parser.add_argument(
        "-i",
        "--volume-files",
        nargs="+",
        dest="volume_files",
        type=Path,
        help="Input volume paths. Accepts a list or a glob pattern.",
    )
    write_parser.add_argument(
        "-o",
        dest="destination",
        required=True,
        help="Output pattern or path, eg ./output/slice_annot.*.dcm or ./slice_visage.dcm",
    )
    write_parser.add_argument(
        "-a",
        "--annotations",
        nargs="?",
        dest="annotations",
        help="Annotations in JSON format. Omit to read from stdin.",
    )
    write_parser.add_argument(
        "--force",
        dest="force",
        action="store_true",
        help="Overwrite destination if files exist.",
    )
    write_parser.set_defaults(func=write)

    read_parser = subparsers.add_parser("read", help="Read dicom annotations.")
    read_parser.add_argument(
        "-i",
        "--annotation-files",
        nargs="+",
        dest="annotation_files",
        type=Path,
        help="Annotation files to read. Accepts a list or a glob pattern.",
    )
    read_parser.add_argument(
        "-v",
        "--volume-files",
        nargs="+",
        dest="volume_files",
        type=Path,
        help="For Visage only: files corresponding to the referenced dicom volume. Accepts a list or a glob pattern.",
    )

    read_parser.set_defaults(func=read)
    return parser

def parse_and_run(argin: Optional[Sequence[str]] = None) -> Any:
    log_config()
    p = make_parser()
    if argin is not None:
        args = p.parse_args(argin)
    else:
        args = p.parse_args()
    return args.func(args)  # call the default function

def console_entry(argin: Optional[Sequence[str]] = None) -> None:
    parse_and_run(argin)
    return None

if __name__ == "__main__":
    console_entry()


import json
import json
import os
import shutil
import time
from io import BytesIO
from pprint import pprint

import PyPDF2
import pandas as pd
from PIL import Image, ImageDraw, ImageFont
from PyPDF2 import PdfReader, PdfWriter
from PyQt5.QtWidgets import QMessageBox
from loguru import logger
from peewee import fn
from reportlab.lib.pagesizes import A4, A3, landscape
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas

from created_one_pdf import created_pdfs
from db import Article, Orders, Statistic, files_base_postgresql, orders_base_postgresql, remove_russian_letters, \
    push_number
from utils import ProgressBar, df_in_xlsx

pdfmetrics.registerFont(TTFont('Arial', 'arial.ttf'))
Image.MAX_IMAGE_PIXELS = None

def add_header_and_footer_to_pdf(pdf_file, footer_text, A3_flag):
    """Надписи сверху пдф файла и снизу"""
    # Open the original PDF and extract its content
    with open(pdf_file, "rb") as pdf:
        pdf_content = BytesIO(pdf.read())
    if A3_flag:
        with open('Настройки\\Параметры значков_A3.json', 'r') as file:
            config = json.load(file)
        pagesize = A3
        size = 8
    else:
        with open('Настройки\\Параметры значков.json', 'r') as file:
            config = json.load(file)
        pagesize = A4
        size = 10

    x2, y2 = config['pdf down']['x'], config['pdf down']['y']
    # Load pages from the original PDF and add header and footer to each page
    reader = PdfReader(pdf_content)
    writer = PdfWriter()

    for page_num in range(len(reader.pages)):
        try:
            page = reader.pages[page_num]

            # Create a canvas for the page
            packet = BytesIO()
            can = canvas.Canvas(packet, pagesize=pagesize)

            # Add the header text (centered) to the canvas
            can.setFont("Arial", size=size)
            if A3_flag:
                can.drawCentredString(x2, y2, f"{footer_text} - Стр.{page_num + 1}")
            else:
                can.drawCentredString(x2, y2, f"{footer_text} - Стр.{page_num + 1}")
            can.save()
            packet.seek(0)
            new_pdf = PdfReader(packet)
            page.merge_page(new_pdf.pages[0])

            writer.add_page(page)
        except Exception as ex:
            logger.error(ex)
    with open(pdf_file, "wb") as output_pdf:
        writer.write(output_pdf)

def combine_images_to_pdf(input_files, output_pdf, size=None, progress=None, self=None, A3_flag=False):
    """Создание файла с наклейками"""
    x_offset = 20
    y_offset = 20
    big_list_skin = []
    for i in input_files:
        if i.nums_in_folder >= 40:
            big_list_skin.append(i)
    input_files = [i for i in input_files if i not in big_list_skin]
    if A3_flag:
        c = canvas.Canvas(output_pdf, pagesize=landscape(A3), pageCompression=1)
        img_width = (A4[0] - 2 * x_offset) / 3
        img_height = (A4[1] - 2 * y_offset) / 3 - 10

        x_positions = [
            x_offset, x_offset + img_width + 10, x_offset + 2 * (img_width + 10),
                      x_offset + 3 * (img_width + 10), x_offset + 4 * (img_width + 10), x_offset + 5 * (img_width + 10)
        ]
        y_positions = [
            A3[0] - y_offset, A3[0] - y_offset - img_height - 15, A3[0] - y_offset - 2 * (img_height + 15)
        ]

        total_images = len(input_files)
        images_per_page = 18  # Размещаем 18 изображений на одной странице
        num_pages = (total_images + images_per_page - 1) // images_per_page

        for page in range(num_pages):
            start_idx = page * images_per_page
            end_idx = min(start_idx + images_per_page, total_images)
            for i, img in enumerate(input_files[start_idx:end_idx]):
                x = x_positions[i % 6]
                y = y_positions[i // 6]
                c.setFont("Helvetica-Bold", 6)
                c.drawString(x, y + 1, f"#{img.num_on_list}  {img.art}")
                try:
                    logger.success(f"Добавился подложка {img.num_on_list}   {img.art}")
                    progress.update_progress()
                    c.drawImage(img.skin, x - 10, y - img_height - 5, width=img_width, height=img_height)
                except Exception as ex:
                    logger.error(f"Не удалось добавить подложку для {img.art} {ex}")
            c.showPage()

        c.save()
    else:
        c = canvas.Canvas(output_pdf, pagesize=A4)
        img_width = (A4[0] - 2 * x_offset) / 3
        img_height = (A4[1] - 2 * y_offset) / 3 - 5

        x_positions = [x_offset, x_offset + img_width + 5, x_offset + 2 * (img_width + 5)]
        y_positions = [A4[1] - y_offset, A4[1] - y_offset - img_height - 10, A4[1] - y_offset - 2 * (img_height + 10)]

        total_images = len(input_files)
        images_per_page = 9
        num_pages = (total_images + images_per_page - 1) // images_per_page

        for page in range(num_pages):
            start_idx = page * images_per_page
            end_idx = min(start_idx + images_per_page, total_images)
            for i, img in enumerate(input_files[start_idx:end_idx]):
                x = x_positions[i % 3]
                y = y_positions[i // 3]
                c.setFont("Helvetica-Bold", 8)
                c.drawString(x, y + 2, f"#{img.num_on_list}     {img.art}")
                try:
                    logger.success(f"Добавился скин {img.num_on_list}     {img.art}")
                    progress.update_progress()
                    c.drawImage(img.skin, x - 10, y - img_height, width=img_width, height=img_height)
                except Exception as ex:
                    logger.error(f"Не удалось добавить подложку для {img.art} {ex}")
            c.showPage()
        c.save()

    if big_list_skin:
        c = canvas.Canvas(f"Файлы на печать/Большие подложки {size}.pdf", pagesize=A4)
        img_width = 505
        img_height = 674
        for i, img in enumerate(big_list_skin):
            c.setFont("Helvetica-Bold", 8)
            c.drawString(30, 30, f"#{img.num_on_list}     {img.art}")
            try:
                logger.success(f"Добавился скин {img.num_on_list}     {img.art}")
                progress.update_progress()

                c.drawImage(img.skin, 40, 100, width=img_width, height=img_height)
            except Exception as ex:
                logger.error(f"Не удалось добавить подложку для {img.art} {ex}")
            c.showPage()
        c.save()
    add_header_and_footer_to_pdf(output_pdf, self.name_doc, A3_flag=A3_flag)

def write_images_art(image, text1):
    """Нанесения номера на значке"""
    width, height = image.size
    draw = ImageDraw.Draw(image)

    # Calculate the font size based on the image width
    font_size = int(width / 13)
    font = ImageFont.truetype("arial.ttf", font_size)

    # Добавляем надпись в правый верхний угол
    bbox1 = draw.textbbox((0, 0), text1, font=font)
    x1 = width - bbox1[2] - 5
    y1 = 5
    draw.text((x1, y1), text1, font=font, fill="black")

    return image

def write_images_art2(image, text, x, y):
    draw = ImageDraw.Draw(image)
    font = ImageFont.truetype("arial.ttf", 40)

    draw.text((x, y), text, font=font, fill="black")

    return image

def distribute_images(queryset, size, a3_flag, self, count) -> tuple:
    if a3_flag:
        with open('Настройки\\Параметры значков_A3.json', 'r') as file:
            config = json.load(file)
    else:
        with open('Настройки\\Параметры значков.json', 'r') as file:
            config = json.load(file)
    nums = config[f'{str(size)}']['nums']
    list_arts = [[None, i.nums_in_folder, i.images, i.id] for i in queryset]
    list_arts = sorted(list_arts, key=lambda x: x[1], reverse=True)
    # Список для хранения наборов
    sets_of_orders = []

    current_set = []  # Текущий набор
    current_count = 0  # Текущее количество элементов в наборе
    while len(list_arts) > 0:
        for order in list_arts[:]:
            if order[1] > nums:
                image_list = [(i, count) for i in order[2].split(', ')]
                if (current_count + (len(image_list) % nums)) <= nums and ((len(image_list) % nums) != 0):
                    current_set.extend(image_list[-(order[1] % nums):])
                    current_count += len(image_list) % nums
                    full_lists = order[1] // nums
                    for i in range(full_lists):
                        sets_of_orders.append(image_list[nums * i:nums * i + nums])
                    push_number(order[3], count)
                    list_arts.remove(order)
                    count += 1
                elif (order[1] > nums) and current_count == 0:
                    full_lists = order[1] // nums
                    for i in range(full_lists):
                        sets_of_orders.append(image_list[nums * i:nums * i + nums])
                    if order[1] % nums != 0:
                        current_set.extend(image_list[-(order[1] % nums):])
                    push_number(order[3], count)
                    list_arts.remove(order)
                    count += 1
                else:
                    sets_of_orders.append(current_set)
                    current_set = []
                    current_count = 0
                    full_lists = order[1] // nums
                    for i in range(full_lists):
                        sets_of_orders.append(image_list[nums * i:nums * i + nums])

                    if order[1] % nums != 0:
                        current_set.extend(image_list[-(order[1] % nums):])
                        current_count += len(image_list[-(order[1] % nums):])
                    push_number(order[3], count)
                    list_arts.remove(order)
                    count += 1

            if (current_count + order[1]) <= nums:
                current_set.extend([[i, count] for i in order[2].split(', ')])
                current_count += order[1]
                list_arts.remove(order)
                push_number(order[3], count)
                count += 1
                if current_count == nums:
                    sets_of_orders.append(current_set)
                    current_set = []
                    current_count = 0
                    break
            continue
        if current_count != 0:
            sets_of_orders.append(current_set)
        if len(list_arts) == 1:
            sets_of_orders.append([[i, count] for i in list_arts[0][2].split(', ')])
            push_number(list_arts[0][3], count)
            list_arts.remove(list_arts[0])
            count += 1

        if list_arts:
            current_set = []
            current_set.extend([[i, count] for i in list_arts[0][2].split(', ')])
            current_count = list_arts[0][1]
            push_number(list_arts[0][3], count)
            list_arts.remove(list_arts[0])
            count += 1

    logger.info(f'Сумма значков: {sum([len(i) for i in sets_of_orders])}')
    # logger.info(f'Сумма значков на листах: {set([len(i) for i in sets_of_orders])}')
    logger.info(f'Количество листов: {len(sets_of_orders)}')
    if self:
        self.list_on_print += len(sets_of_orders)
    return sets_of_orders, count

def create_contact_sheet(images=None, size=None, self=None, A3_flag=False, popsocket=False):
    border_color = (0, 0, 0, 255)  # Черный цвет рамки
    border_width = 1  # Ширина рамки в пикселях
    ready_path = 'Файлы на печать'
    if A3_flag:
        a4_width = 3508
        a4_height = 4961
        with open('Настройки\\Параметры значков_A3.json', 'r') as file:
            config = json.load(file)
    else:
        a4_width = 2480
        a4_height = 3508
        with open('Настройки\\Параметры значков.json', 'r') as file:
            config = json.load(file)
    image_width_mm = config[f'{str(size)}']['diameter']
    image_height_mm = config[f'{str(size)}']['diameter']

    # Convert mm to inches (1 inch = 25.4 mm)
    mm_to_inch = 25.4
    image_width = int(image_width_mm * 300 / mm_to_inch)
    image_height = int(image_height_mm * 300 / mm_to_inch)

    if self:
        self.progress_label.setText(f"Прогресс: Создание изображений {size} mm.")
        self.progress_bar.setValue(0)
        progress = ProgressBar(len(images), self)
    for index, img in enumerate(images, start=1):
        try:
            contact_sheet = Image.new('RGBA', (a4_width, a4_height), (255, 255, 255, 0))
            draw = ImageDraw.Draw(contact_sheet)

            for i in range(config[f'{str(size)}']['ICONS_PER_COL']):
                for j in range(config[f'{str(size)}']['ICONS_PER_ROW']):
                    try:
                        image = Image.open(img[i * config[f'{str(size)}']['ICONS_PER_ROW'] + j][0].strip())
                        image = write_images_art(image,
                                                 f'#{img[i * config[f"{str(size)}"]["ICONS_PER_ROW"] + j][1]}')
                        image = image.resize((image_width, image_height), Image.LANCZOS)
                    except Exception as ex:
                        break
                    try:
                        if size == 56:
                            contact_sheet.paste(image, (j * image_width - 10, i * image_height + 10 * (i + 1)))
                            border_rect = [j * image_width - 10, i * image_height + 10 * (i + 1),
                                           (j + 1) * image_width - 10, (i + 1) * image_height + 10 * (i + 1)]
                        elif size == 25 or size == 44:
                            contact_sheet.paste(image, (j * image_width + 100, i * image_height + 10 * (i + 1)))
                            border_rect = [j * image_width + 100, i * image_height + 10 * (i + 1),
                                           (j + 1) * image_width + 100, (i + 1) * image_height + 10 * (i + 1)]
                        else:
                            contact_sheet.paste(image, (j * image_width + 10, i * image_height + 10 * (i + 1)))
                            border_rect = [j * image_width + 10, i * image_height + 10 * (i + 1),
                                           (j + 1) * image_width + 10, (i + 1) * image_height + 10 * (i + 1)]
                    except Exception as ex:
                        break
                    try:
                        circle_center = ((border_rect[0] + border_rect[2]) // 2, (border_rect[1] + border_rect[3]) // 2)
                        circle_radius = min((border_rect[2] - border_rect[0]) // 2,
                                            (border_rect[3] - border_rect[1]) // 2)
                        draw.ellipse((circle_center[0] - circle_radius, circle_center[1] - circle_radius,
                                      circle_center[0] + circle_radius, circle_center[1] + circle_radius),
                                     outline=border_color, width=border_width)
                    except Exception as ex:
                        break
            if self:
                progress.update_progress()
            if not popsocket:
                path_ready = f'{ready_path}/{size}/{index}.png'
            else:
                path_ready = f'{ready_path}/Popsockets/{index}.png'
            x = config['number on badge']['x'] - 40
            y = config['number on badge']['y']
            image = write_images_art2(contact_sheet, f"{self.name_doc} Стр.{index}", x, y)
            image.save(path_ready)
            logger.success(f'Создано изображение {index}.png')

        except Exception as ex:
            logger.error(ex)
            logger.error(img)

def merge_pdfs_stickers(queryset, output_path):
    pdf_writer = PyPDF2.PdfWriter()
    input_paths = [i.sticker for i in queryset if i.sticker]
    if not input_paths:
        return
    for index, input_path in enumerate(input_paths, start=1):
        try:
            with open(input_path, 'rb') as pdf_file:
                pdf_reader = PyPDF2.PdfReader(pdf_file)

                # Add all pages from PdfReader to PdfWriter
                for page in pdf_reader.pages:
                    pdf_writer.add_page(page)
        except Exception:
            pass
    current_output_path = f"{output_path}.pdf"
    with open(current_output_path, 'wb') as output_file:
        pdf_writer.write(output_file)
    PyPDF2.PdfWriter()

def created_good_images(all_arts, self, A3_flag=False):
    progress = None

    try:
        ready_path = 'Файлы на печать'
        Orders.drop_table()
        if not Orders.table_exists():
            Orders.create_table(safe=True)
        bad_arts_stickers = []
        try:
            shutil.rmtree(ready_path, ignore_errors=True)
            time.sleep(1)
        except:
            pass
        os.makedirs(f'{ready_path}\\25', exist_ok=True)
        os.makedirs(f'{ready_path}\\37', exist_ok=True)
        os.makedirs(f'{ready_path}\\44', exist_ok=True)
        os.makedirs(f'{ready_path}\\56', exist_ok=True)
        os.makedirs(f'{ready_path}\\Popsockets', exist_ok=True)

        for art in all_arts:
            art_clean = remove_russian_letters(art.art.upper())
            row = Article.get_or_none(fn.UPPER(Article.art) == art_clean)
            if row:
                data = [{'art': row.art,
                         'folder': row.folder,
                         'nums': row.nums,
                         'nums_in_folder': row.nums_in_folder,
                         'size': row.size,
                         'skin': row.skin,
                         'sticker': row.sticker,
                         'images': row.images,
                         'shop': row.shop,
                         }
                        ] * art.count
                Orders.bulk_create([Orders(**item) for item in data])

        # Присваивание номера записям Orders
        sorted_orders = Orders.sorted_records()
        for index, row in enumerate(sorted_orders, start=1):
            if not os.path.exists(row.folder):
                logger.error(f'Папка не найдена {row.folder}')
                row.delete_instance()
            if not row.sticker:
                bad_arts_stickers.append((row.art, row.size))

        # Запись ненайденных артикулов и с отсутсвующих стикеров в файл
        if bad_arts_stickers:
            try:
                df_bad_sticker = pd.DataFrame(bad_arts_stickers, columns=['Артикул', 'Размер'])
                df_in_xlsx(df_bad_sticker, 'Не найденные ШК в заказе')
            except Exception as ex:
                logger.error(ex)

        records = Orders.select(Orders.size).where(Orders.shop != 'Popsocket').order_by('-size').distinct()
        records = sorted([i.size for i in records])
        count = 1
        for size in records:
            queryset = Orders.select().where(Orders.size == size).where(Orders.shop != 'Popsocket')

            sets_of_orders, count = distribute_images(queryset, size, A3_flag, self, count)
            if self:
                self.progress_bar.setValue(0)
                self.progress_label.setText(f"Прогресс: Создание подложек {size} mm.")

                progress = ProgressBar(queryset.count(), self)

            try:
                logger.debug(f'Создание наклеек {size}')
                combine_images_to_pdf(queryset.order_by(Orders.num_on_list),
                                      f"{ready_path}/Наклейки {size}.pdf", size, progress, self, A3_flag)
            except Exception as ex:
                logger.error(ex)
                logger.error(f'Не удалось создать файл с наклейками {size}')

            try:
                logger.debug(f'Создание файла ШК {size}')
                merge_pdfs_stickers(queryset.order_by(Orders.num_on_list), f'Файлы на печать\\ШК {size}')
            except Exception as ex:
                logger.error(ex)
                logger.error(f'Не удалось создать файл ШК {size}, возможно не одного не найденно')

            try:
                logger.debug(f'Создание листов со значками {size}')
                create_contact_sheet(sets_of_orders, size, self, A3_flag)
            except Exception as ex:
                logger.error(ex)

        #Popsockets
        queryset = Orders.select().where(Orders.shop == 'Popsocket')
        if len(queryset) > 0:
            size = 44
            sets_of_orders, count = distribute_images(queryset, size, A3_flag, self, count)
            if self:
                self.progress_bar.setValue(0)
                self.progress_label.setText(f"Прогресс: Создание подложек Popsocket")
                progress = ProgressBar(queryset.count(), self)

            try:
                logger.debug(f'Создание наклеек Popsocket {size}')
                combine_images_to_pdf(queryset.order_by(Orders.num_on_list),
                                      f"{ready_path}\\Наклейки Popsockets.pdf", size, progress, self, A3_flag)
            except Exception as ex:
                logger.error(ex)
                logger.error(f'Не удалось создать файл с наклейками {size}')

            try:
                logger.debug(f'Создание файла ШК Popsocket')
                merge_pdfs_stickers(queryset.order_by(Orders.num_on_list), f'Файлы на печать\\ШК Popsocket')
            except Exception as ex:
                logger.error(ex)
                logger.error(f'Не удалось создать файл ШК Popsocket, возможно не одного не найденно')

            try:
                logger.debug(f'Создание листов со значками Popsocket')
                create_contact_sheet(sets_of_orders, size, self, A3_flag, popsocket=True)
            except Exception as ex:
                logger.error(ex)

        if self.create_pdf_checkbox.checkState() == 2:
            try:
                created_pdfs(self)
            except Exception as ex:
                logger.error(ex)

        lists = 0
        try:
            lists = files_base_postgresql(self)
        except Exception as ex:
            logger.error(ex)

        try:
            orders_base_postgresql(self, lists)
        except Exception as ex:
            logger.error(ex)

        self.list_on_print = 0
        QMessageBox.information(self, 'Завершено', 'Создание файлов завершено!')

        records = Orders.select()
        for record in records:
            record.lists = lists
            record.save()
            Statistic.create(art=record.art, nums=record.nums_in_folder, size=record.size)
    except Exception as ex:
        logger.error(ex)

if __name__ == '__main__':
    input_file = r'E:\Очередь на печать\49.png'
    output_file = r'E:\Очередь на печать\!1.png'
    # crop_to_content2(input_file, output_file)


import argparse
import os
import subprocess
import sys
import tempfile

from mopy.config import Config
from mopy.paths import Paths

def target(config):
  target_name = config.target_os + "-" + config.target_cpu
  if config.is_official_build:
    target_name += "-official"
  return target_name

def get_gsutil():
  paths = Paths()
  sys.path.insert(0, os.path.join(paths.src_root, "tools"))
  # pylint: disable=F0401
  import find_depot_tools

  depot_tools_path = find_depot_tools.add_depot_tools_to_path()
  return os.path.join(depot_tools_path, "third_party", "gsutil", "gsutil")

def upload(gsutil_exe, source, dest):
  subprocess.check_call([gsutil_exe, "cp", source, dest])

def write_file_to_gs(gsutil_exe, file_contents, dest):
  with tempfile.NamedTemporaryFile() as temp_version_file:
    temp_version_file.write(file_contents)
    temp_version_file.flush()
    upload(gsutil_exe, temp_version_file.name, dest)

def roll_version(gsutil_exe, config, version):
  service_dir = 'gs://mojo/services/%s/%s' % (target(config), version)
  services = subprocess.check_output(
      [gsutil_exe, 'ls', service_dir]).strip().split('\n')
  for service in services:
    service_binary_name = service.split('/')[-1]
    service_location_file = ("gs://mojo/services/" + target(config) + "/" +
        service_binary_name + "_location")
    service_location_in_gs = service[len('gs://'):]
    write_file_to_gs(gsutil_exe, service_location_in_gs, service_location_file)

def main():
  parser = argparse.ArgumentParser(description="Change the version of the mojo "
      "services on the cdn.")
  parser.add_argument("-v", "--verbose", help="Verbose mode",
      action="store_true")
  parser.add_argument("version",
                      help="New version of the mojo services.")
  args = parser.parse_args()

  gsutil_exe = get_gsutil()

  for target_os in [Config.OS_LINUX, Config.OS_ANDROID]:
    config = Config(target_os=target_os)
    roll_version(gsutil_exe, config, args.version)

  return 0

if __name__ == "__main__":
  sys.exit(main())