# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

import bpy, os, subprocess, colorsys, multiprocessing, sys
from math import pi
from subprocess import PIPE, Popen, STDOUT

nproc = multiprocessing.cpu_count()

class LiVi_c(object):
    def __init__(self, lexport, prev_op):
        self.acc = lexport.scene.livi_calc_acc
        self.scene = bpy.context.scene
        if str(sys.platform) != 'win32':
            self.rm = "rm"
            if lexport.scene.livi_export_time_type == "0" or lexport.scene.livi_anim == "1":
                self.simlistn = ("illumout", "irradout", "dfout")
                self.simlist = (" |  rcalc  -e '$1=47.4*$1+120*$2+11.6*$3' ", " |  rcalc  -e '$1=$1' ", " |  rcalc  -e '$1=(47.4*$1+120*$2+11.6*$3)/100' ")
                self.unit = ("Lux", "W/m^2", "DF %", "Glare")
            else:
                self.simlistn = ("cumillumout", "cumirradout", "", "", "daout")
                self.simlist = (" |  rcalc  -e '$1=47.4*$1+120*$2+11.6*$3' ", " |  rcalc  -e '$1=$1' ")
                self.unit = ("Luxhours", "Wh/m^2", "", "", "DA %")
        if str(sys.platform) == 'win32':
            self.rm = "del"
            if lexport.scene.livi_export_time_type == "0"  or lexport.scene.livi_anim == "1":
                self.simlistn = ("illumout", "irradout", "dfout")
                self.simlist = (' |  rcalc  -e "\$1=47.4*\$1+120*\$2+11.6*\$3" ', " |  rcalc  -e '\$1=\$1' ", " |  rcalc  -e '\$1=(47.4*\$1+120*\$2+11.6*\$3)/100' ")
                self.unit = ("Lux", "W/m^2", "DF %", "Glare")
            else:
                self.simlistn = ("cumillumout", "cumirradout", "", "", "daout")
                self.simlist = (' |  rcalc  -e "\$1=47.4*\$1+120*\$2+11.6*\$3" ', " |  rcalc  -e '\$1=\$1' ")
                self.unit = ("Luxhours", "Wh/m^2", "", "", "DA %")
        try:
            if os.lstat(lexport.filebase+".rtrace").st_size == 0:
                if lexport.metric == "3":
                    self.rad_glare(lexport, prev_op)
                elif prev_op.name == "Radiance Preview":
                    self.rad_prev(lexport, prev_op)
                else:
                    prev_op.report({'ERROR'},"There are no calcsurf materials. Associate a 'calcsurf' material with an object.")
            else:
                if prev_op.name == "Radiance Preview":
                    self.rad_prev(lexport, prev_op)
                elif lexport.metric == "3":
                    self.rad_glare(lexport, prev_op)
                elif lexport.metric == "4":
                    self.dayavail(lexport ,prev_op)
                else:
                    self.rad_calc(lexport, prev_op)
        except:
            pass

    def rad_prev(self, lexport, prev_op):
        if os.path.isfile(lexport.filebase+"-0.poly"):
            cams = [obs for obs in (lexport.scene.objects) if obs.type == 'CAMERA']
            if len(cams) != 0:
                cang = cams[0].data.angle*180/pi
                vv = cang * bpy.context.scene.render.resolution_y/bpy.context.scene.render.resolution_x
                spotmatrix = cams[0].matrix_world
                subprocess.call("rvu -n "+str(nproc)+" -vv "+str(vv)+" -vh "+str(cang)+" -vd "+str(-spotmatrix[0][2])+" "+str(-spotmatrix[1][2])+" "+str(-spotmatrix[2][2])+" -vp "+str(cams[0].location[0])+" "+str(cams[0].location[1])+" "+str(cams[0].location[2])+" "+lexport.pparams(lexport.scene.livi_calc_acc)+" "+lexport.filebase+"-"+str(lexport.scene.frame_current)+".oct &", shell = True)
            else:
                prev_op.report({'ERROR'}, "There is no camera in the scene. Radiance preview will not work")
        else:
            prev_op.report({'ERROR'},"Missing export file. Make sure you have exported the scene.")

    def rad_calc(self, lexport, calc_op):
        lexport.clearscened()
        res = [[] for frame in range(0, bpy.context.scene.frame_end+1)]
        for frame in range(0, bpy.context.scene.frame_end+1):
            if os.path.isfile(lexport.filebase+"-"+str(frame)+".af"):
                subprocess.call(self.rm+" "+lexport.filebase+"-"+str(frame)+".af", shell=True)
            rtcmd = "rtrace -n "+str(nproc)+" -w "+lexport.sparams(self.acc)+" -h -ov -I -af "+lexport.filebase+"-"+str(frame)+".af "+lexport.filebase+"-"+str(frame)+".oct  < "+lexport.filebase+".rtrace "+self.simlist[int(lexport.metric)]+" | tee -a "+lexport.newdir+"/"+self.simlistn[int(lexport.metric)]+"-"+str(frame)+".res"
            rtrun = Popen(rtcmd, shell = True, stdout=PIPE, stderr=STDOUT)
            for line in rtrun.stdout:
               res[frame].append(float(line.rstrip()))
        self.resapply(res, lexport)

    def rad_glare(self, lexport, calc_op):
        scene = bpy.context.scene
        gfiles=[]
        cams = [obs for obs in (scene.objects) if obs.type == 'CAMERA']

        if len(cams) != 0:
            for frame in range(0, scene.frame_end+1):
                spotmatrix = cams[0].matrix_world
                glarecmd = "rpict -vth -vh 180 -vv 180 -x 800 -y 800 -vd "+str(-spotmatrix[0][2])+" "+str(-spotmatrix[1][2])+" "+str(-spotmatrix[2][2])+" -vp "+str(cams[0].location[0])+" "+str(cams[0].location[1])+" "+str(cams[0].location[2])+" "+lexport.sparams(self.acc)+" "+lexport.filename+"-"+str(frame)+".oct | evalglare -c glaretemp"+str(frame)+".hdr"
                glarerun = Popen(glarecmd, shell = True, stdout = PIPE)
                for line in glarerun.stdout:
                    if line.decode().split(",")[0] == 'dgp':
                        glaretext = line.decode().strip(':').replace(',', ' ').split(' ')
                        glaretf = open(lexport.filebase+".glare", "w")
                        glaretf.write('%02d' % lexport.simtimes[frame].day+"/"+'%02d' % lexport.simtimes[frame].month+" "+'%02d' % lexport.simtimes[frame].hour+":"+'%02d' % lexport.simtimes[frame].minute+"\n"+glaretext[0]+": "+str(round(float(glaretext[6]),3))+"\n"+glaretext[1]+": "+str(round(float(glaretext[7]),3))+"\n"+glaretext[2]+": "+str(round(float(glaretext[8]),3))+"\n"+glaretext[3]+": "+str(round(float(glaretext[9]),3))+"\n"+glaretext[4]+": "+str(round(float(glaretext[10]),3))+"\n"+glaretext[5]+": "+str(round(float(glaretext[11]),3)))
                        glaretf.close()
                        subprocess.call("cat "+lexport.filename+".glare | psign -h 32 -cb 0 0 0 -cf 40 40 40 | pcompos glaretemp"+str(frame)+".hdr 0 0 - 800 550 > glare"+str(frame)+".hdr", shell=True)
                        subprocess.call("rm glaretemp"+str(frame)+".hdr", shell=True)

                gfile={"name":"glare"+str(frame)+".hdr"}
                gfiles.append(gfile)

            bpy.ops.sequencer.image_strip_add( directory = lexport.newdir, \
                files = gfiles, \
                frame_start=0, \
                channel=2, \
                filemode=9)
        else:
            calc_op.report({'ERROR'}, "There is no camera in the scene. Create one for glare analysis")
        lexport.scene.livi_display_panel = 0

    def dayavail(self, lexport, calc_op):
        res = [[0] * lexport.reslen for frame in range(0, bpy.context.scene.frame_end+1)]
        vecvals = []
        wd = (7, 5)[int(lexport.scene.livi_calc_da_weekdays)]
        if os.path.splitext(os.path.basename(lexport.scene.livi_export_epw_name))[1] == ".hdr":
            skyrad = open(lexport.filebase+".whitesky", "w")
            skyrad.write("void glow sky_glow \n0 \n0 \n4 1 1 1 0 \nsky_glow source sky \n0 \n0 \n4 0 0 1 180 \nvoid glow ground_glow \n0 \n0 \n4 1 1 1 0 \nground_glow source ground \n0 \n0 \n4 0 0 -1 180\n\n")
            skyrad.close()
            vecfile = open(lexport.scene.livi_calc_vec_name, "r")
        else:
            vecfile = open(lexport.newdir+"/"+os.path.splitext(os.path.basename(lexport.scene.livi_export_epw_name))[0]+".vec", "r")
        for li, line in enumerate(vecfile):
            vecvals.append([float(x) for x in line.strip("\n").strip("  ").split(" ")])
        vecfile.close()
        for frame in range(0, bpy.context.scene.frame_end+1):
            print('help', lexport.newdir, self.simlistn, lexport.metric)
            print(lexport.newdir+"/"+self.simlistn[int(lexport.metric)]+"-"+str(frame)+".res")
            hours = 0
            subprocess.call("oconv -w "+lexport.lights(frame)+" "+lexport.filename+".whitesky "+lexport.mat(frame)+" "+lexport.poly(frame)+" > "+lexport.filename+"-"+str(frame)+".oct", shell = True)
            if not os.path.isdir(lexport.newdir+"/s_data"):
                    os.makedirs(lexport.newdir+"/s_data")
            subprocess.call("cat "+lexport.filebase+".rtrace | rtcontrib -h -I -fo -bn 146 -ab 3 -ad 4096 -lw 0.0003 -n "+str(nproc)+" -f tregenza.cal -b tbin -o "+lexport.newdir+"/s_data/sensor%d.dat -m sky_glow "+lexport.filename+"-"+str(frame)+".oct", shell = True)

            for l, readings in enumerate(vecvals):
                finalillu = []
                for i in range(0, 146):
                    if float(readings[0]) >= lexport.scene.livi_calc_dastart_hour and float(readings[0]) < lexport.scene.livi_calc_daend_hour and float(readings[1]) < wd:
                        sensfile = open(lexport.newdir+"/s_data/sensor"+str(i)+".dat", "r")
                        for j, line in enumerate(sensfile):
                            sens = line.strip("\n").split("\t")
                            senreading = 179*(0.265*float(sens[0])+0.67*float(sens[1])+0.065*float(sens[2]))
                            if i == 0:
                                finalillu.append(senreading*readings[i+2])
                                if j == 0:
                                    hours = hours + 1
                            else:
                                finalillu[j] = finalillu[j] + senreading*readings[i+2]
                        sensfile.close()
                for k, reading in enumerate(finalillu):
                    if reading > lexport.scene.livi_calc_min_lux:

                        res[frame][k] = res[frame][k] + 1

            for r in range(0, len(res[frame])):
                if hours != 0:
                    res[frame][r] = res[frame][r]*100/hours

            daresfile = open(lexport.newdir+"/"+self.simlistn[int(lexport.metric)]+"-"+str(frame)+".res", "w")
            for r in res[frame]:
                daresfile.write(str(round(r, 2))+"\n")
            daresfile.close()
        calc_op.report({'INFO'}, "Calculation is finished.")
        self.resapply(res, lexport)

    def resapply(self, res, lexport):
        maxres = []
        minres = []
        avres = []

        for frame in range(0, lexport.scene.frame_end+1):
            maxres.append(max(res[frame]))
            minres.append(min(res[frame]))
            avres.append(sum(res[frame])/len(res[frame]))

        lexport.scene['resav'] = avres
        lexport.scene['resmax'] = maxres
        lexport.scene['resmin'] = minres

        for frame in range(0, lexport.scene.frame_end+1):
            rgb = []
            j = 0
            for i in range(0, len(res[frame])):
                h = 0.75*(1-(res[frame][i]-min(lexport.scene['resmin']))/(max(lexport.scene['resmax']) + 0.01 - min(lexport.scene['resmin'])))
                rgb.append(colorsys.hsv_to_rgb(h, 1.0, 1.0))
            cno = 0
            for geo in lexport.scene.objects:
                bpy.ops.object.select_all(action = 'DESELECT')
                bpy.context.scene.objects.active = None
                try:
                    if geo['calc'] == 1:
                        bpy.context.scene.objects.active = geo
                        geo.select = True
                        if frame == 0:
                            while len(geo.data.vertex_colors) > 0:
                                bpy.ops.mesh.vertex_color_remove()

                        bpy.ops.mesh.vertex_color_add()
                        geo.data.vertex_colors[frame].name = str(frame)
                        vertexColour = geo.data.vertex_colors[frame].data

                        for face in geo.data.faces:
                            if "calcsurf" in str(geo.data.materials[face.material_index].name):
                                v = vertexColour[face.index]
                                if lexport.scene['cp'] == 0:
                                    v.color1 = rgb[j]
                                    v.color2 = rgb[j]
                                    v.color3 = rgb[j]
                                    if len(face.vertices) == 4:
                                        v.color4 = rgb[j]
                                    j = j + 1

                                if lexport.scene['cp'] == 1:
                                    for vert in face.vertices:
                                        if vert == face.vertices[0]:
                                            v.color1 = rgb[cno + [i for i, x in enumerate(geo['cverts']) if x == vert][0]]
                                        elif vert == face.vertices[1]:
                                            v.color2 = rgb[cno + [i for i, x in enumerate(geo['cverts']) if x == vert][0]]
                                        elif vert == face.vertices[2]:
                                            v.color3 = rgb[cno + [i for i, x in enumerate(geo['cverts']) if x == vert][0]]
                                        elif vert == face.vertices[3]:
                                            v.color4 = rgb[cno + [i for i, x in enumerate(geo['cverts']) if x == vert][0]]
                        cno = cno + len(geo['cverts'])

                except Exception as e:
                    print(e)

        for frame in range(0, lexport.scene.frame_end+1):
            bpy.ops.anim.change_frame(frame = frame)
            for geo in lexport.scene.objects:
                try:
                    if geo['calc'] == 1:
                        for vc in geo.data.vertex_colors:
                            if frame == int(vc.name):
                                vc.active = 1
                                vc.active_render = 1
                                vc.keyframe_insert("active")
                                vc.keyframe_insert("active_render")
                            else:
                                vc.active = 0
                                vc.active_render = 0
                                vc.keyframe_insert("active")
                                vc.keyframe_insert("active_render")
                except:
                    pass
        lexport.scene.livi_display_panel = 1
        bpy.ops.wm.save_mainfile(check_existing = False)


#!/usr/bin/env python3

import argparse as arg
import glob
import os
import subprocess
import sys
#from multiprocessing.pool import ThreadPool
from multiprocessing import Pool
import os

def repexjob(INDEX):
    orig= os.getcwd()
    os.chdir(f"/home/dulyanov/RepExTry/SESSIONS_MULT/KP{INDEX}")
    make_command = ["make"]
    stdout = open(f'{INDEX}-STD.out', 'w')
    stderr = open(f'{INDEX}-STD.err', 'w')
    make_proc = subprocess.Popen(make_command, shell = True, stdout = stdout, stderr = stderr)
    make_proc.wait()
    os.chdir(orig)

if __name__=='__main__':
    parser = arg.ArgumentParser()
    parser.add_argument('--adapters', default='/home/ermolaev/soft/bbmap/resources/adapters.fa')
    parser.add_argument('--bbduk_params', default='ktrim=r k=20 mink=10 hdist=2 threads=10 maxns=0 ftl=19 ftr=139 minlen=120')
    parser.add_argument('--num_runs_simult', default='2')
    parser.add_argument('--comparative', action='store_true')
    parser.add_argument('PATHS', help='EXAMPLE: /home/reads/{ind}/{ind}_read_{read_number}.fastq; {ind} and {read_number} should be left unchanged')
    parser.add_argument('INDEXES', nargs='+')

    args = parser.parse_args()
    COMPARATIVE, PATHS, ADAPTERS, BBDUK_PARAMS, INDEXES, PROCS = args.comparative, args.PATHS, args.adapters, args.bbduk_params, args.INDEXES, args.num_runs_simult

    MAKE_TEMPLATES = glob.glob('/home/dulyanov/RepExTry/**/Makefile_template', recursive=True)

    get_path = lambda ind, read_number: ' '.join(sorted(glob.glob(PATHS.format(ind=ind, read_number=read_number))))

    if not COMPARATIVE:
        for INDEX in INDEXES:
            SOURCE = locals()
            PATHR1FASTQGZ, PATHR2FASTQGZ = get_path(INDEX, '1'), get_path(INDEX, '2')
            print(PATHS.format(ind=INDEX, read_number='1'))
            REPLACER_DICT = dict((i, SOURCE[i]) for i in ('ADAPTERS', 'BBDUK_PARAMS', 'PATHR1FASTQGZ', 'PATHR2FASTQGZ', 'INDEX'))
            for TEMPLATE in MAKE_TEMPLATES:
                with open(TEMPLATE, 'r') as TFILE:
                    TXT = TFILE.read()
                    for NAME, VALUE in REPLACER_DICT.items():
                        TXT=TXT.replace(f'!{NAME}!', f'{VALUE}')
                RUNNER = TEMPLATE.replace('RepExTry/', f'RepExTry/SESSIONS_MULT/KP{INDEX}/').replace('Makefile_template', 'Makefile')
                os.makedirs(os.path.dirname(RUNNER), exist_ok=True)
                with open(RUNNER, 'w') as RFILE:
                    RFILE.write(TXT)
    else:
        for TEMPLATE in MAKE_TEMPLATES:
            SOURCE = locals()
            with open(TEMPLATE, 'r') as TFILE:
                TXT = TFILE.read()
                if TXT[0:8] == 'ADAPTERS':
                    TXTLIST = TXT.split('\n')
                    BBDUK = TXTLIST[9]
                    TXTLIST.pop(9)
                    for INDEX in INDEXES:
                        SOURCE = locals()
                        PATHR1FASTQGZ, PATHR2FASTQGZ = get_path(INDEX, '1'), get_path(INDEX, '2')
                        REPLACER_DICT = dict((i, SOURCE[i]) for i in ('ADAPTERS', 'BBDUK_PARAMS', 'PATHR1FASTQGZ', 'PATHR2FASTQGZ', 'INDEX'))
                        TXTLIST.insert(9, BBDUK.replace('!PATHR1FASTQGZ!', PATHR1FASTQGZ).replace('!PATHR2FASTQGZ!', PATHR2FASTQGZ).replace('!INDEX!', INDEX))
                    TXT = '\n'.join(TXTLIST)
                for NAME, VALUE in REPLACER_DICT.items():
                    TXT=TXT.replace(f'!{NAME}!', f'{VALUE}')
            RUNNER = TEMPLATE.replace('RepExTry/', f'RepExTry/SESSIONS_MULT/comp{"".join(INDEXES)}/').replace('Makefile_template', 'Makefile')
            os.makedirs(os.path.dirname(RUNNER), exist_ok=True)

    pool = Pool(processes=int(PROCS))
    pool.map(repexjob, iter(INDEXES))

# with open('Makefile_template', 'r') as file:


import time
import platform

from behave import when, step
from selenium import webdriver
from selenium.common.exceptions import ElementNotInteractableException
from selenium.webdriver.support import expected_conditions as ec
from selenium.webdriver.support.wait import WebDriverWait

from features.configs.automation_config import TIMEOUT

class BasePage:

    def __init__(self, driver):
        self.driver = driver

    @staticmethod
    @when('I open {page} page in wui admin')
    def open_page(context, page):
        context.driver.get(context.wui_admin_url + "/" + page)

    @when("I open {page} page in Cloud")
    def open_page(self, context, page):
        context.driver.get(context.cloud_url + "/" + page)

    def find_element(self, locator, timeout=TIMEOUT):
        return WebDriverWait(self.driver, timeout).until(ec.presence_of_element_located(locator),
                                                         message=f"Can't find element by locator {locator}")

    def find_elements(self, locator, timeout=TIMEOUT):
        return WebDriverWait(self.driver, timeout).until(ec.presence_of_all_elements_located(locator),
                                                         message=f"Can't find elements by locator {locator}")

    def wait_element_text(self, locator, text, timeout=TIMEOUT):
        return WebDriverWait(self.driver, timeout).until(ec.text_to_be_present_in_element(locator, text),
                                                         message=f"Text {text} is not present in element {locator}")

    def wait_element_is_visible(self, locator, timeout=TIMEOUT):
        return WebDriverWait(self.driver, timeout).until(ec.visibility_of_any_elements_located(locator),
                                                         message=f"Elements with locator {locator} is not visible")

    def wait_element_is_not_visible(self, locator, timeout=TIMEOUT):
        return WebDriverWait(self.driver, timeout).until(ec.invisibility_of_element(locator),
                                                         message=f"Elements with locator {locator} is visible")

    def send_keys_with_retry(self, locator, text, timeout=TIMEOUT):
        is_typed = False
        start_time = time.time()

        while not is_typed and ((time.time() - start_time) < timeout):
            try:
                self.find_element(locator).send_keys(text)
                is_typed = True
            except ElementNotInteractableException:
                time.sleep(1)

        assert is_typed, "Text " + text + " is not typed into element with locator " + str(
            locator) + " in timeout = " + str(
            timeout) + " secs"

    def switch_to_window_with_retry(self, window_number, timeout=TIMEOUT):
        is_switched = False
        start_time = time.time()

        while not is_switched and ((time.time() - start_time) < timeout):
            try:
                self.driver.switch_to_window(self.driver.window_handles[window_number])
                is_switched = True
            except IndexError:
                time.sleep(1)

        assert is_switched, "Window with number " + str(window_number) + " is not chosen in timeout = " + str(
            timeout) + " secs"

    def click_with_retry(self, locator, condition, timeout=TIMEOUT):
        is_clicked = False
        start_time = time.time()

        while not is_clicked and ((time.time() - start_time) < timeout):
            self.find_element(locator).click()

            if condition():
                is_clicked = True
            else:
                time.sleep(1)

        assert is_clicked, "Element with locator " + str(locator) + " is not clicked in timeout = " + str(
            timeout) + " secs"

    def accept_standard_popup(self):
        alert_obj = self.driver.switch_to.alert
        time.sleep(4)
        alert_obj.accept()


import json

data = {"test1": 1, "test2": 2, "test3": 3, "new_key": 4} #assigns data to be used for overriding

class jsonfile(object):
    """docstring for jsonfile"""
    def __init__(self, file): #file is an actual argument
        super(jsonfile, self).__init__() #idk what this does but i feel like it might break if i remove
        self.file = file #makes the argument file a class var

        try:
            with open(file) as file: #attempts to load the file that was inputted
                self.data = file_contents = json.load(file) # parses the JSON data in the file and returns a dictionary assigned to class var data
        except: #if the file doesnt exist yet
            open(file, "x") # creates the file
            print(f'{file} was not found, creating...') #flavortext
            self.data = {} #still makes sure data exists to not error out

    def write(self, input): #overrides the json with input
        with open(self.file, "w") as file: #opens up the file for writing
            file.write(json.dumps(input, indent=4)) # formats and beautifies the JSON as a string and overwrites the file contents with it
        self.data = input #updates the data var to make sure its uptodate

foo = jsonfile('file.json')

print(foo.data)
foo.write(data)
print(foo.data)
print(foo.file)


from setuptools import setup, find_packages
from setuptools.command.install import install as _install

__version__ = '0.4.0'

class NLTKInstall(_install):
    def run(self):
        _install.do_egg_install(self)

        import nltk
        nltk.download('averaged_perceptron_tagger')

setup(
    cmdclass={'install': NLTKInstall},
    name='Code base analizer',
    packages=find_packages(exclude=['tests']),
    include_package_data=True,
    version=__version__,
    install_requires=['nltk'],
    setup_requires=['nltk'],

    description='Module displays most popular words found in your codebase.',
    url='https://github.com/Grin941/codebase_analizer',
    licence='MIT',
    author='Grinenko Alexander',
    author_email='labamifi@gmail.com',
    classifiers=[
        'Development Status :: 1 - Planning',
        'Environment :: Console',
        'Intended Audience :: Developers',
        'License :: OSI Approved :: MIT License',
        'Natural Language :: English',
        'Operating System :: MacOS',
        'Operating System :: POSIX :: Linux',
        'Operating System :: Microsoft :: Windows',
        'Programming Language :: Python :: 3 :: Only',
        'Programming Language :: Python :: Implementation :: CPython',
        'Topic :: Utilities',
    ],
    entry_points={
        'console_scripts': ['analize_codebase=bin.analize_codebase:main']
    },
)


from task import get_task, Inference, Training, ValidationTraining

def test_task_is_training():
    kwargs = {
        "train_data_path": "data/dummy_train_technical_test.csv",
        "validation_split": None,
    }
    task = get_task(**kwargs)
    assert isinstance(task, Training)

def test_task_is_validation_training():
    kwargs = {
        "train_data_path": "data/dummy_train_technical_test.csv",
        "validation_split": .2,
    }
    task = get_task(**kwargs)
    assert isinstance(task, ValidationTraining)

def test_task_is_inference():
    kwargs = {
        "infer_data_path": "data/dummy_test_technical_test.csv",
        "model_checkpoint_path": "path/to/some/model/checkpoint",
    }
    task = get_task(**kwargs)
    assert isinstance(task, Inference)


from collections import deque
def count(n):
    b = 1
    res = []
    while n != 0:
        yield n, b
        n -= 1
        b += 1
        if True:
            pass

def test():
    n = 0
    while True:
        if n != 1:
            yield n
        n += 1
def search(list):
    a = deque(maxlen=3)
    for i in list:
        yield i, a
    a.append(i)
a = search([1, 2, 3, 4, 5, 6])
for i, j in a:
    for each in j:
        print(each)
for i in range(5):
    print(i)

print(count(2))
for i, j in count(5):
    print(i, "===>", j)


import sys

from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QFileDialog
from PyQt5.QtGui import QImage, QPixmap, QTextCursor
from PyQt5.QtCore import pyqtSignal, QThread, QSize, Qt
from Registration import Ui_MainWindow

import os
import time
import datetime as dt
import threading as th
import ctypes
import inspect
import copy
import numpy as np

import pyrealsense2 as rs
import open3d as o3d
import cv2

DELAY = 0

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()

        # Set up the user interface from Designer.
        self.setupUi(self)

        self.dis_update.connect(self.camera_view)

        # Show current 2d depth png pic
        self.pushButton_takephotos.clicked.connect(self.pushButton_takephotos_clicked)

        # Show current 3d pointcloud button
        self.show_current_pointcloud.clicked.connect(self.show_current_pointcloud_clicked)

        # Select and show target 3d pointcloud button
        self.show_target_pointcloud.clicked.connect(self.show_target_pointcloud_clicked)

        self.registration_button.clicked.connect(self.registration_button_clicked)

        # change cmd output to text browser
        self.th = MyThread()
        self.th.signalForText.connect(self.onUpdateText)
        sys.stdout = self.th

        self.thread_camera = None
        self.takePhotos = False
        self.showCurrent3dPointCloud = False

    # 在对应的页面类的内部，与def定义的函数同级
    dis_update = pyqtSignal(QPixmap)

    def onUpdateText(self,text):
        cursor = self.cmd.textCursor()
        cursor.movePosition(QTextCursor.End)
        cursor.insertText(text)
        self.cmd.setTextCursor(cursor)
        self.cmd.ensureCursorVisible()

    def pushButton_takephotos_clicked(self):
        self.takePhotos = True

    def show_current_pointcloud_clicked(self):
        self.showCurrent3dPointCloud = True
        #print(self.showCurrent3dPointCloud)

    def show_target_pointcloud_clicked(self,Filepath):
        global target_dir

        c = QFileDialog.getOpenFileName(None, "Select the target file","C:/", "(*.ply)")
        print("Showing Target 3D pointcloud...")
        if c is not None:
            print("File dir is ", c[0])
            print("Succeed...")
            target_file_dir = str(c[0])
            pcd = o3d.io.read_point_cloud(target_file_dir)
            o3d.visualization.draw_geometries([pcd])

            target_dir = target_file_dir

    # add an event for exit the GUI
    def closeEvent(self, event):
        """我们创建了一个消息框，上面有俩按钮：Yes和No.第一个字符串显示在消息框的标题栏，第二个字符串显示在对话框，
              第三个参数是消息框的俩按钮，最后一个参数是默认按钮，这个按钮是默认选中的。返回值在变量reply里。"""

        reply = QMessageBox.question(self, 'Message', "Are you sure to quit?",
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        # 判断返回值，如果点击的是Yes按钮，我们就关闭组件和应用，否则就忽略关闭事件
        if reply == QMessageBox.Yes:
            self.stop_thread(self.thread_camera)
            event.accept()
            sys.stdout = sys.__stdout__
        else:
            event.ignore()

    # click event and register
    def registration_button_clicked(self):

        start = time.time()
        voxel_size = 0.05  # means 5cm for this dataset

        print(":: Load two point clouds and disturb initial pose.")
        source_reg = o3d.io.read_point_cloud(source_dir)
        target_reg = o3d.io.read_point_cloud(target_dir)

        # process data
        print(":: Downsample with a voxel size %.3f." % voxel_size)
        source_down = source_reg.voxel_down_sample(voxel_size)
        target_down = target_reg.voxel_down_sample(voxel_size)

        radius_normal = voxel_size * 2
        print(":: Estimate normal with search radius %.3f." % radius_normal)

        source_down.estimate_normals(
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal, max_nn=30))
        target_down.estimate_normals(
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal, max_nn=30))

        radius_feature = voxel_size * 5
        print(":: Compute FPFH feature with search radius %.3f." % radius_feature)
        source_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
            source_down,
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature, max_nn=100))
        target_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
            target_down,
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature, max_nn=100))

        print("prepare_dataset took %.3f sec.\n" % (time.time() - start))

        # global registration
        distance_threshold = voxel_size * 1.5
        print(":: RANSAC registration on downsampled point clouds.")
        print("   Since the downsampling voxel size is %.3f," % voxel_size)
        print("   we use a liberal distance threshold %.3f." % distance_threshold)
        result_ransac = o3d.pipelines.registration.registration_ransac_based_on_feature_matching(
            source_down, target_down, source_fpfh, target_fpfh, True,
            distance_threshold,
            o3d.pipelines.registration.TransformationEstimationPointToPoint(False),
            3, [
                o3d.pipelines.registration.CorrespondenceCheckerBasedOnEdgeLength(
                    0.9),
                o3d.pipelines.registration.CorrespondenceCheckerBasedOnDistance(
                    distance_threshold)
            ], o3d.pipelines.registration.RANSACConvergenceCriteria(100000, 0.999))

        print(result_ransac)

        print("execute_global_registration took %.3f sec.\n" % (time.time() - start))

        # refine registration (ICP registration)
        distance_threshold = voxel_size * 0.4
        print(":: Point-to-plane ICP registration is applied on original point")
        print("   clouds to refine the alignment. This time we use a strict")
        print("   distance threshold %.3f." % distance_threshold)

        print(":: Downsample with a voxel size %.3f." % voxel_size)
        source_down = source_reg.voxel_down_sample(voxel_size)
        target_down = target_reg.voxel_down_sample(voxel_size)

        radius_normal = voxel_size * 2
        print(":: Estimate normal with search radius %.3f." % radius_normal)

        source_down.estimate_normals(
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal, max_nn=30))
        target_down.estimate_normals(
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius_normal, max_nn=30))

        radius_feature = voxel_size * 5
        print(":: Compute FPFH feature with search radius %.3f." % radius_feature)
        source_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
            source_down,
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature, max_nn=100))
        target_fpfh = o3d.pipelines.registration.compute_fpfh_feature(
            target_down,
            o3d.geometry.KDTreeSearchParamHybrid(radius=radius_feature, max_nn=100))

        print(source_reg, target_reg, source_down, target_down, source_fpfh, target_fpfh)

        result_icp = o3d.pipelines.registration.registration_icp(
            source_down, target_down, distance_threshold, result_ransac.transformation,
            o3d.pipelines.registration.TransformationEstimationPointToPlane())

        print(result_icp)
        print("refine_registration took %.3f sec.\n" % (time.time() - start))

        self.show_results.setText("The transform Matrix is \n" + str(result_icp.transformation))

        source_temp = copy.deepcopy(source_reg)
        target_temp = copy.deepcopy(target_reg)
        source_temp.paint_uniform_color([1, 0.706, 0])
        target_temp.paint_uniform_color([0, 0.651, 0.929])
        source_temp.transform(result_icp.transformation)

        o3d.visualization.draw_geometries([source_temp, target_temp])

    def open_camera(self):
        # target选择开启摄像头的函数
        self.thread_camera = th.Thread(target=self.open_realsense)
        self.thread_camera.start()
        print('Open Camera...')

    def camera_view(self, c):
        # 调用setPixmap函数设置显示Pixmap
        self.label_show.setPixmap(c)
        # 调用setScaledContents将图像比例化显示在QLabel上
        self.label_show.setScaledContents(True)

    def _async_raise(self, tid, exctype):
        """raises the exception, performs cleanup if needed"""
        tid = ctypes.c_long(tid)
        if not inspect.isclass(exctype):
            exctype = type(exctype)
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype))
        if res == 0:
            raise ValueError("invalid thread id")
        elif res != 1:
            # """if it returns a number greater than one, you're in trouble,
            # and you should call it again with exc=NULL to revert the effect"""
            ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None)
            raise SystemError("PyThreadState_SetAsyncExc failed")

    def stop_thread(self, thread):
        self._async_raise(thread.ident, SystemExit)

    def open_realsense(self):

        global source_dir

        print('open_realsense')

        # Create a pipeline
        pipeline = rs.pipeline()

        # Create a config and configure the pipeline to stream
        #  different resolutions of color and depth streams
        config = rs.config()
        config.enable_stream(rs.stream.depth, 640, 480, rs.format.z16, 30)
        config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

        # Start streaming
        profile = pipeline.start(config)

        # Getting the depth sensor's depth scale (see rs-align example for explanation)
        depth_sensor = profile.get_device().first_depth_sensor()
        depth_scale = depth_sensor.get_depth_scale()
        print("Depth Scale is: ", depth_scale)

        # We will be removing the background of objects more than
        #  clipping_distance_in_meters meters away
        clipping_distance_in_meters = 1  # 1 meter
        clipping_distance = clipping_distance_in_meters / depth_scale

        # Processing blocks
        pc = rs.pointcloud()
        thr_filter = rs.threshold_filter()
        thr_filter.set_option(rs.option.min_distance, 0)
        thr_filter.set_option(rs.option.max_distance, 1.830)

        # Color Intrinsics
        # intr = color_frame.profile.as_video_stream_profile().intrinsics

        # Create an align object
        # rs.align allows us to perform alignment of depth frames to others frames
        # The "align_to" is the stream type to which we plan to align depth frames.
        align_to = rs.stream.color
        align = rs.align(align_to)

        # Streaming loop
        try:
            while True:
                # Get frameset of color and depth
                frames = pipeline.wait_for_frames()
                # frames.get_depth_frame() is a 640x360 depth image

                # Align the depth frame to color frame
                aligned_frames = align.process(frames)

                # Get aligned frames
                aligned_depth_frame = aligned_frames.get_depth_frame()  # aligned_depth_frame is a 640x480 depth image
                color_frame = aligned_frames.get_color_frame()

                # Validate that both frames are valid
                if not aligned_depth_frame or not color_frame:
                    continue

                aligned_depth_frame = thr_filter.process(aligned_depth_frame)

                depth_image = np.asanyarray(aligned_depth_frame.get_data())
                color_image = np.asanyarray(color_frame.get_data())

                #aligned_depth_frame = aligned_frames.first(aligned_depth_frame).as_video_frame()

                points = pc.calculate(aligned_depth_frame)

                if(self.takePhotos == True):

                    now_date = dt.datetime.now().strftime('%F')
                    now_time = dt.datetime.now().strftime('%F_%H%M%S')

                    path_ok = os.path.exists(now_date)
                    if(path_ok == False):
                        os.mkdir(now_date)

                    if(os.path.isdir(now_date)):
                        #id = "1"

                        depth_full_path = ''
                        color_full_path = ''

                        depth_col = cv2.applyColorMap(cv2.convertScaleAbs(depth_image, alpha=0.03), cv2.COLORMAP_JET)

                        depth_full_path = os.path.join('./', now_date, now_time + '_depth.png')
                        depth_col_full_path = os.path.join('./', now_date, now_time + '_depth_col.png')
                        color_full_path = os.path.join('./', now_date, now_time + '_color.png')
                        ply_full_path = os.path.join('./', now_date, now_time + '_pointcloud.ply')

                        cv2.imencode('.png', depth_image)[1].tofile(depth_full_path)
                        cv2.imencode('.png', color_image)[1].tofile(color_full_path)
                        cv2.imencode('.png', depth_col)[1].tofile(depth_col_full_path)
                        points.export_to_ply(ply_full_path, color_frame)

                        print('Saved Successful...')

                        # show current depth pic
                        # zoom
                        scale = 0.8
                        img = QImage(depth_col_full_path)
                        mgnWidth = int(640 * scale)
                        mgnHeight = int(480 * scale)
                        size = QSize(mgnWidth, mgnHeight)
                        pixImg = QPixmap.fromImage(img.scaled(size, Qt.IgnoreAspectRatio))
                        self.show_currentPointCloud.resize(mgnWidth, mgnHeight)
                        self.show_currentPointCloud.setPixmap(pixImg)

                        source_dir = ply_full_path
                    self.takePhotos = False

                if(self.showCurrent3dPointCloud == True and self.takePhotos == False):
                    print(ply_full_path)
                    pcd = o3d.io.read_point_cloud(ply_full_path)
                    o3d.visualization.draw_geometries([pcd])
                    print("Showing the current 3D Pointcloud in Open3D GUI")

                    self.showCurrent3dPointCloud = False

                # Remove background - Set pixels further than clipping_distance to grey
                grey_color = 153
                depth_image_3d = np.dstack(
                    (depth_image, depth_image, depth_image))  # depth image is 1 channel, color is 3 channels
                bg_removed = np.where((depth_image_3d > clipping_distance) | (depth_image_3d <= 0), grey_color,
                                      color_image)

                images = bg_removed

                qimage = QImage(images, 640, 480, QImage.Format_RGB888)
                pixmap = QPixmap.fromImage(qimage)
                self.dis_update.emit(pixmap)

                time.sleep(DELAY)

        finally:
            pipeline.stop()

class MyThread(QThread):
    signalForText = pyqtSignal(str)

    def __init__(self,data=None, parent=None):
        super(MyThread, self).__init__(parent)
        self.data = data

    def write(self, text):
        self.signalForText.emit(str(text))  # 发射信号

    def flush(self):
        pass

if __name__ == "__main__":
    app = QApplication(sys.argv)
    app.aboutToQuit.connect(app.deleteLater)
    w = MainWindow()
    w.show()

    w.open_camera()

    sys.exit(app.exec_())


import cv2
import numpy as np

def sketch(image):
    #convert image to greyscale
    img_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    #clean up image using Gaussian Blur
    img_gray_blur = cv2.GaussianBlur(img_gray, (5,5), 0)

    #Extract edges
    canny_edges = cv2.Canny(img_gray_blur, 10, 70)

    #Invert to binarize the image
    ret, mask = cv2.threshold(canny_edges, 70, 255, cv2.THRESH_BINARY_INV)

    return mask

capture = cv2.VideoCapture(0)

while True:
    ret, frame = capture.read()
    cv2.imshow('Sketchy', sketch(frame))
    if cv2.waitKey(1) == 13: #13 is the Enter waitKey
        break

capture.release()
cv2.destroyAllWindows()


from typing import Any, Dict, Set
from pathlib import PurePosixPath

from sqlalchemy import create_engine, event, MetaData  # type: ignore
from sqlalchemy.engine import Engine  # type: ignore
from sqlalchemy.sql import select  # type: ignore

from .connection import get_sqlite3_path
from .models import RepositoryMetadata, SourceFileInRepository, MockSourceFile
from ._schema import (
    failure, meta, repository, repository_source, source_file, source_summary,
    metadata
)

from sensibility.language import SourceSummary

class UniquenessError(RuntimeError):
    pass

class NewCorpusError(Exception):
    pass

class FileInfo:
    def __init__(self, mappings: Set[SourceFileInRepository],
                 summary: SourceSummary) -> None:
        assert len(mappings) > 0
        self._mappings = mappings
        self.summary = summary

    @property
    def filehash(self) -> str:
        return self._any.filehash

    @property
    def owner(self) -> str:
        return self._any.owner

    @property
    def name(self) -> str:
        return self._any.name

    @property
    def href(self) -> str:
        return self._any.href

    @property
    def license(self) -> str:
        return self._any.license

    @property
    def path(self) -> PurePosixPath:
        return self._any.path

    @property
    def n_tokens(self) -> int:
        return self.summary.n_tokens

    @property
    def sloc(self) -> int:
        return self.summary.sloc

    @property
    def is_unique(self) -> bool:
        return len(self._mappings) == 1

    @property
    def _any(self) -> SourceFileInRepository:
        return next(iter(self._mappings))

class Corpus:
    def __init__(self, engine=None, url: str=None, read_only=False) -> None:
        if engine is not None:
            self.engine = engine
        else:
            if url is None:
                url = f"sqlite:///{get_sqlite3_path()}"
            self.engine = create_engine(url)

        self._initialize_sqlite3(read_only)

        if self.empty:
            metadata.create_all(self.engine)

        self.conn = self.engine.connect()

    @property
    def language(self) -> str:
        """
        The main laguage of the mined sources.
        """
        query = select([meta.c.value]).\
            where(meta.c.key == 'language')
        try:
            result, = self.conn.execute(query)
        except ValueError:
            raise NewCorpusError
        else:
            return result[meta.c.value]

    @property
    def empty(self) -> bool:
        metadata = MetaData()
        metadata.reflect(self.engine)
        return 'repository' not in metadata

    def __getitem__(self, filehash: str) -> bytes:
        """
        Returns a file from the corpus.
        """
        return self.get_source(filehash)

    def set_metadata(self, **kwargs: Any) -> None:
        """
        Sets the metadata table.
        """
        self.conn.execute(meta.insert(), [
            {'key': key, 'value': str(value)}
            for key, value in kwargs.items()
        ])

    def insert_repository(self, repo: RepositoryMetadata) -> None:
        """
        Insert a repository's metadata into the database.
        """
        self.conn.execute(repository.insert(),
                          owner=repo.owner, name=repo.name,
                          revision=repo.revision, license=repo.license,
                          commit_date=repo.commit_date)

    def insert_source_file_from_repo(self, entry: SourceFileInRepository) -> None:
        """
        Insert a source file from a repository into the database. If the
        source file already exists in the database, only the relation is
        inserted.
        """
        trans = self.conn.begin()
        try:
            self.conn.execute((source_file.insert()
                                .prefix_with('OR IGNORE', dialect='sqlite')),
                              source=entry.source_file.source,
                              hash=entry.filehash)
            self.conn.execute(repository_source.insert(),
                              owner=entry.owner, name=entry.name,
                              hash=entry.filehash, path=str(entry.path))
        except:
            trans.rollback()
            raise
        else:
            trans.commit()

    def insert_source_summary(self, filehash: str, summary: SourceSummary) -> None:
        """
        Insert the word count into the source summary.
        """
        self.conn.execute(source_summary.insert(),
                          hash=filehash,
                          sloc=summary.sloc, n_tokens=summary.n_tokens)

    def insert_failure(self, filehash: str) -> None:
        """
        Insert the word count into the source summary.
        """
        self.conn.execute(failure.insert(), hash=filehash)

    def get_source(self, filehash: str) -> bytes:
        """
        Returns the source code for one file.
        """
        query = select([source_file.c.source])\
            .where(source_file.c.hash == filehash)
        result, = self.conn.execute(query)
        return result[source_file.c.source]

    def get_info(self, filehash: str) -> FileInfo:
        # Do an intense query, combining multiple tables.
        query = select([source_summary, repository_source, repository]).select_from(
            source_file.join(source_summary)\
            .join(repository_source)\
            .join(repository)
        ).where(source_summary.c.hash == filehash)
        results = self.conn.execute(query).fetchall()

        # TODO: what if it's not found?

        mock_file = MockSourceFile(filehash)
        repos = {
            (row[repository.c.owner], row[repository.c.name]): RepositoryMetadata(
                owner=row[repository.c.owner],
                name=row[repository.c.name],
                license=row[repository.c.license],
                revision=row[repository.c.revision],
                commit_date=row[repository.c.commit_date]
            ) for row in results
        }
        mappings = set(
            SourceFileInRepository(
                repository=repos[row[repository.c.owner], row[repository.c.name]],
                source_file=mock_file,
                path=PurePosixPath(row[repository_source.c.path])
            ) for row in results
        )

        row, *_ = results
        summary = SourceSummary(sloc=row[source_summary.c.sloc],
                                n_tokens=row[source_summary.c.n_tokens])
        return FileInfo(mappings, summary)

    def _initialize_sqlite3(self, read_only: bool) -> None:
        """
        Set some pragmas for initialy creating the SQLite3 database.
        """

        @event.listens_for(Engine, "connect")
        def set_sqlite_pragma(dbapi_connection, _connection_record):
            cur = dbapi_connection.cursor()
            cur.execute('PRAGMA encoding = "UTF-8"')
            cur.execute('PRAGMA foreign_keys = ON')
            if not read_only:
                cur.execute('PRAGMA journal_mode = WAL')
                cur.execute('PRAGMA synchronous = NORMAL')
            cur.close()


import hashlib
import logging
from playwright.sync_api import sync_playwright
from sqlalchemy.orm import Session

from app.config import settings
from app.logic.image import compress_image
from db.models.screenshot import Screenshot

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

MAX_LINKS_TO_FOLLOW = settings.max_links_to_follow

def calculate_checksum(image_path):
    with open(image_path, "rb") as image_file:
        file_contents = image_file.read()
        return hashlib.sha256(file_contents).hexdigest()

def add_screenshot_to_db(unique_id, image_path, file_name, image_url: str, db):
    # Adjust the image_path to point to the compressed file
    compressed_image_path = image_path + ".jpg"

    checksum = calculate_checksum(compressed_image_path)
    new_screenshot = Screenshot(
        unique_id=unique_id,
        image_path=compressed_image_path,
        checksum=checksum,
        file_name=file_name + ".jpg",
        image_url=image_url,
    )
    db.add(new_screenshot)
    db.commit()
    return new_screenshot

def take_screenshot(browser, url, path):
    try:
        page = browser.new_page()
        page.goto(url)
        page.screenshot(path=path)
        page.close()

        # Compress the image after taking the screenshot
        compress_image(path)

        # Return the new path with the compressed file extension
        return path + ".jpg"
    except Exception as e:
        logger.error(f"Error taking screenshot of {url}: {e}")
        return None

def crawl_and_screenshot(start_url, number_of_links, unique_id, db: Session):
    number_of_links = min(number_of_links, MAX_LINKS_TO_FOLLOW)
    if number_of_links < 1:
        raise ValueError(
            f"Number of links to follow must be greater than 0, got {number_of_links}"
        )

    if number_of_links > MAX_LINKS_TO_FOLLOW:
        raise ValueError(
            f"Number of links to follow must be less than {MAX_LINKS_TO_FOLLOW}, got {number_of_links}"
        )

    if not start_url.startswith("http"):
        raise ValueError(f"Start URL must start with http, got {start_url}")

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)

        screenshots = []
        initial_path = take_screenshot(
            browser, start_url, f"screenshots/{unique_id}_0.png"
        )
        if initial_path:
            screenshots.append(
                add_screenshot_to_db(
                    unique_id, initial_path, f"{unique_id}_0.png", start_url, db
                )
            )

        page = browser.new_page()
        page.goto(start_url)
        links = page.evaluate(
            "() => Array.from(document.querySelectorAll('a')).map(a => a.href);"
        )
        page.close()
        valid_links = list(set(filter(lambda link: link.startswith("http"), links)))
        followed_links = valid_links[:number_of_links]

        for index, url in enumerate(followed_links, start=1):
            path = f"screenshots/{unique_id}_{index}.png"
            screenshot = take_screenshot(browser, url, path)
            if screenshot:
                screenshots.append(
                    add_screenshot_to_db(
                        unique_id, screenshot, f"{unique_id}_{index}.png", url, db
                    )
                )

        browser.close()
        return [s.image_path for s in screenshots if s is not None]


from re import search
import csv

# 單純 concordance function，輸入欲查上下文之關鍵字 kw，以及所有 sentences；
# return (上文, kw, 下文) 之 list。
def concordance(kw,sentences):
    concordance_list = []
    for sentence in sentences:
        m = search(pattern='(?P<left>.*)(?P<kw>' + kw + ')(?P<right>.*)', string=sentence)
        if m:
            concordance_list.append([m.group('left'), m.group('kw'), m.group('right')])
    return concordance_list

# 通用型 concordancer，輸入欲查上下文之關鍵字 kw，以及 csv 語料庫格式 row，以及欲檢索之欄位 target_column；
# return csv 格式的 rows，並於 target_column 前後 insert 上文和下文之欄位資料。
from nltk.text import ConcordanceIndex
def generic_concordancer(kw, rows, target_column_num):
    header_row = next(rows)
    header_row.insert(target_column_num+1, "right_print")
    header_row.insert(target_column_num, "left_print")

    output_rows= [header_row]
    for row in rows:
        if kw in row[target_column_num]:
            CI = ConcordanceIndex(tokens=list(row[target_column_num]))
            ConcordanceLine = CI.find_concordance(word=kw)[0]
            row.insert(target_column_num+1, ConcordanceLine.right_print)
            row[target_column_num] = kw
            row.insert(target_column_num, ConcordanceLine.left_print)
            output_rows.append(row)
    return output_rows

def segmented_concordancer(kw, rows, target_column_num):
    header_row = next(rows)
    header_row.insert(target_column_num+1, "right_print")
    header_row.insert(target_column_num, "left_print")

    output_rows= [header_row]
    for row in rows:
        if kw in row[target_column_num]:
            CI = ConcordanceIndex(tokens=row[target_column_num].split("/"))
            ConcordanceLine = CI.find_concordance(word=kw)[0]
            row.insert(target_column_num+1, ConcordanceLine.right_print)
            row[target_column_num] = kw
            row.insert(target_column_num, ConcordanceLine.left_print)
            output_rows.append(row)
    return output_rows

import sys
from klokahAPI import klokahAPI
if __name__ == "__main__":
#   csvfile = open(sys.argv[1]) #corpora_root_dir/nan/liching-v3-all.tsv
 #  kw = sys.argv[2] # 打
  # for row in generic_concordancer(kw=kw, rows=csv.reader(csvfile, delimiter="\t"), target_column_num=2):
   #    print(row)

    d = sys.argv[1]
    txt = kw = sys.argv[2]
    target_column_num = int(sys.argv[3])

    rows = klokahAPI(d=int(d), txt=txt)
    for row in generic_concordancer(kw, rows, target_column_num):
        print(row)


""" 804 https://leetcode.com/problems/unique-morse-code-words/"""

import time
from typing import List

class Solution:
    def uniqueMorseRepresentations(self, words: List[str]) -> int:
        alphabet = [".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.",
                    "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.."]

        words_morse = []
        for word in words:
            word_morse = ""
            for letter in word:
                word_morse += alphabet[ord(letter)-97]
            words_morse.append(word_morse)
        return len(set(words_morse))

if __name__ == "__main__":
    start_time = time.time()
    print(Solution().uniqueMorseRepresentations(["gin", "zen", "gig", "msg"]))

    print("--- %s seconds ---" % (time.time() - start_time))


# 获取html内容字符串，进行分析
with open('bs1.html',encoding='utf8') as f:
    html_doc = f.read()

# 导入 BeautifulSoup
from bs4 import BeautifulSoup

# 指定用html5lib来解析html文档
soup = BeautifulSoup(html_doc, "html5lib")

# print(soup.div.sting)
div = soup.find('div',id='d1')
print(div.find('a'))


import os
import os.path
import tkinter
from datetime import date
from tkinter import Tk, Listbox, END, messagebox
from tkinter import ttk

import requests
from bs4 import BeautifulSoup

today = date.today()
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:71.0) Gecko/20100101 Firefox/71.0"}

class DeckMGT(ttk.Frame):
    def __init__(self, parent):
        ttk.Frame.__init__(self, parent)
        self.parent = parent
        self.generateIntroGUI()

    def generateIntroGUI(self):
        for widget in self.parent.winfo_children():  # clear frame on load
            widget.destroy()
        self.parent.geometry("450x300")
        self.parent.title("Price Checker | Main Menu")

        self.labelTop = ttk.Label(self.parent, text="View Card History")
        self.labelTop.pack()

        cardNames = []  # populate a list of cards
        if not os.listdir("Cards/"):
            cardNames.append("No cards in system")
        else:
            for each in os.listdir("Cards"):
                cardNames.append(each.replace(".txt", ""))
        self.cardNameComboBox = ttk.Combobox(self.parent, values=cardNames,
                                             postcommand=self.updateList, width=25)  # state="readonly

        self.cardNameComboBox.pack()
        self.cardNameComboBox.current(0)

        self.cardHistoryBtn = ttk.Button(self.parent, text="View", command=self.generateCardHistoryGUI)
        self.cardHistoryBtn.pack()

        self.deleteCardBtn = ttk.Button(self.parent, text="Delete Card", command=self.removeCard)
        self.deleteCardBtn.pack()

        self.cardHistoryBtn = ttk.Button(self.parent, text="Generate Price Report", command=self.generatePriceReport)
        self.cardHistoryBtn.pack()

        self.newURLEntry = ttk.Entry(self.parent)
        self.newURLEntry.pack()

        self.newCardBtn = ttk.Button(self.parent, text="Add New Card", command=self.generateNewCard)
        self.newCardBtn.pack()

        self.exitButton = ttk.Button(self.parent, text="Exit Application", command=self.exitApplication)
        self.exitButton.pack()

    def generateCardHistoryGUI(self):
        listbox = Listbox(Tk())
        listbox.configure(width=75, height=20)
        listbox.winfo_toplevel().title("Results for " + self.cardNameComboBox.get())
        listbox.configure()
        listbox.pack()

        for eachLine in open("Cards/" + self.cardNameComboBox.get() + ".txt", "r+", encoding="utf-8").readlines():
            listbox.insert(END, eachLine)

    def generatePriceReport(self):
        storedReports = []
        buylistURLs = []
        listbox = Listbox(Tk())
        listbox.configure(width=75, height=20)
        listbox.winfo_toplevel().title("Price Report")
        listbox.configure()
        listbox.pack()
        looptest = 0

        for each in open("URLs.txt"):
            print(each)
            buylistURLs.append(each.replace("catalog", "buylist"))
            if(
                BeautifulSoup(requests.get(url=buylistURLs[looptest], headers=headers).content, "html.parser").find(
                    'form', attrs={"class": "add-to-cart-form", "data-variant": "NM-Mint, English"}) is None):
                buylistPrice = 0
            else:
                buylistPrice = BeautifulSoup(requests.get(url=buylistURLs[looptest], headers=headers).content, "html.parser").find(
                    'form', attrs={"class": "add-to-cart-form", "data-variant": "NM-Mint, English"}).get_text()[
                7:].split("/", 1)[0].strip()
            looptest += 1
            soup = BeautifulSoup(requests.get(url=each, headers=headers).content, "html.parser")
            cardName = soup.find('h1', attrs={"class": "title"}).get_text().replace('/', '')
            if (soup.find('span', attrs={"class": "variant-short-info"}).get_text() == "Out of stock."):
                price = soup.find('span', attrs={"class": "price no-stock"}).get_text()[5:]
            else:
                if (soup.find('form', attrs={"class": "add-to-cart-form", "data-variant": "NM-Mint, English",
                                             "data-name": cardName}) is None):
                    price = (
                        soup.find('form',
                                  attrs={"class": "add-to-cart-form"}).get_text()[
                        7:].split("/", 1)[0].rstrip())
                else:
                    price = (
                        soup.find('form',
                                  attrs={"class": "add-to-cart-form", "data-variant": "NM-Mint, English",
                                         "data-name": cardName}).get_text()[
                        7:].split("/", 1)[0].rstrip())
            print(price)
            print(buylistPrice)
            if (os.path.exists("Cards/" + cardName + ".txt")):
                eachLastLine = open("Cards/" + cardName + ".txt", "r+", encoding="utf-8").readlines()[-1].split("|", 1)[
                    0].strip()
            if (eachLastLine != price):
                storedReports.append(cardName + " changed price from " + str(eachLastLine) + " to " + price)
                open("Cards/" + cardName + ".txt", "a", encoding="utf-8").write(
                    price + " | Buylist (cash): " + buylistPrice + " | Credit: " + (
                        str(float(buylistPrice) * 1.25)) + " | " + str((date.today())) + "\n")
            """else:
                eachLastLine = open("Cards/" + cardName + ".txt", "w", encoding="utf-8").write(
                    price + " | " + str(date.today()) + "\n") #overwrites file if same price is met"""

        if (len(storedReports) == 0):
            listbox.insert(END, "NO CHANGES!")
        else:
            for eachLine in storedReports:
                listbox.insert(END, eachLine)

    def generateNewCard(self):
        soup = BeautifulSoup(requests.get(url=self.newURLEntry.get(), headers=headers).content, "html.parser")
        cardName = soup.find('h1', attrs={"class": "title"}).get_text().replace('/', '')
        buylistURLs = self.newURLEntry.get().replace("catalog", "buylist")
        # buylistPrice = (BeautifulSoup(requests.get(url=buylistURLs, headers=headers).content, "html.parser").find('span', attrs={"class": "regular price"}).get_text()[5:].split("/", 1)[0].rstrip())
        if (soup.find('form', attrs={"class": "add-to-cart-form", "data-variant": "NM-Mint, English",
                                     "data-name": cardName}) is None):
            buylistPrice = (
                soup.find('form',
                          attrs={"class": "add-to-cart-form"}).get_text()[
                7:].split("/", 1)[0].rstrip())
        else:
            buylistPrice = (
                soup.find('form',
                          attrs={"class": "add-to-cart-form", "data-variant": "NM-Mint, English",
                                 "data-name": cardName}).get_text()[
                7:].split("/", 1)[0].rstrip())
        """buylistPrice = BeautifulSoup(requests.get(url=buylistURLs, headers=headers).content, "html.parser").find('form',
                                                                                attrs={
                                                                                "class": "add-to-cart-form",
                                                                                "data-variant": "NM-Mint, English",
                                                                                "data-name": cardName}).get_text()[
                       7:].split("/", 1)[0].rstrip()"""

        newCard = open("Cards/" + soup.find('h1', attrs={"class": "title"}).get_text().replace('/', '') + ".txt", "w")
        if (soup.find('span', attrs={"class": "variant-short-info"}).get_text() == "Out of stock."):
            newCard.write(
                soup.find('span', attrs={"class": "price no-stock"}).get_text()[
                5:] + " | Buylist (cash): " + buylistPrice + " | Credit: " + (
                    str(float(buylistPrice) * 1.25)) + " | " + str((date.today())) + "\n")
        else:
            if (soup.find('form', attrs={"class": "add-to-cart-form", "data-variant": "NM-Mint, English",
                                         "data-name": cardName}) is None):
                newCard.write(
                    soup.find('form',
                              attrs={"class": "add-to-cart-form"}).get_text()[
                    7:].split("/", 1)[0].rstrip() + " | Buylist (cash): " + buylistPrice + " | Credit: " + (
                        str(float(buylistPrice) * 1.25)) + " | " + str((date.today())) + "\n")
            else:
                newCard.write(
                    soup.find('form',
                              attrs={"class": "add-to-cart-form", "data-variant": "NM-Mint, English"}).get_text()[
                    7:].split("/", 1)[0].rstrip() + " | Buylist (cash): " + buylistPrice + " | Credit: " + (
                        str(float(buylistPrice) * 1.25)) + " | " + str((date.today())) + "\n")
        newCard = open("URLs.txt", "a")
        newCard.write(self.newURLEntry.get() + "\n")
        newCard.close()
        self.newURLEntry.delete(0, END)

    def updateList(self):
        cardNames = []  # populate a list of cards
        for each in os.listdir("Cards"):
            cardNames.append(each.replace(".txt", ""))

        self.cardNameComboBox.configure(values=list(set(cardNames)))  # updates card combobox on action

    def removeCard(self):
        confirmationBtn = messagebox.askquestion("Delete Card", "Are you sure you want to delete this?",
                                                 icon="warning")
        if confirmationBtn == "yes":
            os.remove("Cards/" + self.cardNameComboBox.get() + ".txt")
            self.updateList()

    def exitApplication(self):
        root.destroy()

if __name__ == '__main__':
    root = tkinter.Tk()
    ttk.Style().configure("TButton", padding=6, relief="flat", background="#666666")
    run = DeckMGT(root)
    root.mainloop()


# -*- coding: utf-8 -*-
"""
Created on Sat Aug 13 15:49:14 2016

@author: Administrator
"""
'''
    adaboost方法，选择树分类器，效果没有随机森林好
    选择逻辑回归分类器，
'''
import numpy as np
from sklearn.ensemble import AdaBoostClassifier
from sklearn.grid_search import GridSearchCV
from sklearn.cross_validation import StratifiedShuffleSplit
from sklearn.metrics import classification_report,accuracy_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
import treeinterpreter.treeinterpreter as ti
import gc
import os
from sklearn.decomposition import PCA
from sklearn.pipeline import  Pipeline
from time import time

#%%
#load data
rootPath = r'G:\OneDrive\codes\python\RF\data'
#rootPath = r'D:\OneDrive\codes\python\RF\data'
trainPath = rootPath+r'\train'
#trainPath = rootPath+r'\train_filter'
trainlabPath = rootPath+r'\trainlab'
#testPath = rootPath+r'\test'
testPath = rootPath+r'\test'
testlabPath = rootPath+r'\testlab'

data1 = np.loadtxt(open(trainPath,"rb"),delimiter=",",skiprows=0,dtype=np.float)
lab1 = np.loadtxt(open(trainlabPath,"rb"),delimiter=",",skiprows=0,dtype=np.int)
data2 = np.loadtxt(open(testPath,"rb"),delimiter=",",skiprows=0,dtype=np.float)
lab2 = np.loadtxt(open(testlabPath,"rb"),delimiter=",",skiprows=0,dtype=np.int)
test = {'data':data2.transpose(),'lab':lab2}
train = {'data':data1.transpose(),'lab':lab1}

#ssp = StratifiedShuffleSplit(test['lab'],n_iter=1,test_size=0.9,random_state=1)
ssp = StratifiedShuffleSplit(train['lab'],n_iter=3,test_size=0.1,random_state=1)
for trainlab,testlab in ssp:
    print("train:\n%s\ntest:\n%s" % (trainlab,testlab))
    X = test['data'][trainlab];Xlab = test['lab'][trainlab]
    Y = test['data'][testlab];Ylab = test['lab'][testlab]

#%% pipeliene&cv
pca = PCA()
adaboost_tree = AdaBoostClassifier(base_estimator=DecisionTreeClassifier(max_features='auto',criterion="entropy"))
adaboost_log = AdaBoostClassifier(base_estimator=LogisticRegression(C=1000,class_weight=None,penalty='l2'
        ,solver='newton-cg',max_iter=200,tol=1E-5,n_jobs=1,verbose=3,
        multi_class='multinomial', fit_intercept=True))

#%% 选择模型
"""
 By default, weak learners are decision stumps.默认的基分类器是决策树
learning_rate:controls the contribution of the weak learners in the final combination.
main parameters to tune：n_estimators and the complexity of the base estimators
"""
#pipeline = Pipeline([('pca',pca),('ada',adaboost_tree)])
pipeline = Pipeline([('pca',pca),('ada',adaboost_log)])
#paramMap = {'ada__n_estimators':[175,150,200],'ada__learning_rate':[1E-3,1E-1,1],'ada__algorithm':["SAMME",'SAMME.R'],
#            'pca__n_components':[200,50,25,16],"pca__whiten":[True]}
paramMap = {'ada__n_estimators':[50],'ada__learning_rate':[1],'ada__algorithm':["SAMME",'SAMME.R'],
            'pca__n_components':[25],"pca__whiten":[True]}

#cv 选择一个cv生成器
#refit =False,表示不用整个数据集来refit,best_estimator
#grid_search = GridSearchCV(pipeline,paramMap,refit=False,n_jobs=1,cv=ssp)
grid_search = GridSearchCV(pipeline,paramMap,refit=True,n_jobs=3,cv=ssp)
#%%
# training
print("start training···")
start = time()
#grid_search.fit(test['data'],test['lab'])
grid_search.fit(train['data'],train['lab'])
print("GridSearchCV took %.2f seconds for %d cadidate param sets"
      %(time()-start,len(grid_search.grid_scores_)))

best_score= grid_search.best_score_
best_param = grid_search.best_params_
#%%
#testing
if os.path.exists(rootPath+'\prediction'):
    os.remove(rootPath+'\prediction')#删除文件
row,col = test['data'].shape
prediction = np.array([])
for i in range(int(np.ceil(row/1000.0))):
    start = 1000*i
#    prediction,bias,contributions, =ti.predict(rf,test['data'][start:min(start+20,col)])
    prediction = np.concatenate((prediction,grid_search.predict(test['data'][start:min(start+1000,row)])),axis=1)

#追加到文件
np.savetxt(open(rootPath+'\prediction','w'),prediction,delimiter = ',',fmt ='%d')

print("calssification report:")
report = classification_report(test['lab'],prediction)
acu=accuracy_score(test['lab'],prediction)


import sqlite3
from sqlite3 import Error

# need to create a db connection
def create_db(db_file):
    """ create a database connection to a SQLite database """
    conn = None
    try:
        conn = sqlite3.connect(db_file)
        print(sqlite3.version)
    except Error as e:
        print(e)
    finally:
        if conn:
            conn.close()

# where i defined the path to the database
if __name__ == '__main__':
    create_db(r"C:\Users\heath\PycharmProjects\final\commands\Courses.db")


from django.conf import settings
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from mptt.models import MPTTModel, TreeForeignKey

class Adressee(MPTTModel):
    """
    Adressee Table implimented with MPTT.
    """

    name = models.CharField(
        verbose_name=_("Название адресата"),
        help_text=_("Необходимо и не должно повторяться"),
        max_length=255,
        unique=True,
    )
    slug = models.SlugField(
        max_length=255, verbose_name=_("Adressee safe URL"), unique=True
    )
    parent = TreeForeignKey(
        "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
    )
    is_active = models.BooleanField(default=True)

    class MPTTMeta:
        order_insertion_by = ["name"]

    class Meta:
        verbose_name = _("Adressee")
        verbose_name_plural = _("Adressees")

    def get_absolute_url(self):
        return reverse("store:adressee_list", args=[self.slug])

    def __str__(self):
        return self.name

class Category(MPTTModel):
    """
    Category Table implimented with MPTT.
    """

    name = models.CharField(
        verbose_name=_("Category Name"),
        help_text=_("Required and unique"),
        max_length=255,
        unique=True,
    )
    slug = models.SlugField(
        verbose_name=_("Category safe URL"), max_length=255, unique=True
    )
    parent = TreeForeignKey(
        "self", on_delete=models.CASCADE, null=True, blank=True, related_name="children"
    )
    is_active = models.BooleanField(default=True)

    class MPTTMeta:
        order_insertion_by = ["name"]

    class Meta:
        verbose_name = _("Category")
        verbose_name_plural = _("Categories")

    def get_absolute_url(self):
        return reverse("store:category_list", args=[self.slug])

    def __str__(self):
        return self.name

class ProductType(models.Model):
    """
    ProductType Table will provide a list of the different types
    of products that are for sale.
    """

    name = models.CharField(
        verbose_name=_("Product Name"),
        help_text=_("Required"),
        max_length=255,
        unique=True,
    )
    is_active = models.BooleanField(default=True)

    class Meta:
        verbose_name = _("Product Type")
        verbose_name_plural = _("Product Types")

    def __str__(self):
        return self.name

class ProductSpecification(models.Model):
    """
    The Product Specification Table contains product
    specifiction or features for the product types.
    """

    product_type = models.ForeignKey(ProductType, on_delete=models.RESTRICT)
    name = models.CharField(
        verbose_name=_("Name"), help_text=_("Required"), max_length=255
    )

    class Meta:
        verbose_name = _("Product Specification")
        verbose_name_plural = _("Product Specifications")

    def __str__(self):
        return self.name

class Product(models.Model):
    """
    The Product table contining all product items.
    """

    adressee = models.ForeignKey(
        Adressee, on_delete=models.RESTRICT, null=True, blank=True
    )
    product_type = models.ForeignKey(ProductType, on_delete=models.RESTRICT)
    category = models.ForeignKey(Category, on_delete=models.RESTRICT)
    title = models.CharField(
        verbose_name=_("title"),
        help_text=_("Required"),
        max_length=255,
    )
    description = models.TextField(
        verbose_name=_("description"), help_text=_("Not Required"), blank=True
    )
    slug = models.SlugField(max_length=255)
    regular_price = models.DecimalField(
        verbose_name=_("Regular price"),
        help_text=_("Maximum 999.99"),
        error_messages={
            "name": {
                "max_length": _("The price must be between 0 and 999.99."),
            },
        },
        max_digits=15,
        decimal_places=2,
    )
    discount_price = models.DecimalField(
        verbose_name=_("Discount price"),
        help_text=_("Maximum 999.99"),
        error_messages={
            "name": {
                "max_length": _("The price must be between 0 and 999.99."),
            },
        },
        max_digits=15,
        decimal_places=2,
    )
    is_active = models.BooleanField(
        verbose_name=_("Product visibility"),
        help_text=_("Change product visibility"),
        default=True,
    )
    internalnumber = models.PositiveSmallIntegerField(
        blank=True, verbose_name="Внутренний номер"
    )
    inventorynumber = models.IntegerField(
        default=0, blank=True, verbose_name="Инвентарный №"
    )
    serialnumber = models.CharField(
        max_length=50, blank=True, default=0, verbose_name="Серийниый №"
    )
    rozport = models.PositiveSmallIntegerField(blank=True, verbose_name="Розетка(порт)")
    signature = models.BooleanField(default=False, verbose_name="Роспись")
    created_at = models.DateTimeField(
        _("Created at"), auto_now_add=True, editable=False
    )
    updated_at = models.DateTimeField(_("Updated at"), auto_now=True)
    users_wishlist = models.ManyToManyField(
        settings.AUTH_USER_MODEL, related_name="user_wishlist", blank=True
    )

    class Meta:
        ordering = ("created_at",)
        verbose_name = _("Product")
        verbose_name_plural = _("Products")

    def get_absolute_url(self):
        return reverse("store:product_detail", args=[self.slug])

    def __str__(self):
        return self.title

class ProductSpecificationValue(models.Model):
    """
    The Product Specification Value table holds each of the
    products individual specification or bespoke features.
    """

    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    specification = models.ForeignKey(ProductSpecification, on_delete=models.RESTRICT)
    value = models.CharField(
        verbose_name=_("value"),
        help_text=_("Product specification value (maximum of 255 words"),
        max_length=255,
    )

    class Meta:
        verbose_name = _("Product Specification Value")
        verbose_name_plural = _("Product Specification Values")

    def __str__(self):
        return self.value

class ProductImage(models.Model):
    """
    The Product Image table.
    """

    product = models.ForeignKey(
        Product, on_delete=models.CASCADE, related_name="product_image"
    )
    image = models.ImageField(
        verbose_name=_("image"),
        help_text=_("Upload a product image"),
        upload_to="images/",
        default="images/default.png",
    )
    alt_text = models.CharField(
        verbose_name=_("Alturnative text"),
        help_text=_("Please add alturnative text"),
        max_length=255,
        null=True,
        blank=True,
    )
    is_feature = models.BooleanField(default=False)
    created_at = models.DateTimeField(auto_now_add=True, editable=False)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        verbose_name = _("Product Image")
        verbose_name_plural = _("Product Images")