import connect_db as conn
import time
import os
import send_email as se
import pandas as pd
import numpy as np
import json
import re
from openpyxl.utils import get_column_letter, column_index_from_string
from openpyxl import load_workbook
from xlrd import open_workbook
from itertools import groupby

upload_task = {}
os.environ['NLS_LANG'] = 'SIMPLIFIED CHINESE_CHINA.UTF8'

# None to ''
def xstr(s):
    return '' if s is None else str(s).strip()

# Upload progress
def get_progress(user_key):
    global upload_task

    if user_key in upload_task:
        return upload_task[user_key]
    return 0

# Check username and password
def check_account(username, password):
    if not (username and password):
        print('用户名或密码为空')
        return False

    sql = "select 用户号 from erpbase..tblOperatorData where 用户号 = '%s' and 密码= '%s' " % (
        username, password)
    results = conn.MssConn.query(sql)
    if not results:
        print('查询不到数据')
        return False
    return True

# Get customer list
def get_cust_code_list():
    json_data = []

    sql = "SELECT DISTINCT CUSTOMERSHORTNAME FROM TBLTSVNPIPRODUCT ORDER BY CUSTOMERSHORTNAME "
    results = conn.OracleConn.query(sql)
    for row in results:
        result = {}
        result['value'] = xstr(row[0])
        result['label'] = xstr(row[0])

        json_data.append(result)
    return json_data

# Get customer po template
def get_po_template(cust_code):
    if not cust_code:
        print('客户代码不可为空')
        return []

    json_data = []
    sql = "SELECT CUST_CODE,TEMPLATE_FILE ,TEMPLATE_PIC ,KEY_LIST ,FILE_LEVEL,FILE_URL,ACCEPT,TEMPLATE_ID FROM CMP_CUST_PO_TEMPLATE WHERE CUST_CODE  = '%s'" % (
        cust_code)
    results = conn.OracleConn.query(sql)
    for row in results:
        result = {}
        result['file_name'] = xstr(row[1])
        result['img_url'] = xstr(row[2])
        result['file_key'] = xstr(row[3])
        result['level'] = xstr(row[4])
        result['file_url'] = xstr(row[5])
        result['accept'] = xstr(row[6])
        result['file_id'] = xstr(row[7])
        result['show_progress_flag'] = False
        result['show_filelist_flag'] = False
        result['load_progress'] = 0

        json_data.append(result)
    return json_data

# Upload po file
def upload_po_file(f, po_header):
    global upload_task
    upload_task[po_header['file_id']] = 0

    if not f:
        print('文件不存在')
        po_header['err_desc'] = '上传的文件不存在'
        return False

    file_dir = os.path.join(os.getcwd(), 'uploads/po/' +
                            po_header['po_type']+'/'+po_header['cust_code'])

    if not os.path.exists(file_dir):
        os.makedirs(file_dir)

    file_path = os.path.join(file_dir, f.filename)
    f.save(file_path)

    sql = "select PO_ITEM_SEQ.nextval from dual"
    po_header['upload_id'] = conn.OracleConn.query(sql)[0][0]

    if not parse_po_file(file_path, po_header):
        return False

    ret = get_upload_data(po_header['upload_id'])
    mail_attachment = []
    mail_attachment.append(file_path)
    mail_attachment.append(os.path.join(os.getcwd(), '已上传订单.xlsx'))
    send_mail(ret, po_header, mail_attachment)

    upload_task[po_header['file_id']] = 100

    return ret

# Sent mail
def send_mail(ret_data, po_header, mail_attachment):
    mail_keyid = po_header['upload_id']

    mail_body = get_mail_body(
        po_header['user_name'], mail_keyid, po_header['mail_tip'], ret_data)

    if po_header['user_name'] == '07885':
        sql = "select recv_user_to from erp_email_recv where email_type = 'WO_UPLOAD_RECV_MY' "
    else:
        sql = "select recv_user_to from erp_email_recv where email_type = 'WO_UPLOAD_RECV' "

    mail_recv = conn.OracleConn.query(sql)[0][0].split(',')

    sql = "select recv_user_cc from erp_email_recv where email_type = 'WO_UPLOAD_RECV' "
    mail_recv_cc = conn.OracleConn.query(sql)[0][0].split(',')

    mail_title = '已上传客户' + po_header['cust_code'] + '的订单, 明细请查看附件'

    se.send_email(mail_title, mail_body, mail_attachment,
                  mail_recv, mail_recv_cc)

# Get mail body
def get_mail_body(createBy, uploadid, addContent, json_data):
    # Get total data
    content = f'''

内勤人员：{createBy} 上传WO

{addContent}

以下为上传WO的汇总信息

                  序号	保税	客户代码	客户PO	客户机种	客户Fab机种	厂内机种	晶圆料号
                  客户LOTID	片数	DIES	上传人员工号	上传时间

          '''

    for row in json_data['total_data']:
        content = content + f'''
{row['id']}	{row['banded']}	{row['cust_code']}	{row['po_id']}
        {row['cust_device']}	{row['fab_device']}	{row['ht_pn']}	{row['wafer_pn']}
        {row['lot_id']}	{row['wafer_qty']}	{row['die_qty']}	{row['upload_by']}	{row['upload_date']}

        '''

    content = content + '
'

    # Get detail data
    content = content + f'''
以下为明细数据

                  序号	客户PO	客户机种	客户Fab机种	厂内机种
                  客户LOTID	waferID	grossdies	打标码

    '''

    for row in json_data['detail_data']:
        content = content + f'''
{row['id']}	{row['po_id']}
        {row['cust_device']}	{row['fab_device']}	{row['ht_pn']}
        {row['lot_id']}	{row['wafer_id']}	{row['gross_dies']}	{row['mark_code']}

        '''

    content = content + '
'

    return content

# Get upload data
def get_upload_data(upload_id):
    dict_data = {}
    json_data_total = []
    json_data_detail = []

    # Total data
    sql = f'''
    select row_number() over(ORDER BY bb.lotid) as 序号,case bb.substratetype when 'A' THEN '保税' when 'B' THEN '非保税' else '未知' end as 是否保税,
    bb.customershortname as 客户代码,aa.po_num,aa.mpn_desc as 客户机种名,aa.Fab_conv_id as 客户Fab机种,aa.mtrl_num as 厂内机种,cc.MARKETLASTUPDATE_BY AS 晶圆料号,
    bb.lotid AS LOTID,count(DISTINCT bb.wafer_id) AS 片,sum(bb.passbincount + bb.failbincount) Dies,bb.qtech_created_by, to_char(bb.qtech_created_date,'yyyy-MM-dd'),cc.residual
    from customeroitbl_test aa inner join mappingdatatest bb on to_char(aa.id) = bb.filename INNER JOIN TBLTSVNPIPRODUCT cc ON aa.CUSTOMERSHORTNAME  = cc.CUSTOMERSHORTNAME
    AND aa.MPN_DESC  = cc.CUSTOMERPTNO1 AND aa.FAB_CONV_ID  = cc.CUSTOMERPTNO2  where aa.wafer_visual_inspect = '{upload_id}'
    group by bb.customershortname,aa.Fab_conv_id,aa.mpn_desc,bb.lotid,aa.mtrl_num,bb.passbincount,bb.failbincount,aa.po_num,bb.qtech_created_by,
    to_char(bb.qtech_created_date,'yyyy-MM-dd'),bb.substratetype,cc.MARKETLASTUPDATE_BY,cc.residual
    '''
    results = conn.OracleConn.query(sql)
    for row in results:
        result = {}
        result['id'] = xstr(row[0])
        result['banded'] = xstr(row[1])
        result['cust_code'] = xstr(row[2])
        result['po_id'] = xstr(row[3])
        result['cust_device'] = xstr(row[4])
        result['fab_device'] = xstr(row[5])
        result['ht_pn'] = xstr(row[6])
        result['wafer_pn'] = xstr(row[7])
        result['lot_id'] = xstr(row[8])
        result['wafer_qty'] = row[9]
        result['die_qty'] = row[10]
        result['upload_by'] = xstr(row[11])
        result['upload_date'] = xstr(row[12])
        result['npi_owner'] = xstr(row[13])

        json_data_total.append(result)

    dict_data['total_data'] = json_data_total

    # Detail data
    sql = f'''
    select row_number() over(ORDER BY  bb.lotid,bb.substrateid) as 序号,case bb.substratetype when 'A' then '保税' when 'B' then '非保税' else '未知' end as 是否保税, bb.customershortname as 客户代码,
          aa.Fab_conv_id as FAB机种,aa.mpn_desc as 客户机种,cc.residual as NPI负责人员,
          aa.mtrl_num as 厂内机种,
          aa.po_num as PO_NUM,
          bb.lotid as LOT_ID,
          bb.wafer_id as WAFER_NO,
          bb.substrateid as WAFERID,
          bb.passbincount as GOOD_DIES,
          bb.failbincount as NG_DIES,
          bb.passbincount + bb.failbincount as GROSS_DIES,
          bb.productid as 打标码,
          aa.Imager_Customer_Rev as 二级代码, bb.qtech_created_by as 上传人员,bb.qtech_created_date as 上传时间,  bb.qtech_lastupdate_by as 更新人员, bb.qtech_lastupdate_date as 更新时间
     from customeroitbl_test aa
     left join tbltsvnpiproduct cc on cc.customerptno1 = aa.mpn_desc  and  cc.qtechptno = aa.mtrl_num  and cc.customershortname = aa.customershortname and cc.residual is not null
    inner join mappingdatatest bb
       on to_char(aa.id) = bb.filename
      and aa.wafer_visual_inspect = '{upload_id}'
      group by  bb.customershortname,cc.residual,aa.mtrl_num,aa.Fab_conv_id, aa.mpn_desc,aa.po_num,bb.lotid,bb.wafer_id,bb.substrateid,bb.passbincount,bb.failbincount,bb.productid,aa.Imager_Customer_Rev ,bb.substratetype,bb.qtech_created_by,bb.qtech_created_date,bb.qtech_lastupdate_by,bb.qtech_lastupdate_date
    '''

    results = conn.OracleConn.query(sql)
    for row in results:
        result = {}
        result['id'] = xstr(row[0])
        result['banded'] = xstr(row[1])
        result['cust_code'] = xstr(row[2])
        result['fab_device'] = xstr(row[3])
        result['cust_device'] = xstr(row[4])
        result['npi_owner'] = xstr(row[5])
        result['ht_pn'] = xstr(row[6])
        result['po_id'] = xstr(row[7])
        result['lot_id'] = xstr(row[8])
        result['wafer_no'] = xstr(row[9])
        result['wafer_id'] = xstr(row[10])
        result['good_dies'] = row[11]
        result['ng_dies'] = row[12]
        result['gross_dies'] = row[13]
        result['mark_code'] = xstr(row[14])
        result['second_code'] = xstr(row[15])
        result['upload_by'] = xstr(row[16])
        result['upload_date'] = xstr(row[17])
        result['update_by'] = xstr(row[18])
        result['update_date'] = xstr(row[19])

        json_data_detail.append(result)

    dict_data['detail_data'] = json_data_detail

    set_xl(json_data_total, json_data_detail)
    return dict_data

# Get cell value
def get_cell_val(row, col, data, header):
    if col > len(header) or (row-2) > len(data):
        val = ""
    else:
        val = data[row-3][header[col-1]]
    return val

# Set new xl
def set_xl(total_data, detail_data):
    wb = load_workbook('export_xl_template/template.xlsx')
    # Total
    header_list = ["id", "po_id", "cust_code",  "cust_device", "fab_device",
                   "ht_pn", "lot_id", "wafer_qty", "die_qty", "banded", "upload_by", "upload_date", "npi_owner"]

    ws = wb.get_sheet_by_name(wb.sheetnames[0])
    for row in range(3, len(total_data)+3):
        for col in range(1, len(header_list)):
            ws.cell(column=col, row=row, value=get_cell_val(
                row, col, total_data, header_list))

    # Detail
    header_list = ["id", "po_id", "cust_code",  "cust_device", "fab_device", "ht_pn", "lot_id",
                   "wafer_no", "wafer_id", "gross_dies", "good_dies", "ng_dies", "mark_code"]

    ws = wb.get_sheet_by_name(wb.sheetnames[1])

    for row in range(3, len(detail_data)+3):
        for col in range(1, 30):
            ws.cell(column=col, row=row, value=get_cell_val(
                row, col, detail_data, header_list))

    wb.save("已上传订单.xlsx")

# Parse po file
def parse_po_file(file_name, po_header):
    po_dict = get_po_config(po_header)
    if not po_dict:
        po_header['err_desc'] = '查询不到配置文件无法解析'
        return False

    file_type = po_dict['file_type']
    if not file_name.upper().endswith(file_type.upper()):
        po_header['err_desc'] = '您上传的文件和模板设定的文件类型不一致'
        return False

    if file_type == 'xlsx' or file_type == 'xls':
        if not parse_xlsx_file(file_name, po_header, po_dict):
            return False
    else:
        po_header['err_desc'] = '文件类型不支持解析'
        return False

    return True

# Get Json config
def get_po_config(po_header):
    sql = "SELECT TEMPLATE_CONFIG FROM CMP_CUST_PO_TEMPLATE WHERE TEMPLATE_ID  = %s" % (
        po_header['file_id'])
    results = conn.OracleConn.query(sql)
    if not results:
        print("无法获取配置文件")
        return False

    template_config = results[0][0]
    file_dir = os.path.join(os.getcwd(), template_config)

    f = open(file_dir, 'r', encoding="utf-8")
    po_dict = json.load(f)
    return po_dict

# Parse xlsx file
def parse_xlsx_file(file_name, po_header, po_dict):
    file_cfg = ['file_key', 'file_index', 'file_header', 'file_max_cols']
    for item in file_cfg:
        if not item in po_dict:
            po_header['err_desc'] = '配置文件错误，请联系IT处理'
            return False

    file_key = po_dict['file_key']
    file_index = po_dict['file_index']
    file_header = po_dict['file_header']
    file_max_cols = po_dict['file_max_cols']

    po_data = []
    df = pd.DataFrame(pd.read_excel(
        file_name, sheet_name=file_index, header=file_header, keep_default_na=False))
    for index, row in df.iterrows():
        po_row_data = {}
        for key in file_key:
            col_name = file_key[key]['position']['col_name']
            if col_name != "":
                if col_name in row:
                    po_row_data[key] = row[col_name]
            elif 'other_key' in po_dict:
                if key in po_dict['other_key']:
                    po_row_data[key] = get_cell_val_by_openpyxl(
                        file_name, po_dict['other_key'][key]['position'])
            else:
                po_row_data[key] = ''

        po_data.append(po_row_data)

    print(po_data)
    if not check_po_data(po_header, po_dict, po_data):
        return False

    if not save_po_data(po_header, po_dict, po_data):
        return False
    return True

# Get cell value by openpyxl/xlrd
def get_cell_val_by_openpyxl(file_name, key_postion):
    if file_name.endswith('.xls'):
        dict1 = thans_col_row_from_string(key_postion)
        cell_val = open_workbook(file_name).sheets()[0].cell_value(
            dict1['row'], dict1['col'])

    elif file_name.endswith('.xlsx'):
        wb = load_workbook(file_name)
        ws = wb.get_sheet_by_name(wb.sheetnames[0])
        cell_val = ws[key_postion].value

    print(cell_val)
    return cell_val

def thans_col_row_from_string(s):
    dict = {}
    ss = [''.join(list(g)) for k, g in groupby(s, key=lambda x: x.isdigit())]

    dict['col'] = column_index_from_string(ss[0]) - 1
    dict['row'] = int(ss[1]) - 1
    return dict

# Get list
def get_wafer_list(wafer_str):
    if wafer_str == "":
        return []
    wafer_str = str(wafer_str)
    wafer_str_new = re.sub(r'[_~-]', ' _ ', wafer_str)
    pattern = re.compile(r'[A-Za-z0-9_]+')
    result1 = pattern.findall(wafer_str_new)

    # extend
    for i in range(1, len(result1)-1):
        if result1[i] == '_':
            if result1[i-1].isdigit() and result1[i+1].isdigit():
                bt = []
                if int(result1[i-1]) < int(result1[i+1]):
                    for j in range(int(result1[i-1])+1, int(result1[i+1])):
                        bt.append(f'{j}')
                else:
                    for j in range(int(result1[i-1])-1, int(result1[i+1]), -1):
                        bt.append(f'{j}')
                result1.extend(bt)

    # remove '_'
    result2 = sorted(set(result1), key=result1.index)
    if '_' in result2:
        result2.remove('_')

    return result2

# Check po data
def check_po_data(po_header, po_dict, po_data):

    for item in po_data:
        # Check 0:necessary key
        necessary_key_list = ['po_id', 'customer_device', 'lot_id', 'wafer_id']
        for key in necessary_key_list:
            if not key in item:
                po_header['err_desc'] = '配置文件必要KEY不存在错误，或选错上传文件，无法解析上传文件，请联系IT处理'
                return False

        wafer_id_list = get_wafer_list(item['wafer_id'])
        if len(wafer_id_list) == 0:
            continue

        # Check 1:Wafer qty
        if 'wafer_qty' in item:
            wafer_qty = item['wafer_qty']
            if len(wafer_id_list) != int(wafer_qty):
                print('wafer qty和wafer list明细不一致')
                po_header['err_desc'] = 'wafer qty和wafer list明细不一致'
                return False

        # Check 2:customer code => customer device
        if 'cust_fab_device' in item:
            sql = f"SELECT count(1) FROM TBLTSVNPIPRODUCT t  WHERE t.CUSTOMERSHORTNAME  = '{po_header['cust_code']}' AND instr( '{item['cust_fab_device']}',t.CUSTOMERPTNO1) > 0  "
        else:
            sql = f"SELECT count(1) FROM TBLTSVNPIPRODUCT t  WHERE t.CUSTOMERSHORTNAME  = '{po_header['cust_code']}' AND t.CUSTOMERPTNO1  = '{item['customer_device']}'  "

        if conn.OracleConn.query(sql)[0][0] == 0:
            print('客户代码和客户机种无法对应，或者NPI对照表未维护对应关系')
            po_header['err_desc'] = '客户代码和客户机种无法对应，或者NPI对照表未维护对应关系'
            return False

    return True

# Save po data
def save_po_data(po_header, po_dict, po_data):
    global upload_task

    num = 0
    for item in po_data:
        wafer_id_list = get_wafer_list(item['wafer_id'])
        num = num + len(wafer_id_list)

    for item in po_data:
        wafer_id_list = get_wafer_list(item['wafer_id'])
        if len(wafer_id_list) == 0:
            continue

        for i in range(len(wafer_id_list)):
            insert_po_data(wafer_id_list[i], po_header, item)
            upload_task[po_header['file_id']
                        ] = upload_task[po_header['file_id']] + 100 / float(num)
            if upload_task[po_header['file_id']] >= 100:
                upload_task[po_header['file_id']] = 99

    return True

# Insert to DB
def insert_po_data(wafer_id, po_header, po_data):
    # Get data
    if wafer_id.isdigit() and (len(wafer_id) == 1):
        wafer_id = ('00' + wafer_id)[-2:]
    sql = "select CustomerBCtbl_SEQ.nextval ID from dual"
    max_id = conn.OracleConn.query(sql)[0][0]
    upload_id = po_header['upload_id']
    bonded = 'A' if po_header['bonded_type'] == '保税' else 'B'
    create_by = po_header['user_name']
    lot_id = po_data['lot_id']
    cust_code = po_header['cust_code']
    po_id = po_data['po_id']
    cust_device = po_data['customer_device']
    fab_device = po_data['fab_device']
    cust_fab_device = po_data['cust_fab_device'] if 'cust_fab_device' in po_data else ''
    product_code = po_data['add_0']
    ret = get_cust_pn_info(cust_code, cust_device,
                           fab_device, cust_fab_device, product_code)
    if not ret:
        po_header['err_desc'] = '无法对应NPI对照表唯一厂内机种'

    fab_device = ret['fab_device'] if ret else ''
    cust_device = ret['cust_device'] if ret else ''
    ht_pn = ret['ht_pn'] if ret else ''
    passbin_count = ret['gross_dies'] if ret else '0'
    failbin_count = '0'
    product_id = ret['product_id'] if ret else ''
    wafer_vesion = po_data['add_0']
    ship_comment = po_data['add_1']
    probe_ship_part_type = po_data['add_2']
    shipping_mst_level = po_data['add_2']
    reticle_level_71 = po_data['add_3']
    shipping_mst_260 = po_data['add_3']
    reticle_level_72 = po_data['add_4']
    reticle_level_73 = po_data['add_5']
    assembly_facility = po_data['add_6']
    batch_comment_assy = po_data['add_7']
    mark_code = po_data['mark_code']

    # Delete old wafer data
    delete_po_data('1', str(lot_id)+str(wafer_id))
    # Oracle insert
    sql = ''' insert into mappingDataTest(id,substrateid,substratetype,productid,micronlotid,lotid,Wafer_ID,passbincount,
              failbincount,CustomerShortName,flag,Qtech_Created_By,Qtech_Created_Date,filename)
              values( mappingData_SEQ.Nextval,'%s','%s','%s','%s',
                     '%s','%s','%s','%s','%s','Y','%s',sysdate,'%s')
          ''' % (str(lot_id)+str(wafer_id), bonded, mark_code, 'std_new', lot_id, wafer_id, passbin_count, failbin_count, cust_code, create_by, max_id)

    conn.OracleConn.exec(sql)

    sql = f''' insert into CustomerOItbl_test(id,po_num,wafer_visual_inspect,source_batch_id,SHIP_SITE,Test_site,FAB_CONV_ID,mpn_desc,
            Imager_Customer_Rev,Created_Date,mtrl_num,CustomerShortName,flag,Qtech_Created_By,Qtech_Created_Date,
            probe_ship_part_type,RETICLE_LEVEL_71,RETICLE_LEVEL_72,RETICLE_LEVEL_73,ASSEMBLY_FACILITY,BATCH_COMMENT_ASSY,
            jobno,date_code,shipping_mst_level,shipping_mst_260,TARGET_WAF_THICKNESS,COMP_CODE,SHIP_COMMENT)
            values( {max_id},'{po_id}','{upload_id}','{lot_id}','{cust_code}','HTKS','{fab_device}','{cust_device}','{wafer_vesion}','','{ht_pn}','{cust_code}',
            'Y','{create_by}',sysdate,'{probe_ship_part_type}','{reticle_level_71}','{reticle_level_72}','{reticle_level_73}','{assembly_facility}',
            '{batch_comment_assy}','{bonded}','','{shipping_mst_level}','{shipping_mst_260}','{lot_id}','HTKS','{ship_comment}')
          '''

    conn.OracleConn.exec(sql)

    # Sqlserver insert
    sql = ''' insert into [ERPBASE].[dbo].[tblmappingData](substrateid,substratetype,productid,lotid,Wafer_ID,passbincount,
              failbincount,CustomerShortName,flag,Qtech_Created_By,Qtech_Created_Date,filename)
              values('%s','%s','%s','%s','%s','%s','0','%s','Y','%s',getdate(),'%s')
          ''' % (str(lot_id)+str(wafer_id), bonded, mark_code, lot_id, wafer_id, passbin_count, cust_code, create_by, max_id)

    conn.MssConn.exec(sql)

    sql = f''' insert into [ERPBASE].[dbo].[tblCustomerOI](id,po_num,wafer_visual_inspect,source_batch_id,SHIP_SITE,Test_site,FAB_CONV_ID,mpn_desc,
            Imager_Customer_Rev,Created_Date,mtrl_num,CustomerShortName,flag,Qtech_Created_By,Qtech_Created_Date,
            probe_ship_part_type,RETICLE_LEVEL_71,RETICLE_LEVEL_72,RETICLE_LEVEL_73,ASSEMBLY_FACILITY,BATCH_COMMENT_ASSY,
            jobno,date_code,shipping_mst_level,shipping_mst_260,TARGET_WAF_THICKNESS,COMP_CODE,SHIP_COMMENT)
            values( {max_id},'{po_id}','{upload_id}','{lot_id}','{cust_code}','HTKS','{fab_device}','{cust_device}','{wafer_vesion}','','{ht_pn}','{cust_code}',
            'Y','{create_by}',getdate(),'{probe_ship_part_type}','{reticle_level_71}','{reticle_level_72}','{reticle_level_73}','{assembly_facility}',
            '{batch_comment_assy}','{bonded}','','{shipping_mst_level}','{shipping_mst_260}','{lot_id}','HTKS','{ship_comment}')
          '''
    conn.MssConn.exec(sql)

def get_cust_pn_info(cust_code, cust_device, fab_device, cust_fab_device, product_code):
    if cust_fab_device != '':
        sql = f''' SELECT QTECHPTNO,CUSTOMERDIEQTY,QTECHPTNO2,CUSTOMERPTNO2,CUSTOMERPTNO1 FROM TBLTSVNPIPRODUCT
            WHERE CUSTOMERSHORTNAME = '{cust_code}' and instr('{cust_fab_device}',CUSTOMERPTNO1) > 0
            AND   instr('{cust_fab_device}',CUSTOMERPTNO2) > 0 AND STRUCKSTR3 = '{product_code}'
            and substr(QTECHPTNO2 ,-3,2) <> 'WB' and QTECHPTNO NOT LIKE '%FT'
            AND MARKETLASTUPDATE_BY IS NOT null
            '''
    else:
        if fab_device != '':
            sql = f''' SELECT QTECHPTNO,CUSTOMERDIEQTY,QTECHPTNO2,CUSTOMERPTNO2,CUSTOMERPTNO1 FROM TBLTSVNPIPRODUCT
                WHERE CUSTOMERSHORTNAME = '{cust_code}' and CUSTOMERPTNO1  = '{cust_device}'
                and CUSTOMERPTNO2 = '{fab_device}'
                and substr(QTECHPTNO2 ,-3,2) <> 'WB' and QTECHPTNO NOT LIKE '%FT'
                AND MARKETLASTUPDATE_BY IS NOT null
                '''
        else:
            sql = f''' SELECT QTECHPTNO,CUSTOMERDIEQTY,QTECHPTNO2,CUSTOMERPTNO2,CUSTOMERPTNO1 FROM TBLTSVNPIPRODUCT
                WHERE CUSTOMERSHORTNAME = '{cust_code}' and CUSTOMERPTNO1  = '{cust_device}'
                and substr(QTECHPTNO2 ,-3,2) <> 'WB' and QTECHPTNO NOT LIKE '%FT'
                AND MARKETLASTUPDATE_BY IS NOT null
                '''

    results = conn.OracleConn.query(sql)

    if not results:
        print('无法获取NPI对照表对应机种信息')
        return False

    if len(results) > 1:
        print('无法获取唯一的NPI对照表对应机种信息')
        return False

    ret = {}
    ret['ht_pn'] = results[0][0]
    ret['gross_dies'] = results[0][1]
    ret['product_id'] = results[0][2]
    ret['fab_device'] = results[0][3]
    ret['cust_device'] = results[0][4]
    return ret

# Delete po data
def delete_po_data(flag_, del_id):
    if flag_ == '0':
        # Delete by lot
        sql = f"delete from mappingdatatest where lotid = '{del_id}' "
        conn.OracleConn.exec(sql)
        sql = f"delete from CustomerOItbl_test where source_batch_id = '{del_id}' "
        conn.OracleConn.exec(sql)
        sql = f"delete from [ERPBASE].[dbo].[tblmappingData] where lotid = '{del_id} "
        conn.MssConn.exec(sql)
        sql = f"delete from [ERPBASE].[dbo].[tblCustomerOI] where source_batch_id = '{del_id}' "
        conn.MssConn.exec(sql)
    elif flag_ == '1':
        # Delete by wafer
        sql = f"delete from CustomerOItbl_test where to_char(id) in (select filename from mappingdatatest where substrateid = '{del_id}') "
        conn.OracleConn.exec(sql)
        sql = f"delete from mappingdatatest where substrateid = '{del_id}' "
        conn.OracleConn.exec(sql)
        sql = f"delete from [ERPBASE].[dbo].[tblCustomerOI] where CONVERT(char(20),id) in (select filename from [ERPBASE].[dbo].[tblmappingData] where substrateid = '{del_id}') "
        conn.MssConn.exec(sql)
        sql = f"delete from [ERPBASE].[dbo].[tblmappingData] where substrateid = '{del_id}' "
        conn.MssConn.exec(sql)
    elif flag_ == '2':
        # Delete by uploadid
        sql = f"delete from mappingdatatest where filename in (select to_char(id) from CustomerOItbl_test where wafer_visual_inspect = '{del_id}') "
        conn.OracleConn.exec(sql)
        sql = f"delete from CustomerOItbl_test where wafer_visual_inspect = '{del_id}' "
        conn.OracleConn.exec(sql)
        sql = f"delete from [ERPBASE].[dbo].[tblmappingData] where filename in (select CONVERT(char(20),id)  from [ERPBASE].[dbo].[tblCustomerOI] where wafer_visual_inspect = '{del_id}') "
        conn.MssConn.exec(sql)
        sql = f"delete from [ERPBASE].[dbo].[tblCustomerOI] where wafer_visual_inspect = '{del_id}' "
        conn.MssConn.exec(sql)


from pandas.util._decorators import (  # noqa
    Appender,
    Substitution,
    cache_readonly,
)

from pandas.core.util.hashing import (  # noqa
    hash_array,
    hash_pandas_object,
)

def __getattr__(name):
    if name == "testing":
        import pandas.util.testing

        return pandas.util.testing
    else:
        raise AttributeError(f"module 'pandas.util' has no attribute '{name}'")


def get_name():
    """asks user's name and validates it"""
    name_is_string = False
    while name_is_string == False:
        name = input("Enter name/company's name: ")
        print("\n")
        if name.isalpha():
            print("Hi "+name+"! Welcome to our shop."+"\n"+"Take a look at our laptops and decide which one you want."+"\n")
            name_is_string = True
            return name
        else:
            print("Error: Please input a valid name.\n")

def select_id(inventory,operation_num):
    """asks for laptop id and validates it"""
    id_success = False
    while id_success == False:
        try:
            laptop_id = int(input("Enter the desired laptop id: "))
            print("\n")
            if operation_num == "1":
                if laptop_id <= 0 or laptop_id > len(inventory):
                    print("Error: ID does not exist\n")
                    # display_laptops()
                elif int(inventory[laptop_id-1][3]) == 0:
                    print("The product is currently out of stock. We apologize for the inconvenience.\n"+
                      "Please feel free to explore some of our other products that may meet your needs. \n")
                else :
                    id_success = True
                    return laptop_id
            elif operation_num == "2":
                if laptop_id <= 0 or laptop_id > len(inventory):
                    print("Error: ID does not exist\n")
                else:
                    id_success = True
                    return laptop_id
        except:
            print("Error: Invalid input for ID\n")

def select_qty(current_qty,operation_num):
    """asks for laptop quantity and validates it"""
    qty_success = False
    while qty_success == False:
        try:
            laptop_qty = int(input("Enter the desired quantity of laptop: "))
            print("\n")
            if operation_num == "1":
                if laptop_qty <= 0:
                    print("Error: Quantity cannot be negative or zero\n")
                elif laptop_qty > current_qty:
                    print("Unfortunately, at the moment we only have",current_qty,"available.\n")
                else:
                    qty_success = True
                    return laptop_qty
            elif operation_num == "2":
                if laptop_qty <= 0:
                    print("Error: Invalid quantity\n")
                elif laptop_qty > 100:
                    print("Please note that we are currently limiting orders to 100 per resellers.\n")
                else:
                    qty_success = True
                    return laptop_qty
        except:
            print("Error: Invalid input for quantity\n")

def add_to_cart(inventory,id,qty,cart):
    """adds the product, qty, rate, net amount to the list cart"""
    product = inventory[id-1][0]
    brand = inventory[id-1][1]
    rate = inventory[id-1][2].replace("$","")
    net_amount = int(rate)*qty

    cart.append([product,brand,str(qty),rate,str(net_amount)])
    return cart


import numpy as np
from matplotlib import pyplot as plt

num_epocs = 30

#file='./results/multilayer_perceptron/multilayer_perceptron_' + repr(num_epocs) + 'e.txt'
file='./results/multilayer_perceptron/training/tanh/multilayer_perceptron_128n_30e_f_tanh.txt'

epocas = np.loadtxt(file, delimiter='\t', skiprows=0,usecols=[0])
loss= np.loadtxt(file, delimiter='\t', skiprows=0,usecols=[1])
acc= np.loadtxt(file, delimiter='\t', skiprows=0,usecols=[2])

plt.figure()
plt.plot(epocas,acc)
plt.title("Variación de acc respecto a épocas")
plt.xlabel("Acc")
plt.ylabel("Número de épocas")

manager = plt.get_current_fig_manager()
manager.resize(*manager.window.maxsize())

plt.savefig('./results/multilayer_perceptron/training/multilayer_perceptron_' + repr(num_epocs) + 'e_acc.png')

plt.figure()
plt.plot(epocas,loss)
plt.title("Variación de loss respecto a épocas")
plt.xlabel("Loss")
plt.ylabel("Número de épocas")

manager = plt.get_current_fig_manager()
manager.resize(*manager.window.maxsize())

plt.savefig('./results/multilayer_perceptron/training/multilayer_perceptron_' + repr(num_epocs) + 'e_loss.png')


from backend.models.mysql_connection_pool import MySQLPool

class ListaModel:
    def __init__(self):
        self.mysql_pool = MySQLPool()

    def get_lista(self, id_lista):
        params = {'id_lista' : id_lista}
        rv = self.mysql_pool.execute("SELECT * from listas where id_lista=%(id_lista)s", params)
        data = []
        content = {}
        for result in rv:
            content = {'id_lista': result[0], 'id_curso': result[1], 'dni_alumno': result[2], 'estado': result[3]}
            data.append(content)
            content = {}
        return data

    def get_listas(self):
        rv = self.mysql_pool.execute("SELECT * from listas")
        data = []
        content = {}
        for result in rv:
            content = {'id_lista': result[0], 'id_curso': result[1], 'dni_alumno': result[2], 'estado': result[3]}
            data.append(content)
            content = {}
        return data

    def create_lista(self, id_curso, dni_alumno, estado):
        data = {
            'id_curso' : id_curso,
            'dni_alumno': dni_alumno,
            'estado': estado,
        }
        query = """insert into listas (id_curso, dni_alumno, estado)
            values (%(id_curso)s, %(dni_alumno)s, %(estado)s)"""
        cursor = self.mysql_pool.execute(query, data, commit=True)

        data['id_lista'] = cursor.lastrowid
        return data

    def update_lista(self, id_lista, id_curso, dni_alumno, estado):
        data = {
            'id_lista' : id_lista,
            'id_curso' : id_curso,
            'dni_alumno': dni_alumno,
            'estado': estado,
        }
        query = """update listas set id_curso = %(id_curso)s, dni_alumno = %(dni_alumno)s,
                    estado= %(estado)s where id_lista = %(id_lista)s"""
        cursor = self.mysql_pool.execute(query, data, commit=True)

        result = {'result':1}
        return result

    def delete_lista(self, id_lista):
        params = {'id_lista' : id_lista}
        query = """delete from listas where id_lista = %(id_lista)s"""
        self.mysql_pool.execute(query, params, commit=True)

        result = {'result': 1}
        return result


# pytest -v --driver Chrome --driver-path C:\\chromedrivers\\chromedriver.exe\\chromedriver tests/test_selenium_simple.py

from selenium import webdriver

import time
from selenium.webdriver.common.by import By
import pytest

def test_search_example():
    """ Search some phrase in google and make a screenshot of the page. """
    selenium = webdriver.Chrome('C:/chromedrivers/chromedriver.exe')
    # Open google search page:
    selenium.get('https://google.com')

    time.sleep(5)  # just for demo purposes, do NOT repeat it on real projects!

    # Find the field for search text input:
    search_input = selenium.find_element(by=By.NAME, value='q')

    # Enter the text for search:
    search_input.clear()
    search_input.send_keys('first test')

    time.sleep(5)  # just for demo purposes, do NOT repeat it on real projects!

    # Click Search:
    search_button = selenium.find_element(by=By.NAME, value='btnK')
    search_button.submit()
    # search_button.click()

    time.sleep(10)  # just for demo purposes, do NOT repeat it on real projects!

    # Make the screenshot of browser window:
    selenium.save_screenshot('result.png')
    selenium.close()
    selenium.quit()


def GetAnswerWithUnderscores(answer, letters_guessed):
    string = ''
    for chars in answer:
        if chars in letters_guessed:
            string += chars
        else:
            string += '_'
    return string
answer = GetAnswerWithUnderscores('welcome', 'mel')
print('GetAnswerWithUnderscores #1: expected _el__me, got', answer)
answer = GetAnswerWithUnderscores('quick', 'rstlne')
print('GetAnswerWithUnderscores #2: expected _____, got', answer)


# -*- coding: utf-8  -*-

import pytest

import collections
import itertools
import sys

from lxml import etree
import numpy as np
import xmlserdes as X
import xmlserdes.utils as XU
from xmlserdes.errors import XMLSerDesError, XMLSerDesWrongChildrenError

import re

make_TD = X.TypeDescriptor.from_terse

def list_product(*args):
    return list(itertools.product(*args))

class TestAtomicTypes(object):
    @pytest.mark.parametrize('td_func',
                             [X.Atomic, make_TD],
                             ids=['verbose', 'terse'])
    #
    def test_type_descriptor(self, td_func):
        for tp, val, exp_txt in [(int, 42, '42'),
                                 (float, 3.5, '3.5'),
                                 (str, '3 < 5', '3 < 5'),
                                 (np.uint8, np.uint8(42), '42')]:
            td = td_func(tp)
            elt = td.xml_element(val, 'foo')
            assert XU.str_from_xml_elt(elt) == '<foo>%s</foo>' % exp_txt
            val_round_trip = td.extract_from(elt, 'foo')
            assert type(val_round_trip) is tp
            assert val_round_trip == val

    @pytest.mark.parametrize(
        'dtype_str',
        ['i1', 'i2', 'i4', 'u1', 'u2', 'u4'])
    #
    def test_terse_string_descriptors(self, dtype_str):
        td = make_TD(dtype_str)
        tp = np.dtype(dtype_str).type

        elt = td.xml_element(19, 'foo')
        assert '<foo>19</foo>' == XU.str_from_xml_elt(elt)
        val_round_trip = td.extract_from(elt, 'foo')
        assert tp is type(val_round_trip)
        assert val_round_trip == 19

    def test_bad_terse_string_descriptors(self):
        with pytest.raises(TypeError, match='data type .* not understood'):
            make_TD('not-a-real-dtype-code')

    def test_bool(self):
        td = make_TD(bool)

        elt = td.xml_element(True, 'foo')
        assert XU.str_from_xml_elt(elt) == '<foo>true</foo>'
        assert td.extract_from(elt, 'foo') is True

        elt = td.xml_element(False, 'foo')
        assert XU.str_from_xml_elt(elt) == '<foo>false</foo>'
        assert td.extract_from(elt, 'foo') is False

    def test_bool_bad_serialize_values(self):
        td = make_TD(bool)

        with pytest.raises(XMLSerDesError,
                           match='expected True or False but got "42"') as exc_info:
            td.xml_element(42, 'foo')
        assert exc_info.value.xpath == []

    def test_bool_bad_deserialize_values(self):
        td = make_TD(bool)

        with pytest.raises(XMLSerDesError,
                           match='expected "true" or "false" but got "banana"') as exc_info:
            #
            bad_xml = etree.fromstring('<foo>banana</foo>')
            td.extract_from(bad_xml, 'foo')
        assert exc_info.value.xpath == ['foo']

    def test_int_bad_deserialize_values(self):
        td = make_TD(int)
        bad_xml = etree.fromstring('<foo>banana</foo>')
        with pytest.raises(XMLSerDesError,
                           match='could not parse "banana" as "int"') as exc_info:
            td.extract_from(bad_xml, 'foo')
        assert exc_info.value.xpath == ['foo']

@pytest.mark.skipif(sys.version_info < (3, 4),
                    reason='requires Python 3.4 or higher')
class TestAtomicEnum(object):
    def test_bad_enum(self):
        with pytest.raises(TypeError, match='expected Enum-derived type'):
            X.AtomicEnum(int)

    def test_bad_enum_value(self):
        from enum import Enum
        Animal = Enum('Animal', 'Cat Dog Rabbit')
        td = X.AtomicEnum(Animal)
        with pytest.raises(ValueError, match="expected instance of <enum 'Animal'>"):
            td.xml_element(42, 'bad-animal')

class BareRectangle(collections.namedtuple('BareRectangle', 'width height')):
    pass

class TestListTypes(object):
    @pytest.mark.parametrize('td',
                             [X.List(X.Atomic(int), 'wd'),
                              make_TD([int, 'wd'])],
                             ids=['verbose', 'terse'])
    #
    def test_type_descriptor(self, td):
        val = [42, 100, 99, 123]
        elt = td.xml_element(val, 'widths')
        assert (XU.str_from_xml_elt(elt) ==
                '<widths>%s</widths>' % ''.join('<wd>%d</wd>' % x for x in val))
        val_round_trip = td.extract_from(elt, 'widths')
        assert val_round_trip == val

    @pytest.mark.parametrize('list_descr,exc_re',
                             [([1, 2, 3], 'expected 1 or 2 elements'),
                              ([], 'expected 1 or 2 elements'),
                              ([BareRectangle], '1-elt list: .* has no "xml_default_tag"')],
                             ids=['three-elt', 'empty', 'no-default-tag'])
    #
    def test_bad_construction(self, list_descr, exc_re):
        with pytest.raises(ValueError, match=exc_re):
            make_TD(list_descr)

class TestNestedListTypes(object):
    @pytest.mark.parametrize('td',
                             [X.List(X.List(X.Atomic(int), 'wd'), 'stripe-group'),
                              make_TD([[int, 'wd'], 'stripe-group'])],
                             ids=['verbose', 'terse'])
    #
    def test_type_descriptor(self, td):
        groups = [[1, 2], [3, 4, 5]]

        elt = td.xml_element(groups, 'stripe-groups')

        exp_txt = ('<stripe-groups>%s</stripe-groups>'
                   % ''.join(('<stripe-group>%s</stripe-group>'
                              % ''.join('<wd>%d</wd>' % x for x in grp))
                             for grp in groups))

        assert exp_txt == XU.str_from_xml_elt(elt)

        groups_round_trip = td.extract_from(elt, 'stripe-groups')
        assert groups == groups_round_trip

class Rectangle(collections.namedtuple('BareRectangle', 'width height')):
    xml_descriptor = X.SerDesDescriptor([('width', X.Atomic(int)),
                                         ('height', X.Atomic(int))])

class TestRectangleEquality(object):
    def test_equality(self):
        r0 = Rectangle(42, 100)
        r1 = Rectangle(42, 100)
        assert r1 is not r0
        assert r1 == r0

class TestInstanceTypes(object):
    def test_bad_construction_verbose(self):
        with pytest.raises(ValueError, match='has no xml_descriptor'):
            X.Instance(BareRectangle)

    def test_bad_construction_terse(self):
        with pytest.raises(ValueError, match='no "xml_descriptor" attribute'):
            make_TD(BareRectangle)
        with pytest.raises(ValueError, match='unhandled terse descriptor'):
            make_TD(lambda x: x)

    @pytest.mark.parametrize('td',
                             [X.Instance(Rectangle),
                              make_TD(Rectangle)],
                             ids=['verbose', 'terse'])
    #
    def test_type_descriptor(self, td):
        rect = Rectangle(42, 100)
        elt = td.xml_element(rect, 'rect')
        assert XU.str_from_xml_elt(elt) == expected_rect_xml(42, 100)

        rect_round_trip = td.extract_from(elt, 'rect')
        assert rect_round_trip == rect

    def test_bad_xml_wrong_n_children(self):
        td = X.Instance(Rectangle)
        bad_xml = etree.fromstring('<rect>42100<c>123</c></rect>')
        with pytest.raises(XMLSerDesWrongChildrenError,
                           match=('mismatched children: '
                                  r'\[missing: width, missing: height, '
                                  r'unexpected: a, '
                                  r'unexpected: b, '
                                  r'unexpected: c\]')) as exc_info:
            td.extract_from(bad_xml, 'rect')
        assert exc_info.value.xpath == ['rect']

    def test_bad_xml_wrong_tag(self):
        td = X.Instance(Rectangle)
        bad_xml = etree.fromstring('<rect>42100</rect>')
        with pytest.raises(XMLSerDesError,
                           match='.*missing: width.*unexpected: a') as exc_info:
            td.extract_from(bad_xml, 'rect')
        assert exc_info.value.xpath == ['rect']

class _TestNumpyBase(object):
    @pytest.mark.parametrize(
        'x,regexp',
        [('hello', 'not ndarray'),
         (np.array([[1, 2], [3, 4]]), 'not 1-dimensional'),
         (np.zeros((12,), dtype=np.float32), 'expected dtype .* but got "float32"')],
        ids=['string', 'multi-dml', 'wrong-dtype'])
    #
    def test_bad_value(self, x, regexp):
        with pytest.raises(XMLSerDesError, match=regexp) as exc_info:
            self.td.xml_element(x, 'values')
        assert exc_info.value.xpath == []

class TestNumpyAtomic(_TestNumpyBase):
    def setup_method(self, method):
        self.td = X.NumpyAtomicVector(np.int32)

    def round_trip_1(self, xs, td, go_via_string):
        written_elt = td.xml_element(xs, 'values')
        read_elt = (etree.fromstring(XU.str_from_xml_elt(written_elt))
                    if go_via_string
                    else written_elt)
        xs_round_trip = td.extract_from(read_elt, 'values')
        assert xs_round_trip.shape == xs.shape
        assert np.all(xs_round_trip == xs)

    # flake8 doesn't realise this is defined when we use in below,
    # hence 'noqa' at that point.
    dtypes_to_test = [np.uint8, np.uint16, np.uint32, np.uint64,
                      np.int8, np.int16, np.int32, np.int64,
                      np.float32, np.float64]

    @pytest.mark.parametrize(
        'dtype,td_func,use_empty_xs,go_via_string',
        list_product(dtypes_to_test,
                     [X.NumpyAtomicVector, lambda dt: make_TD((np.ndarray, dt))],
                     [False, True],
                     [False, True]),
        ids=['-'.join(flds)
             for flds in list_product([dt.__name__ for dt in dtypes_to_test],  # noqa
                                      ['verbose', 'terse'],
                                      ['nonempty', 'empty'],
                                      ['via-xml-elt', 'via-string'])])
    #
    def test_round_trips(self, dtype, td_func, use_empty_xs, go_via_string):
        xs = np.array([] if use_empty_xs
                      else [-1.23, -9.99, 0.234, 42, 99, 100.11],
                      dtype=dtype)
        self.round_trip_1(xs, td_func(dtype), go_via_string)

    def test_content(self):
        xs = np.array([32, 42, 100, 99, -100], dtype=np.int32)
        elt = self.td.xml_element(xs, 'values')
        assert XU.str_from_xml_elt(elt) == '<values>32,42,100,99,-100</values>'

    def test_empty_content(self):
        xs = np.array([], dtype=np.int32)
        elt = self.td.xml_element(xs, 'values')
        assert XU.str_from_xml_elt(elt) == '<values></values>'

class TestNumpyAtomicConvenience(TestNumpyAtomic):
    def setup_method(self, method):
        self.td = X.NumpyVector(np.int32)

class TestNumpyScalarDType(object):
    def test_content(self):
        colour_dtype = np.dtype([('red', np.uint8),
                                 ('green', np.uint8),
                                 ('blue', np.uint8)])
        # The '[()]' indexing extracts scalar from 0-dim array:
        colour = np.array((25, 50, 75), dtype=colour_dtype)[()]
        td = make_TD(colour_dtype)
        elt = td.xml_element(colour, 'colour')
        assert XU.str_from_xml_elt(elt) == ('<colour>'
                                            '<red>25</red>'
                                            '<green>50</green>'
                                            '<blue>75</blue></colour>')

RectangleDType = np.dtype([('width', np.int32), ('height', np.int32)])

RectanglePairDType = np.dtype([('big', RectangleDType), ('small', RectangleDType)])

def remove_whitespace(s):
    return re.sub(r'\s+', '', s)

class TestNumpyRecordStructured(_TestNumpyBase):
    type_descr = X.NumpyRecordVectorStructured(RectangleDType, 'rect')

    def setup_method(self, method):
        self.td = TestNumpyRecordStructured.type_descr
        self.vals = np.array([(42, 100), (99, 12)], dtype=RectangleDType)

    @pytest.mark.parametrize('td',
                             [type_descr, make_TD((np.ndarray, RectangleDType, 'rect'))],
                             ids=['verbose', 'terse'])
    #
    def test_type_descriptor(self, td):
        xml_elt = td.xml_element(self.vals, 'rects')
        expected_xml = remove_whitespace(
            """<rects>
                 <rect><width>42</width><height>100</height></rect>
                 <rect><width>99</width><height>12</height></rect>
               </rects>""")
        assert XU.str_from_xml_elt(xml_elt) == expected_xml
        vals_rt = td.extract_from(xml_elt, 'rects')
        assert vals_rt.dtype == self.vals.dtype
        assert vals_rt.shape == self.vals.shape
        assert np.all(vals_rt == self.vals)

    @pytest.mark.parametrize(
        'bad_inner_str,exc_re,exp_xpath',
        [('<rect><width>42</width><height>100</height><depth>99</depth></rect>',
          'mismatched children',
          ['rectangles', 'rect[1]']),
         ('<rect><width>42</width><height>100</height></rect>'
          '<rect><width>42</width><height>100</height></rect>'
          '<rect><width>42</width><height>100</height><depth>99</depth></rect>',
          'mismatched children',
          ['rectangles', 'rect[3]']),
         ('<rect><wd>42</wd><ht>100</ht></rect>',
          'missing: width.*unexpected: wd',
          ['rectangles', 'rect[1]'])],
        ids=['wrong-n-elts', 'wrong-n-elts-third-child', 'wrong-child-tag'])
    #
    def test_bad_xml(self, bad_inner_str, exc_re, exp_xpath):
        bad_str = '<rectangles>%s</rectangles>' % bad_inner_str
        bad_xml = etree.fromstring(bad_str)
        with pytest.raises(XMLSerDesError, match=exc_re) as exc_info:
            self.td.extract_from(bad_xml, 'rectangles')
        assert exc_info.value.xpath == exp_xpath

class TestNumpyDTypeScalar(object):
    atomics_td = X.DTypeScalar(RectangleDType)
    atomics_val = np.array((42, 100), dtype=RectangleDType)[()]
    atomics_exp = '<rect><width>42</width><height>100</height></rect>'

    nested_td = X.DTypeScalar(RectanglePairDType)
    nested_val = np.array(((42, 100), (4, 10)), dtype=RectanglePairDType)[()]
    nested_exp = remove_whitespace(
        """<rect-pair>
               <big><width>42</width><height>100</height></big>
               <width>4</width><height>10</height>
           </rect-pair>""")

    @pytest.mark.parametrize(
        'type_descr,val,exp_xml,tag',
        [(atomics_td, atomics_val, atomics_exp, 'rect'),
         (nested_td, nested_val, nested_exp, 'rect-pair')],
        ids=['atomics', 'nested'])
    #
    def test_round_trip(self, type_descr, val, exp_xml, tag):
        assert val.shape == ()

        xml_elt = type_descr.xml_element(val, tag)
        assert XU.str_from_xml_elt(xml_elt) == exp_xml

        val_rt = type_descr.extract_from(xml_elt, tag)
        assert type(val_rt) == np.ndarray
        assert val_rt.dtype == val.dtype
        assert val_rt.shape == val.shape
        assert val_rt == val

    ColourDType = np.dtype([('r', np.uint8), ('g', np.uint8), ('b', np.uint8)])

    @pytest.mark.parametrize(
        'x,regexp',
        [('hello', 'not numpy scalar'),
         (np.array([1, 2]), 'not numpy scalar'),
         (np.array([[1, 2], [3, 4]]), 'not numpy scalar'),
         (np.array((12,), dtype=np.float32)[()], 'not numpy scalar'),
         (np.array((10, 20, 30), dtype=ColourDType)[()], 'expected dtype')],
        ids=['string', 'vector', 'multi-dml', 'wrong-dtype-atomic', 'wrong-dtype-structured'])
    #
    def test_bad_value(self, x, regexp):
        with pytest.raises(XMLSerDesError, match=regexp) as exc_info:
            self.atomics_td.xml_element(x, 'values')
        assert exc_info.value.xpath == []

class TestNumpyRecordStructuredNested(object):
    type_descr = X.NumpyRecordVectorStructured(RectanglePairDType, 'rect-pair')
    vals = np.array([((420, 100), (42, 10)), ((430, 110), (43, 11))],
                    dtype=RectanglePairDType)

    def test_round_trip(self):
        xml_elt = self.type_descr.xml_element(self.vals, 'rect-pairs')
        expected_xml = remove_whitespace(
            """<rect-pairs>
                 <rect-pair>
                     <big><width>420</width><height>100</height></big>
                     <width>42</width><height>10</height>
                 </rect-pair>
                 <rect-pair>
                     <big><width>430</width><height>110</height></big>
                     <width>43</width><height>11</height>
                 </rect-pair>
               </rect-pairs>""")
        assert expected_xml == XU.str_from_xml_elt(xml_elt)
        vals_rt = self.type_descr.extract_from(xml_elt, 'rect-pairs')
        assert vals_rt.dtype == self.vals.dtype
        assert vals_rt.shape == self.vals.shape
        assert np.all(vals_rt == self.vals)

class TestNumpyRecordStructuredConvenience(TestNumpyRecordStructured):
    def setup_method(self, method):
        TestNumpyRecordStructured.setup_method(self, method)
        self.td = X.NumpyVector(RectangleDType, 'rect')

class TestDescriptors(object):
    def setup_method(self, method):
        self.rect = BareRectangle(42, 100)

    @pytest.mark.parametrize(
        'descriptor_tup,exp_tag,exp_vslot',
        [(('width', X.Atomic(int)), 'width', 'width'),
         (('wd', 'width', X.Atomic(int)), 'wd', 'width'),
         (('wd', (lambda x: x.width), X.Atomic(int)), 'wd', None)],
        ids=['pair', 'triple-attr-name', 'triple-function'])
    #
    def test_tuple_construction(self, descriptor_tup, exp_tag, exp_vslot):
        elt_descriptor = X.ElementDescriptor.new_from_tuple(descriptor_tup)
        val = 42
        assert elt_descriptor.tag == exp_tag
        assert elt_descriptor.value_from(self.rect) == val
        assert elt_descriptor.value_slot == exp_vslot
        xml_elt = elt_descriptor.xml_element(self.rect)
        assert XU.str_from_xml_elt(xml_elt) == '<%s>%d</%s>' % (exp_tag, val, exp_tag)
        round_trip_val = elt_descriptor.extract_from(xml_elt)
        assert round_trip_val == val

    @pytest.mark.parametrize(
        'bad_arg,exc_tp,exc_re',
        [((1, 2, 3, 4), ValueError, 'length'),
         (42, TypeError, 'object .* has no len')],
        ids=['wrong-length', 'wrong-type'])
    #
    def test_bad_construction(self, bad_arg, exc_tp, exc_re):
        with pytest.raises(exc_tp, match=exc_re):
            X.ElementDescriptor.new_from_tuple(bad_arg)

def expected_rect_xml(w, h):
    return '<rect><width>%d</width><height>%d</height></rect>' % (w, h)

class TestObject(object):
    def setup_method(self, method):
        self.rect = Rectangle(42, 123)

    def test_1(self):
        serialized_xml = X.serialize(self.rect, 'rect')
        assert XU.str_from_xml_elt(serialized_xml) == expected_rect_xml(42, 123)

        rect_round_trip = X.deserialize(Rectangle, serialized_xml, 'rect')
        assert rect_round_trip == self.rect

    @pytest.mark.parametrize(
        'xml_str,des_tag,exc_re,exp_xpath',
        [('<rect><width>99</width></rect>', 'rect', 'mismatched children', ['rect']),
         (expected_rect_xml(42, 100), 'rectangle', 'expected tag .* but got', [])],
        ids=['wrong-n-children', 'wrong-tag'])
    #
    def test_bad_input(self, xml_str, des_tag, exc_re, exp_xpath):
        bad_xml = etree.fromstring(xml_str)
        with pytest.raises(XMLSerDesError, match=exc_re) as exc_info:
            X.deserialize(Rectangle, bad_xml, des_tag)
        assert exc_info.value.xpath == exp_xpath

class Layout(collections.namedtuple('Layout_', 'colour cornerprops stripes ids shape components')):
    xml_descriptor = X.SerDesDescriptor(
        [('colour', X.Atomic(str)),
         ('corner-properties', 'cornerprops', X.List(X.List(X.Atomic(str), 'prop'), 'corner')),
         ('stripes', X.List(X.Atomic(str), 'stripe-colour')),
         ('product-id-codes', 'ids', X.NumpyAtomicVector(np.uint32)),
         ('shape', X.Instance(Rectangle)),
         ('components', X.NumpyRecordVectorStructured(RectangleDType, 'rect'))])

class TestComplexObject(object):
    def test_1(self):
        layout = Layout('dark-blue',
                        [['rounded', 'red'],
                         ['chamfered', 'matt'],
                         ['pointy', 'anodized', 'black'],
                         ['bevelled', 'twisted', 'plastic-coated']],
                        ['red', 'burnt-ochre', 'orange'],
                        np.array([99, 42, 123], dtype=np.uint32),
                        Rectangle(210, 297),
                        np.array([(20, 30), (40, 50)], dtype=RectangleDType))
        xml = X.serialize(layout, 'layout')
        xml_str = XU.str_from_xml_elt(xml)
        expected_str = remove_whitespace(
            """<layout>
                 <colour>dark-blue</colour>
                 <corner-properties>
                   <corner>
                     <prop>rounded</prop>
                     <prop>red</prop>
                   </corner>
                   <corner>
                     <prop>chamfered</prop>
                     <prop>matt</prop>
                   </corner>
                   <corner>
                     <prop>pointy</prop>
                     <prop>anodized</prop>
                     <prop>black</prop>
                   </corner>
                   <corner>
                     <prop>bevelled</prop>
                     <prop>twisted</prop>
                     <prop>plastic-coated</prop>
                   </corner>
                 </corner-properties>
                 <stripes>
                   <stripe-colour>red</stripe-colour>
                   <stripe-colour>burnt-ochre</stripe-colour>
                   <stripe-colour>orange</stripe-colour>
                 </stripes>
                 <product-id-codes>99,42,123</product-id-codes>
                 <shape>
                   <width>210</width>
                   <height>297</height>
                 </shape>
                 <components>
                   <rect>
                     <width>20</width>
                     <height>30</height>
                   </rect>
                   <rect>
                     <width>40</width>
                     <height>50</height>
                   </rect>
                 </components>
               </layout>""")
        assert xml_str == expected_str
        X.deserialize(Layout, xml, 'layout')

class TestTerseErrorInputs(object):
    @pytest.mark.parametrize(
        'bad_tup,exc_re',
        [((), 'empty tuple'),
         ((42, 'hello', 'world'), 'numpy.ndarray as first'),
         ((np.ndarray, BareRectangle), 'atomic numpy type as second'),
         ((np.ndarray, 'foo', 'bar'), 'numpy dtype as second'),
         ((np.ndarray, 2, 3, 4), 'expected 2 or 3 elements but got 4')],
        ids=['empty', 'wrong-first-elt', '2-elt-not-dtype', '3-elt-not-dtype', 'wrong-length'])
    #
    def test_numpy_descriptor(self, bad_tup, exc_re):
        with pytest.raises(ValueError, match=exc_re):
            make_TD(bad_tup)


from typing import Optional

import sys
from os.path import dirname, abspath
dir = dirname(dirname(abspath(__file__)))
sys.path.append(dir)

from utils.tree import TreeNode

class BinaryTreeLevelOrder:

    def __init__(self) -> None:
        pass

    #
    #        3
    #      /   \
    #     9    20
    #          / \
    #         15  7
    # output = [[3], [9, 20], [15, 7]]

    #  (3, 0)
    # []

    def level_order(self, root: Optional[TreeNode]) -> list[list[int]]:
        results = []
        if root is None:
            return results

        queue = [ (root, 0) ]

        while queue:
            (current, level) = queue.pop(0)

            if len(results) - 1 < level:
                results.append([current.val])
            else:
                results[level].append(current.val)

            if current.left:
                queue.append((current.left, level + 1))
            if current.right:
                queue.append((current.right, level + 1))

        return results


import os
import time
from tqdm import tqdm
import torch
import pickle
import numpy as np
from torch import nn
import torch.nn.functional as F
import matplotlib.pyplot as plt
from torch.utils.data import TensorDataset,DataLoader

print(torch.__version__) # This code has been updated for PyTorch 1.0.0

# trainFeats, trainLabel = None, None

savePath = 'lecture5_output/'
# if not os.path.isdir(savePath):
#     os.makedirs(savePath)
#
#     # Loading the saved features
#     with open("trainFeats.pckl", "rb") as f:
#         trainFeats = pickle.load(f)
#         print(trainFeats)
#     with open("trainLabel.pckl", "rb") as f:
#         trainLabel = pickle.load(f)
#
#     with open("testFeats.pckl", "rb") as f:
#         testFeats = pickle.load(f)
#     with open("testLabel.pckl", "rb") as f:
#         testLabel = pickle.load(f)
#
#     # Generating 1-hot label vectors
#     trainLabel2 = np.zeros((50000, 10))
#     testLabel2 = np.zeros((10000, 10))
#     for d1 in range(trainLabel.shape[0]):
#         trainLabel2[d1, trainLabel[d1]] = 1
#     for d2 in range(testLabel.shape[0]):
#         testLabel2[d2, testLabel[d2]] = 1
#
#     print('Finished loading saved feature matrices from the disk!')

with open("trainFeats.pckl", "rb") as f:
    trainFeats = pickle.load(f)
    print(trainFeats)
with open("trainLabel.pckl", "rb") as f:
    trainLabel = pickle.load(f)

with open("testFeats.pckl", "rb") as f:
    testFeats = pickle.load(f)
with open("testLabel.pckl", "rb") as f:
    testLabel = pickle.load(f)

# Generating 1-hot label vectors
trainLabel2 = np.zeros((50000, 10))
testLabel2 = np.zeros((10000, 10))
for d1 in range(trainLabel.shape[0]):
    trainLabel2[d1, trainLabel[d1]] = 1
for d2 in range(testLabel.shape[0]):
    testLabel2[d2, testLabel[d2]] = 1

device = "cpu"
pinMem = False

# Defining the perceptron
class perceptron(nn.Module):
    def __init__(self,n_channels): #n_channels => length of feature vector
        super(perceptron, self).__init__()
        self.L = nn.Linear(n_channels,10) #Mapping from input to output
    def forward(self,x): #x => Input
        x = self.L(x) #Feed-forward
        x = F.softmax(x,dim=1) #Softmax non-linearity, dim=1 corresponds to labels
        return x

# Checking availability of GPU
# use_gpu = torch.cuda.is_available()
# if use_gpu:
#     print('GPU is available!')
#     device = "cuda"
#     pinMem = True
# else:
#     print('GPU is not available!')

# Creating pytorch dataset from the feature matices
trainDataset = TensorDataset(torch.from_numpy(trainFeats), torch.from_numpy(trainLabel2))
testDataset = TensorDataset(torch.from_numpy(testFeats), torch.from_numpy(testLabel2))
# Creating dataloader
trainLoader = DataLoader(trainDataset, batch_size=1, shuffle=True,num_workers=4, pin_memory=pinMem)
testLoader = DataLoader(testDataset, batch_size=1, shuffle=False,num_workers=4, pin_memory=pinMem)

# Definining the training routine
def train_model(model, criterion, num_epochs, learning_rate):
    start = time.time()
    train_loss = []  # List for saving the loss per epoch
    train_acc = []  # List for saving the accuracy per epoch
    tempLabels = []  # List for saving shuffled labels as fed into the network
    for epoch in range(num_epochs):
        epochStartTime = time.time()
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))

        running_loss = 0.0
        # Loading data in batches
        batch = 0
        for data in tqdm(trainLoader):
            inputs, labels = data

            inputs, labels = inputs.float().to(device), labels.float().to(device)

            # Initializing model gradients to zero
            model.zero_grad()
            # Data feed-forward through the network
            outputs = model(inputs)
            # Predicted class is the one with maximum probability
            _, preds = outputs.data.max(1)
            # Finding the MSE
            loss = criterion(outputs, labels)
            # Accumulating the loss for each batch
            running_loss += loss.item()

            # Backpropaging the error
            if batch == 0:
                totalLoss = loss
                totalPreds = preds
                tempLabels = labels.data.cpu()
                batch += 1
            else:
                totalLoss += loss
                totalPreds = torch.cat((totalPreds, preds), 0)
                tempLabels = torch.cat((tempLabels, labels.data.cpu()), 0)
                batch += 1

        totalLoss = totalLoss / batch
        totalLoss.backward()

        # Updating the model parameters
        for f in model.parameters():
            f.data.sub_(f.grad.data * learning_rate)

        epoch_loss = running_loss / 50000  # Total loss for one epoch
        train_loss.append(epoch_loss)  # Saving the loss over epochs for plotting the graph

        # Accuracy per epoch
        tempLabels = tempLabels.numpy()
        _, totalLabels = np.where(tempLabels == 1)
        epoch_acc = np.sum(np.equal(totalPreds.cpu().numpy(), np.array(totalLabels))) / 50000.0
        train_acc.append(epoch_acc * 100)  # Saving the accuracy over epochs for plotting the graph

        epochTimeEnd = time.time() - epochStartTime
        print('Average epoch loss: {:.6f}'.format(epoch_loss))
        print('Average epoch accuracy: {:.4f} %'.format(epoch_acc * 100))
        print('-' * 25)
        # Plotting Loss vs Epochs
        fig1 = plt.figure(1)
        plt.plot(range(epoch + 1), train_loss, 'r--', label='train')
        if epoch == 0:
            plt.legend(loc='upper right')
            plt.xlabel('Epochs')
            plt.ylabel('Loss')
            plt.title('Plot of training loss vs epochs')
        fig1.savefig(savePath + 'lossPlot.png')
        # Plotting Accuracy vs Epochs
        fig2 = plt.figure(2)
        plt.plot(range(epoch + 1), train_acc, 'g--', label='train')
        if epoch == 0:
            plt.legend(loc='upper left')
            plt.xlabel('Epochs')
            plt.ylabel('Accuracy')
            plt.title('Plot of training accuracy vs epochs')
        fig2.savefig(savePath + 'accPlot.png')

    time_elapsed = time.time() - start
    print('Training complete in {:.0f}m {:.0f}s'.format(
        time_elapsed // 60, time_elapsed % 60))
    return model

featLength = 2+5+2
# Initilaizing the model
model = perceptron(featLength).to(device)
criterion = nn.MSELoss()
model = train_model(model,criterion,num_epochs=100,learning_rate=1) # Training the model

# Finding testing accuracy
test_running_corr = 0
# Loading data in batches
batches = 0
testLabels = []

model.eval()  # Testing the model in evaluation mode

for tsData in tqdm(testLoader):
    inputs, labels = tsData

    inputs, labels = inputs.float().to(device), labels.float()

    with torch.no_grad():  # No back-propagation during testing; gradient computation is not required

        # Feedforward train data batch through model
        output = model(inputs)
        # Predicted class is the one with maximum probability
        _, preds = output.data.max(1)
        if batches == 0:
            totalPreds = preds
            testLabels = torch.argmax(labels, dim=1)  # Converting 1-hot vector labels to integer labels
            batches = 1
        else:
            totalPreds = torch.cat((totalPreds, preds), 0)
            testLabels = torch.cat((testLabels, torch.argmax(labels, dim=1)), 0)

        # Finding total number of correct predictions
ts_corr = np.sum(np.equal(totalPreds.cpu().numpy(), testLabels.numpy()))
# Calculating accuracy
ts_acc = ts_corr / testLabels.shape[0]
print('Accuracy on test set = ' + str(ts_acc * 100) + '%')