'''
Created on 2013-2-27

@author: tinyms
'''
import sys,hashlib,os,re
import concurrent.futures
import socket,json
import urllib.request
import urllib.parse
#from datetime import date,datetime
from bs4 import BeautifulSoup
from PyQt5 import QtCore,QtGui

import sqlite3

def connect_db():
    cnn = sqlite3.connect("_cache")
    #cnn.text_factory = str
    return cnn

def create_history_db():
    cnn = connect_db()
    cur = cnn.cursor()
    cur.executescript("""
    CREATE TABLE IF NOT EXISTS [Match] (
      [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
      [Score] NVARCHAR(5),
      [Detect_Result] NVARCHAR(3),
      [Asia] INT DEFAULT (0),
      [Result] REAL DEFAULT (0.00),
      [Actual_Result] INT DEFAULT (-1),
      [Top_Draw] REAL DEFAULT (0.00),
      [TeamNames] NVARCHAR(255),
      [Last10TextStyle] NVARCHAR(30),
      [Last6TextStyle] NVARCHAR(30),
      [Last4TextStyle] NVARCHAR(30),
      [Last4BattleHistoryDesc] NVARCHAR(20),
      [Odds_WL] NVARCHAR(20),
      [Odds_AM] NVARCHAR(20),
      [Odds_LB] NVARCHAR(20),
      [Odds_365] NVARCHAR(20),
      [Odds_YSB] NVARCHAR(20),
      [Odds_WL_Change] NVARCHAR(20),
      [Odds_AM_Change] NVARCHAR(20),
      [Odds_LB_Change] NVARCHAR(20),
      [Odds_365_Change] NVARCHAR(20),
      [Odds_YSB_Change] NVARCHAR(20),
      [EventName] NVARCHAR(20)
      );
    """)
    cnn.commit()
    cur.close()

def batch_save_history_matchs(ds):
    try:
        rows = []
        for row in ds:
            if row["db_actual_result"] == -1:
                continue
            companys = 0
            if row["Odds_WL"]:
                companys += 1
            if row["Odds_AM"]:
                companys += 1
            if row["Odds_LB"]:
                companys += 1
            if row["Odds_365"]:
                companys += 1
            if row["Odds_YSB"]:
                companys += 1
            if companys < 3:
                continue
            detect_result(row)
            #_batch_save_history_matchs_for_score(row)
            tmp = dict()
            tmp["Score"] = row["score"]
            tmp["Detect_Result"] = row["result"]
            tmp["Asia"] = row["db_asian"]
            tmp["Result"] = row["asia_avg_balls"]
            tmp["Actual_Result"] = row["db_actual_result"]
            tmp["TeamNames"] = " VS ".join(row["team_names"])
            tmp["Last10TextStyle"] = row["last_10_text_style"]
            tmp["Last6TextStyle"] = row["last_6_text_style"]
            tmp["Last4TextStyle"] = row["last_4_text_style"]
            tmp["Last4BattleHistoryDesc"] = row["last_4_status_text_style"]

            tmp["Odds_WL"] = row["Odds_WL"]#新增
            tmp["Odds_AM"] = row["Odds_AM"]#新增
            tmp["Odds_LB"] = row["Odds_LB"]#新增
            tmp["Odds_365"] = row["Odds_365"]#新增
            tmp["Odds_YSB"] = row["Odds_YSB"]#新增

            tmp["Top_Draw"] = 0.00
            if tmp["Odds_WL"]:
                nums = [float(f) for f in tmp["Odds_WL"].split(" ")]
                draw = nums[1]
                tmp["Top_Draw"] = draw - int(draw)

            tmp["Odds_WL_Change"] = row["Odds_WL_Change"]#新增
            tmp["Odds_AM_Change"] = row["Odds_AM_Change"]#新增
            tmp["Odds_LB_Change"] = row["Odds_LB_Change"]#新增
            tmp["Odds_365_Change"] = row["Odds_365_Change"]#新增
            tmp["Odds_YSB_Change"] = row["Odds_YSB_Change"]#新增

            tmp["EventName"] = row["season_name"]
            rows.append(tmp)
            pass

        cnn = connect_db()
        cur = cnn.cursor()
        sql = """
            INSERT INTO Match(Score,Detect_Result,Asia,Result,Actual_Result,Top_Draw,TeamNames,Last10TextStyle,Last6TextStyle,Last4TextStyle,Last4BattleHistoryDesc,
            Odds_WL,Odds_AM,Odds_LB,Odds_365,Odds_YSB,
            Odds_WL_Change,Odds_AM_Change,Odds_LB_Change,Odds_365_Change,Odds_YSB_Change,EventName)
            VALUES(:Score,:Detect_Result,:Asia,:Result,:Actual_Result,:Top_Draw,:TeamNames,:Last10TextStyle,:Last6TextStyle,:Last4TextStyle,:Last4BattleHistoryDesc,
            :Odds_WL,:Odds_AM,:Odds_LB,:Odds_365,:Odds_YSB,:Odds_WL_Change,:Odds_AM_Change,:Odds_LB_Change,:Odds_365_Change,:Odds_YSB_Change,:EventName)
        """
        cur.executemany(sql,rows)
        cnn.commit()
        cur.close()
    except:
        print("Insert to db failure.")
        info=sys.exc_info()
        print(info[0],":",info[1])

#初赔差异
def diff_wl_lb_first_odds(wl,lb):
    rt = {"flag":"","diff":""}
    if not wl or not lb:
        return rt
    wl_nums = [round(float(i),2) for i in wl.split(" ")]
    lb_nums = [round(float(i),2) for i in lb.split(" ")]
    #N,H,L => NoChange,High,Low
    if lb_nums[0]-wl_nums[0] > 0 :
        rt["flag"] = "H"
    elif lb_nums[0]-wl_nums[0] < 0 :
        rt["flag"] = "L"
    elif lb_nums[0]-wl_nums[0] == 0 :
        rt["flag"] = "N"

    if lb_nums[1]-wl_nums[1] > 0 :
        rt["flag"] += "H"
    elif lb_nums[1]-wl_nums[1] < 0 :
        rt["flag"] += "L"
    elif lb_nums[1]-wl_nums[1] == 0 :
        rt["flag"] += "N"

    if lb_nums[2]-wl_nums[2] > 0 :
        rt["flag"] += "H"
    elif lb_nums[2]-wl_nums[2] < 0 :
        rt["flag"] += "L"
    elif lb_nums[2]-wl_nums[2] == 0 :
        rt["flag"] += "N"

    items = list()
    items.append(lb_nums[0]-wl_nums[0])
    items.append(lb_nums[1]-wl_nums[1])
    items.append(lb_nums[2]-wl_nums[2])

    rt["diff"] = json.dumps(items)

    return rt

def list_history_matchs(model,event_name):
    cnn = connect_db()
    cnn.row_factory = sqlite3.Row
    cur = cnn.cursor()
    if model == "":
        sql2 = "SELECT * FROM match WHERE Event=:evt_name ORDER BY Model LIMIT 100"
        return cur.execute(sql2,{"evt_name":event_name})
    else:
        sql = "SELECT * FROM match WHERE Model=? AND Event=? LIMIT 100"
        return cur.execute(sql,(model,event_name))

def groupby_match_result(model,event_name):
    cnn = connect_db()
    cnn.row_factory = sqlite3.Row
    cur = cnn.cursor()
    if model == "":
        sql2 = "SELECT AR,COUNT(1) as scale FROM match WHERE Event=:evt_name GROUP BY AR LIMIT 100"
        return cur.execute(sql2,{"evt_name":event_name})
    else:
        sql = "SELECT AR,COUNT(1) as scale FROM match WHERE Model=? AND Event=? GROUP BY AR LIMIT 100"
        return cur.execute(sql,(model,event_name))

def cache_last_access_urls(url):
    if url.find("@")!=-1:
        return
    if url == "http://zc.trade.500.com/sfc/index.php":
        return
    if os.path.exists("data/urls-cache"):
        arr = json.loads(read_file("data/urls-cache"))
        if url in arr:
            return
        arr.append(url)
        if len(arr) > 20:
            write_file("data/urls-cache",json.dumps(arr[1:]))
        else:
            write_file("data/urls-cache",json.dumps(arr))
    else:
        arr = [url]
        write_file("data/urls-cache",json.dumps(arr))

def get_last_access_urls():
    if os.path.exists("data/urls-cache"):
        arr = json.loads(read_file("data/urls-cache"))
        arr.reverse()
        return arr
    return []

#utils funciton

#求列表排序后的中值
def median_value(numbers):
    return sum(numbers)/10

def median_value1(numbers):
    n = len(numbers)
    copy = numbers[:]
    copy.sort()
    if n & 1:
        print(int(n/2))
        return copy[int(n/2)]
    else:
        return (copy[int(n/2) - 1] + copy[int(n/2)]) / 2

#求列表中频繁出现的数字
def freq_num(numbers):
    results = set()

    freq = {}
    for i in range(len(numbers)):
        try:
            freq[numbers[i]] += 1
        except KeyError:
            freq[numbers[i]] = 1

    max = 0
    first_max_item = None
    print(freq)
    for k in freq:
        v = freq[k]
        if v > max:
            max = v
            first_max_item = k
    results.add(first_max_item)
    #find onter same nums item
    first_max_value = freq[first_max_item]
    for k in freq:
        v = freq[k]
        if first_max_value == v:
            results.add(k)

    return results

def trim_all(text):
    return "".join(text.split())

def md5(s):
    h = hashlib.new('ripemd160')
    h.update(bytearray(s.encode("utf8")))
    return h.hexdigest()

def url_with_params(url):
    r1 = urllib.parse.urlsplit(str(url))
    if r1.query!="":
        return True
    return False

def write_file(file_name,data):
    f = QtCore.QFile(file_name)
    if not f.open(QtCore.QIODevice.WriteOnly):
        return
    stream = QtCore.QTextStream(f)
    stream.setCodec("UTF-8")
    stream << data

def append_file(file_name,data):
    f = QtCore.QFile(file_name)
    if not f.open(QtCore.QIODevice.Append):
        return
    stream = QtCore.QTextStream(f)
    stream.setCodec("UTF-8")
    stream << data

def read_file(file_name):
    f = QtCore.QFile(file_name)
    if not f.open(QtCore.QIODevice.ReadOnly):
        return
    stream = QtCore.QTextStream(f)
    stream.setCodec("UTF-8")
    return str(stream.readAll())

def web_page_download(url,cached=True):
    soup = None
    while True:
        try:
            f = get_cache_web_file_name(url)
            if os.path.exists(f):
                html = read_file(f)
                soup = BeautifulSoup(html)
            else:
                web_page = urllib.request.urlopen(url,timeout=15)
                html = web_page.read()
                html = html.decode('gb18030')
                soup = BeautifulSoup(html)
                if url_with_params(url) and cached:
                    local_file = get_cache_web_file_name(url)
                    write_file(local_file,html)
            break
        except urllib.error.URLError as ex:
            info=sys.exc_info()
            #send_msg(info[1])
            print(info[0],":",info[1])
            continue
            #break
    return soup

def soup(local_file):
    if os.path.exists(local_file):
        html = read_file(local_file)
        return BeautifulSoup(html)
    return None

def mkdirs(path):
    isExists=os.path.exists(path)
    if not isExists:
        os.makedirs(path)
        return True
    else:
        return False

def get_number(text):
    p = re.compile("\\d+")
    nums = p.findall(text)
    if len(nums)>0:
        return nums[0]
    return ""

def get_date_text(text):
    p = re.compile("\\d{4}-\\d{2}-\\d{2}")
    dates = p.findall(text)
    if len(dates)>0:
        return dates[0]
    return ""

def get_short_time(text):
    p = "\\d{2}-\\d{2}\\s{1}\\d{2}:\\d{2}"
    r = re.compile(p)
    matchs = r.findall(text)
    if len(matchs)>0:
        return matchs[0]
    return ""

def get_int_numbers(text):
    p = re.compile("\\d+",re.M)
    return p.findall(text)

def get_float_numbers(text):
    p = re.compile("\\d+\\.\\d+",re.M)
    return p.findall(text)

def get_map_value(map_,key,defaut_=None):
    if map_.has_key(key):
        return map_[key]
    return defaut_

def get_zero_div(a,b,extra=0.01):
    try:
        r = a/b
    except ZeroDivisionError:
        r = a/(b+extra)
    return round(r,2)

def get_sina_vote_scale_url():
    parse = web_page_download("http://sports.sina.com.cn/iframe/lottery/sfc/iframe_index.html")
    if not parse:
        return ""
    links = parse.find_all("a")
    for link in links:
        text = link.get_text()
        if text == "火线伤停":
            return link["href"]
    return ""

def udp_server(deal_func):
    ip = ('127.0.0.1',MessageService.PORT)
    udp = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    while True:
        try:
            udp.bind(ip)
            break
        except:
            MessageService.PORT += 1
            ip = ('127.0.0.1',MessageService.PORT)
            continue

    while True:
        data,ip = udp.recvfrom(2028)
        if data == "close_message_loop":
            udp.close()
            break
        if data:
            if deal_func:
                deal_func.emit(str(data,encoding="utf8"))
    udp.close()

def send_msg(data):
    ip = ('127.0.0.1',MessageService.PORT)
    udp = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
    udp.sendto(data.encode("utf8"),ip)
    udp.close()

class MessageService(QtCore.QThread):
    PORT = 8888
    signal_message_arrival = QtCore.pyqtSignal(str)
    def __init__(self):
        QtCore.QThread.__init__(self)

    def run(self):
        try:
            udp_server(self.signal_message_arrival)
        except:
            info=sys.exc_info()
            print(info[0],":",info[1])

def DownloadWebPageTask(url):
    send_msg("下载 %s" % url)
    while True:
        try:
            web_page = urllib.request.urlopen(url,timeout=15)
            html = web_page.read()
            html = html.decode('gb18030')
            local_file = get_cache_web_file_name(url)
            write_file(local_file,html)
            break
        except urllib.error.URLError as ex:
            info=sys.exc_info()
            send_msg(info[1])
            print(info[0],":",info[1])
            send_msg("重下载 %s" % url)
            continue

def get_cache_web_file_name(url):
    return "cache_web_pages/%s/%s" % ("html",md5(url))

def batch_download_web_pages(urls):
    with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:
        for url in urls:
            f = get_cache_web_file_name(url)
            if os.path.exists(f):
                continue
            executor.submit(DownloadWebPageTask,url)
        send_msg("等待下载任务完成..")
        executor.shutdown()

def asian_rq(key):
    m = dict()
    m["平手"] = "0"
    m["平手/半球"] = "0.25"
    m["半球"] = "0.5"
    m["半球/一球"] = "0.75"
    m["一球"] = "1"
    m["一球/球半"] = "1.25"
    m["球半"] = "1.5"
    m["球半/两球"] = "1.75"
    m["两球"] = "2"
    m["两球/两球半"] = "2.25"
    m["两球半"] = "2.5"
    m["两球半/三球"] = "2.75"
    m["三球"] = "3"
    m["三球/三球半"] = "3.25"
    m["三球半"] = "3.5"
    m["三球半/四球"] = "3.75"
    m["四球"] = "4"
    sign = "-"
    if key:
        if key.find("受")!=-1:
            sign = "+"
            key = key.replace("受","")
        val = m.get(key)
        if val:
            return sign + val
    return ""

#For Matchs TreeView
class MatchsTreeItem(object):
    def __init__(self, data, parent=None):
        self.parentItem = parent
        self.itemData = data
        self.childItems = []

    def appendChild(self, item):
        self.childItems.append(item)

    def child(self, row):
        return self.childItems[row]

    def childCount(self):
        return len(self.childItems)

    def columnCount(self):
        return len(self.itemData)

    def data(self, column):
        try:
            if column == 0:
                return self.itemData["asia_avg_balls"]
            elif column == 1:
                return self.itemData["result"]
            elif column == 2:
                return self.itemData["actual_result"]
            elif column == 3:
                return self.itemData["odds_asian"]
            elif column == 4:
                return self.itemData["odds"]
            elif column == 5:
                return self.itemData['team_names']
            elif column == 6:
                return self.itemData['score']
            elif column == 7:
                return self.itemData["season_name"]
            elif column == 8:
                return self.itemData["match_date"]
            return ""
        except IndexError:
            return None

    def parent(self):
        return self.parentItem

    def row(self):
        if self.parentItem:
            return self.parentItem.childItems.index(self)
        return 0

#赛果预测
def detect_result(match):
    scores = []
    #scores += match["formula_total"].to_results()
    scores += match["formula_last10"].to_results()
    scores += match["formula_last6"].to_results()
    scores += match["formula_last4"].to_results()
    avg_balls = (match["formula_last10"].avg_balls + match["formula_last6"].avg_balls + match["formula_last4"].avg_balls)/3
    num = round(avg_balls,2)
    match["asia_avg_balls"] = "%.2f" % num
    #scores += match["formula_last_mid"].to_results()
    #r = Result(scores,match["odds"])
    #match["result"]=r.detect(match["formula_last4"])
    diff = abs(num)
    if diff>=0 and diff<=0.25:
        match["result"]= "1"
    elif diff>0.25 and diff < 0.75:
        if num > 0:
            match["result"]= "31"
        else:
            match["result"]= "10"
    elif diff >= 0.75:
        if num > 0:
            match["result"]= "3"
        else:
            match["result"]= "0"

class MatchsTreeModel(QtCore.QAbstractItemModel):
    HIDE_SCORE = False
    def __init__(self, data, parent=None):
        super(MatchsTreeModel, self).__init__(parent)
        titles = dict()
        titles["asia_avg_balls"]="实力差"
        titles["odds_asian"] = "欧亚"
        titles["result"] = "预测"
        titles["actual_result"]="赛果"
        titles["team_names"]="对阵球队"
        titles["score"]="比分"
        titles["odds"]="初盘(威廉)"
        titles["season_name"]="赛事"
        titles["match_date"]="比赛日期"
        self.rootItem = MatchsTreeItem(titles)

        self.setupModelData(data, self.rootItem)

    def columnCount(self, parent):
        if parent.isValid():
            return parent.internalPointer().columnCount()
        else:
            return self.rootItem.columnCount()

    def data(self, index, role):

        if not index.isValid():
            return None

        tip_index = -1
        if role == QtCore.Qt.DisplayRole:
            item = index.internalPointer()
            data = item.data(index.column())
            return data

        if role == QtCore.Qt.TextAlignmentRole:
            if 0==index.column() or 1==index.column() or 2==index.column():
                return QtCore.Qt.AlignCenter

        if role == QtCore.Qt.BackgroundRole:
            item = index.internalPointer()
            data = item.data(index.column())
            if index.column() == 4:
                if data:
                    arr = [float(s) for s in data.split(" ")]
                    draw = arr[1]
                    int_part = int(draw)
                    dot_part = draw - int_part
                    if round(dot_part,1) >= 0.4:
                        tip_index = index.column()
                        return QtGui.QColor("#FFFFCC")
                pass

            if 4 == index.column():
                return QtGui.QColor("#FFCC99")
            elif 0 == index.column():
                return QtGui.QColor("#FFFFCC")
            elif 1 == index.column():
                return QtGui.QColor("#99CC99")

        return None

    def flags(self, index):
        if not index.isValid():
            return QtCore.Qt.NoItemFlags

        return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable

    def headerData(self, section, orientation, role):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return self.rootItem.data(section)
        return None

    def index(self, row, column, parent):
        if not self.hasIndex(row, column, parent):
            return QtCore.QModelIndex()

        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()

        childItem = parentItem.child(row)
        if childItem:
            return self.createIndex(row, column, childItem)
        else:
            return QtCore.QModelIndex()

    def parent(self, index):
        if not index.isValid():
            return QtCore.QModelIndex()

        childItem = index.internalPointer()
        parentItem = childItem.parent()

        if not parentItem:
            return QtCore.QModelIndex()

        if parentItem == self.rootItem:
            return QtCore.QModelIndex()

        return self.createIndex(parentItem.row(), 0, parentItem)

    def rowCount(self, parent):
        if parent.column() > 0:
            return 0

        if not parent.isValid():
            parentItem = self.rootItem
        else:
            parentItem = parent.internalPointer()

        return parentItem.childCount()

    def convert_odds_to_balls(self,odds):
        if not odds:
            return "0"
        items = odds.split(" ")
        nums = [float(i) for i in items]
        win_rate = round(1/nums[0],2)
        #draw_rate = round(1/nums[1],2)
        lost_rate = round(1/nums[2],2)
        diff = win_rate-lost_rate
        sign = "-"
        if diff < 0:
            sign = "+"
        balls = "%s%.2f" % (sign,round(abs(diff)/0.5,2))
        return balls

    def setupModelData(self, matchs, parent):
        for match in matchs:
            match["asian_rq_text_style"] = "%s%s%s" % (match["rq"],match["asian"],match["score"])
            detect_result(match)
            #odds_detect_exclude_one_result(match)
            match["odds"] = match["odds"];
            match["odds_asian"] = self.convert_odds_to_balls(match["Odds_WL"])
            if match["actual_result"].find("-1")!=-1:
                match["actual_result"]=""

            if match["score"] == "[]":
                match["score"] = ""

            if type(match['team_names']) == list:
                if MatchsTreeModel.HIDE_SCORE:
                    team_names = ' VS '.join(match['team_names'])
                else:
                    team_names = ' VS '.join(match['team_names'])
            else:
                team_names = match['team_names']

            match["team_names"] = team_names
            match["match_date"] = match["match_date"]
            first_child = MatchsTreeItem(match)
            parent.appendChild(first_child)
        pass


from ..lib import utils
from pyramid_restler.view import RESTfulView
from pyramid.response import Response
from pyramid.exceptions import HTTPNotFound
from pyramid.httpexceptions import HTTPFound

def response_callback(request, response):
    #response.headerlist.append(('x', 'y'))
    try:
        if request.context.session.is_active:
            request.context.session.close()
    except:
        pass

    if 'callback' in request.params:
        response.text = request.params['callback'] + '(' + response.text + ')'

class CustomView(RESTfulView):

    """ Default Customized View Methods
    """
    def __init__(self, context, request):
        self.context = context
        self.request = request
        self.request.add_response_callback(response_callback)
        self.base_name = self.request.matchdict.get('base')

    def get_base(self):
        """ Return Base object
        """
        return self.context.get_base()

    def set_base(self, base_json):
        """ Set Base object
        """
        return self.context.set_base(base_json)

    def get_collection(self, render_to_response=True):
        """ Search database objects
        """
        params = self.request.params.get('$$', '{}')
        query = utils.json2object(params)
        try:
            collection = self.context.get_collection(query)
        except Exception as e:
            raise Exception('SearchError: %s' % e)
        if render_to_response:
            response = self.render_to_response(collection)
        else:
            response = collection
        return response

    def get_member(self):
        id = self.request.matchdict['id']
        self.wrap = False
        member = self.context.get_member(id)
        return self.render_to_response(member)

    def create_member(self):
        member = self.context.create_member(self._get_data())
        id = self.context.get_member_id_as_string(member)
        return self.render_custom_response(id, default_response=id)

    def update_member(self):
        id = self.request.matchdict['id']
        member = self.context.get_member(id, close_sess=False)
        if member is None:
            raise HTTPNotFound()
        self.context.update_member(member, self._get_data(member))
        return self.render_custom_response(id, default_response='UPDATED')

    def delete_member(self):
        id = self.request.matchdict['id']
        member = self.context.delete_member(id)
        if member is None:
            raise HTTPNotFound()
        return Response('DELETED', charset='utf-8', status=200, content_type='')

    def render_custom_response(self, id, default_response):
        _return = self.request.params.get('return', '')
        is_valid_return = hasattr(self.context.entity, _return)
        if is_valid_return:
            member = self.context.get_member(id)
            response_attr = getattr(member, _return)
            return Response(str(response_attr), charset='utf-8', status=200, content_type='application/json')
        else:
            return Response(default_response, charset='utf-8', status=200, content_type='')


import os
from collections import defaultdict

def pretty_print(img_dict: dict):
	mini = min(img_dict.keys(), key=lambda x: x[0])[0]
	maxi = max(img_dict.keys(), key=lambda x: x[0])[0]
	minj = min(img_dict.keys(), key=lambda x: x[1])[1]
	maxj = max(img_dict.keys(), key=lambda x: x[1])[1]
	for i in range(mini, maxi + 1):
		for j in range(minj, maxj + 1):
			print(f"{'#' if img_dict[(i,j)]==1 else '.'}", end='')
		print()

cwd = os.path.dirname(os.path.abspath(__file__))
with open(f"{cwd}/input.txt") as f:
	enhance_str = f.readline().strip()
	enhance = [1 if c == '#' else 0 for c in enhance_str]
	img_str = [l for l in f.read().strip().splitlines()]
img_dict = defaultdict(int)
img_dict_odd = defaultdict(lambda: 1)
for i, l in enumerate(img_str):
	for j, c in enumerate(l):
		if c == '#':
			img_dict[(i, j)] = 1
			img_dict_odd[(i, j)] = 1
		else:
			img_dict[(i, j)] = 0
			img_dict_odd[(i, j)] = 0

for n in range(50):
	if n == 2:
		print(f"p1: {sum(img_dict.values())}")
	mini = min(img_dict.keys(), key=lambda x: x[0])[0]
	maxi = max(img_dict.keys(), key=lambda x: x[0])[0]
	minj = min(img_dict.keys(), key=lambda x: x[1])[1]
	maxj = max(img_dict.keys(), key=lambda x: x[1])[1]
	updates = dict()
	for i in range(mini - 1, maxi + 2):
		for j in range(minj - 1, maxj + 2):
			binary_str = []
			for i2 in range(-1, 2):
				for j2 in range(-1, 2):
					if enhance[0] == 1 and n % 2 != 0:
						binary_str.append(str(img_dict_odd[(i + i2, j + j2)]))
					else:
						binary_str.append(str(img_dict[(i + i2, j + j2)]))
			binary = int(''.join(binary_str), 2)
			updates[(i, j)] = enhance[binary]
	for k, v in updates.items():
		img_dict[k] = v
		img_dict_odd[k] = v

print(f"p2: {sum(img_dict.values())}")


from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import sys
from os import path
sys.path.append(path.join(path.dirname(path.realpath(__file__)), '..'))

from ansible.module_utils.basic import AnsibleModule
from ansible.errors import AnsibleError
from ansible.playbook.conditional import Conditional
from ansible.plugins.action import ActionBase
from ansible.module_utils.six import string_types
from ansible.parsing.yaml.objects import AnsibleUnicode
from filter_plugins import variables
from utils import parameters
from utils import dicts

class ActionModule(ActionBase):
    ''' handler for assert_that_variables operations '''

    _ASSERT_ARGS_SPEC = {
        'HasValue': {
            'type': [ AnsibleUnicode, [AnsibleUnicode], [AnsibleUnicode,dict] ],
            'default': [],
            'tolist': True
            },
        'HasIntValue': {
            'type': [ AnsibleUnicode, [AnsibleUnicode], [AnsibleUnicode,dict] ],
            'default': [],
            'tolist': True
            },
        'HasBoolValue': {
            'type': [ AnsibleUnicode, [AnsibleUnicode], [AnsibleUnicode,dict] ],
            'default': [],
            'tolist': True
            },
        'HasValueIn':   {
            'type': [ [AnsibleUnicode,list], [AnsibleUnicode,list,dict] ],
            'default': [],
            'tolist': True
            },
        'MutuallyHasValueOrNot': {
            'type': [ list, [list,dict] ],
            'default': [],
            'tolist': True
            }
    }
    _EXTRA_ARGS_SPEC = {
        'fail_msg': {
            'type': str,
            'default': 'Assertion failed'
            },
        'success_msg': {
            'type': str,
            'default': 'All assertions passed'
            },
        'required_one_of' : [
            'HasValue', 'HasIntValue', 'HasBoolValue', 'HasValueIn', 'MutuallyHasValueOrNot']
    }
    _ARGS_SPEC = dicts.merge(_ASSERT_ARGS_SPEC, _EXTRA_ARGS_SPEC)
    _VALID_ARGS = frozenset(tuple(_ASSERT_ARGS_SPEC.keys()) + tuple(_EXTRA_ARGS_SPEC.keys()))

    def _init_global_vars(self, tmp=None, task_vars=None):
        ''' initialize class variables '''

        # Initialize simple vars
        self._task_vars = dict() if task_vars is None else task_vars

        # Initialize tasj result
        self._result = super(ActionModule, self).run(tmp, task_vars)
        self._result['_ansible_verbose_always'] = True
        self._result['changed'] = False
        self._result['failed'] = False

        del tmp  # tmp no longer has any effect

        # Define an AnsibleModule object
        # To check parameters
        self._params = parameters.ParametersSpec(self._ARGS_SPEC, self._task_vars, self._task.args)

    def _do_all_assert(self):
        ''' perform all the asserts '''

        for arg in self._ASSERT_ARGS_SPEC:
            for param_value in self._params[arg]:

                # Parse all the parameters values
                # Defined variable name, extra condition
                # and valid values list
                variable = None
                variable_list = []
                values = []
                extra_cond = {}
                if arg == 'MutuallyHasValueOrNot':
                    variable_list = param_value
                    if len(param_value) == 2 and type(param_value[1]) == list:
                        variable_list = param_value[0]
                        extra_cond = param_value[1]
                elif arg == 'HasValueIn':
                    variable = param_value[0]
                    values = param_value[1]
                    if len(param_value) == 3:
                        extra_cond = param_value[2]
                else:
                    variable = param_value
                    if isinstance(variable, list):
                        variable = param_value[0]
                        if len(param_value) == 2:
                            extra_cond = param_value[1]

                # If there is no when condition or
                # if the when condition is evaluated
                # to True then performs the check
                if (  'when' not in extra_cond.keys() or
                      self._do_assert(extra_cond['when'], fill_result=False) ):

                    # Define allow empty params
                    allow_empty = (
                        variables.to_bool(extra_cond['allow_empty'], default=False)
                        if 'allow_empty' in extra_cond.keys()
                        else False )
                    allow_empty = ('not {0}|is_set or ' if allow_empty else '{0}|is_set and ').format(variable)

                    if arg == 'MutuallyHasValueOrNot':
                        self._do_assert((
                            '({0}|is_set) or (not {1}|is_set)'
                        ).format(
                            '|is_set and '.join(variable_list),
                            '|is_set and not '.join(variable_list)
                        ), arg)
                    elif arg == 'HasValueIn':
                        self._do_assert("{0}{1}|in_list(['{2}'])".format(
                            allow_empty,
                            variable,
                            "','".join(values)
                        ), arg)
                    else:
                        # Defined assert method to use
                        method = 'is_set'
                        if arg == 'HasIntValue':
                            method = 'is_int'
                        elif arg == 'HasBoolValue':
                            method = 'is_bool'

                        # Performing assertions
                        if self._do_assert('{0}{1}|{2}'.format(allow_empty, variable, method), arg):
                            for condition in extra_cond.keys():
                                if condition == 'min':
                                    self._do_assert('{0}{1}>={2}'.format(allow_empty, variable, extra_cond[condition]), arg)
                                elif condition == 'max':
                                    self._do_assert('{0}{1}<={2}'.format(allow_empty, variable, extra_cond[condition]), arg)

        # If no error found then return success message
        if not self._result['failed']:
            self._result['msg'] = self._params['success_msg']

    def _do_assert(self, condition, category="", fill_result=True):
        ''' perform a single assert and fill the result '''

        cond = Conditional(loader=self._loader)
        cond.when = [AnsibleUnicode(condition)]

        test_result = cond.evaluate_conditional(templar=self._templar, all_vars=self._task_vars)
        if fill_result and not test_result:
            self._result['failed'] = True
            self._result['evaluated_to'] = False
            if 'assertion' not in self._result.keys():
                self._result['assertion'] = []
            self._result['assertion'].append('{0}> {1}'.format(category, condition))
            self._result['msg'] = self._params['fail_msg']

        return test_result

    def run(self, tmp=None, task_vars=None):
        ''' run the action plugin '''

        # Initializing module global vars
        self._init_global_vars(tmp, task_vars)

        # Launch assertions
        self._do_all_assert()

        return self._result


# -*- coding: utf-8 -*-
import pygame
import sys
import json

class Settings():
    """ Store the settings """

    def __init__(self, settings_json_file):
    # Initialize settings

        # Load json file first
        with open(settings_json_file, 'r', encoding='utf-8') as f:
            load_data = json.load(f)
            window_size = int(load_data['window_size'])
            if window_size < 15 or window_size > 30:
                print("[INFO] Program settings argument 'window_size' error. Fail to launch.")
                sys.exit()
            route_points = int(load_data['route_points'])
            if route_points < 10 or route_points > 100:
                print("[INFO] Program settings argument 'route_points' error. Fail to launch.")
                sys.exit()
            lazer_shown = bool(load_data['lazer_shown'])

        # Set screen size
        self.screen_scale = window_size  # (fit 15~30  best 25) decided in json file
        self.screen_offset = 30  # (best 30)

        # Set colors and fonts
        self.bg_color = (230, 230, 230)
        self.eg_color_1 = (255, 153, 51)
        self.eg_color_2 = (255, 180, 50)
        self.title_color = (20, 20, 20)
        self.choose_color = (225, 205, 90)
        self.box_color_1 = (100, 100, 200)
        self.box_color_2 = (100, 200, 100)
        self.box_color_3 = (200, 100, 100)
        self.box_color_3_no_key = (150, 100, 100)
        self.team_route_color = (50, 50, 220)
        self.enemy_route_color = (220, 50, 50)
        self.person_route_color = (220, 50, 220)
        self.font_color_1 = (254, 67, 101)
        self.font_color_2 = (131, 175, 161)
        self.title_font1 = pygame.font.SysFont("Times New Roman", int(2.2 * self.screen_scale), True, True)
        self.title_font2 = pygame.font.SysFont("Times New Roman", int(1.1 * self.screen_scale), True, True)
        self.title_font3 = pygame.font.SysFont("Times New Roman", int(1.2 * self.screen_scale), True, True)
        self.title_font4 = pygame.font.SysFont("Consolas", int(0.6 * self.screen_scale) - 1, True, True)
        self.bottom_font = pygame.font.SysFont("Times New Roman", int(0.5 * self.screen_scale + 1), True, True)

        # Load the icons (using relative path in Windows)
        icon_size = int(1.8 * self.screen_scale)
        self._img_protected = pygame.image.load(r".\images\protected.png")
        self._img_protected = pygame.transform.scale(self._img_protected, (icon_size, icon_size))
        self._img_mission1 = pygame.image.load(r".\images\mission1.png")
        self._img_mission1 = pygame.transform.scale(self._img_mission1, (icon_size, icon_size))
        self._img_mission1_u = pygame.image.load(r".\images\mission1_u.png")
        self._img_mission1_u = pygame.transform.scale(self._img_mission1_u, (icon_size, icon_size))
        self._img_mission2 = pygame.image.load(r".\images\mission2.png")
        self._img_mission2 = pygame.transform.scale(self._img_mission2, (icon_size, icon_size))
        self._img_mission2_u = pygame.image.load(r".\images\mission2_u.png")
        self._img_mission2_u = pygame.transform.scale(self._img_mission2_u, (icon_size, icon_size))
        self._img_mission3 = pygame.image.load(r".\images\mission3.png")
        self._img_mission3 = pygame.transform.scale(self._img_mission3, (icon_size, icon_size))
        self._img_mission3_u = pygame.image.load(r".\images\mission3_u.png")
        self._img_mission3_u = pygame.transform.scale(self._img_mission3_u, (icon_size, icon_size))

        # Load the progress bar (using relative path in Windows)
        self._progbar1 = pygame.image.load(r".\images\prog_bar1.png")
        self._progbar1 = pygame.transform.scale(self._progbar1, (int(12 * self.screen_scale), int(1.2 * self.screen_scale)))
        self._progbar2 = pygame.image.load(r".\images\prog_bar2.png")
        self._progbar3 = pygame.image.load(r".\images\prog_bar3.png")
        self._progbar3 = pygame.transform.scale(self._progbar3, (int(12 * self.screen_scale), int(1.2 * self.screen_scale)))
        self._progbar4 = pygame.image.load(r".\images\prog_bar4.png")

        # Load the button
        self._button1 = pygame.image.load(r".\images\button1.png")
        self._button2 = pygame.image.load(r".\images\button2.png")

        # Load the loading image
        self._loading_img = pygame.image.load(r".\images\loading.png")
        _loading_img_rect = self._loading_img.get_rect()
        self._loading_img = pygame.transform.scale(self._loading_img, (int(_loading_img_rect.width * self.screen_scale / 28), int(_loading_img_rect.height * self.screen_scale / 28)))

        # other settings
        self.choose_index = 0
        self.run_active = False
        self.have_run = False  # if run_active has been activated, do not launch cmd thread again
        self.selecting_mode = False
        self.game_mode = 0  # 0: training  1: easy  2: medium  3: hard
        self.reset_request = False  # game reset
        self.paused = False  # game paused
        self.init_game_args()  # initialize arguments

        # Set APF settings
        self.k_att = 1.2
        self.k_rep = 0.3
        self.d_o = 5
        self.d_dr = 0.6
        self.adv_length = 0.05
        self.k_mvrep = 1
        self.angle_multi = 3.14159265 / 3
        self.k_force = 0.02

        # enemy_mission_args: easy, medium, hard
        # [0]velocity factor; [1]random offset 1; [2]random offset 2;
        # [3]max_v(0.9m/s(0.135) 1.2m/s(0.18) 1.4m/s(0.21)); [4]attack radius; [5]attack limit period;
        # [6]destination offset to person; [7]box open time periods
        self.enemy_mission_args = [[0.6, 2, 3, 0.1348, 2.1, 5.5, 2.0, 50], [0.9, 1.5, 2.25, 0.1798, 2.4, 5.0, 2.2, 55], [1.2, 1, 1.5, 0.2098, 2.7, 4.5, 2.4, 60]]

        # other settings
        self.route_points = route_points
        self.lazer_shown = lazer_shown

    def init_game_args(self):
        # init arguments
        self.show_instr = False
        self.show_keyboard = False
        self.show_hiscore = False
        self.show_route = False
        self.clean_request = False  # clean hiscore request
        self.draw_person = True  # to show the -1HP effect

        self.enemy_status = 2    # 0: hover  1: move straight  2: move circular  3: approach team flights  4~6: performing defend task
        self.enemy_status_changed = False  # enemy_status is changed, arguments should be reset
        self.timer = 0.0
        self.time_period = 0.15
        self.out_of_bound_flag = False
        self.out_times = 0
        self.person_direction = 0  # 0: stand  1: up  2: down  3: left  4: right
        self.crack_qrcode = False  # get the QRcode of the boxes
        self.get_key = False  # get the password of the boxes
        self.all_open = False  # open all boxes
        self.failed = False
        self.complete = False
        self.complete_time = 0.0
        self.hiscore_recorded = False
        self.show_immune_time = 0

        self.clock_1 = 30  # 30 periods per clock
        self.font_color_1and2 = self.font_color_1

    def change_font_color(self):
        if self.font_color_1and2 == self.font_color_1:
            self.font_color_1and2 = self.font_color_2
        else:
            self.font_color_1and2 = self.font_color_1


'''

visited : 방문했다면 True, 아니라면 False
노드가 1000일때 간선이 500개까지 받아지고 꽤나 많이 받아질 수 있기에 연결list를 사용하여 풀이진행.

for문으로 node를 돌면서 visited가 False일때 bfs를 동작시키고 counting 변수값을 증가 시킴.
'''

import sys
from collections import deque
input = sys.stdin.readline
two_input = lambda : map(int, input().split())

n,m = two_input()

linked_list = [[0]*(n+1) for _ in range(n+1)]
visited = [False]*(n+1)
for _ in range(m):
    a,b = two_input()
    linked_list[a][b]=1
    linked_list[b][a]=1

count = 0

def bfs(start):

    global count
    count += 1

    queue = deque([])
    queue.append(start)
    visited[start] = True

    while(queue):
        current = queue.popleft()
        linked_node = linked_list[current]

        for node in range(1,len(linked_node)):
            if linked_node[node] == 1 and visited[node]==False:
                visited[node]=True
                queue.append(node)

for node in range(1, n+1):
    if visited[node]==False:
        bfs(node)

print(count)


import smtplib
import email.mime.multipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
import tkinter
from tkinter import *
import tkinter.messagebox as box
from tkinter import ttk

global ephone,eage,eaddress,ename

#===================================================Send Email=======================================

def SendEmail():
    v1 = ephone.get()
    v2 = ename.get()
    v3 = eaddress.get()
    v4 = eage.get()
    if (ephone.get()!="" and ename.get()!="" and eaddress.get()!="" and eage.get!="" and v1.isdigit()==True and
            v2.isalpha()==True and v4.isdigit()==True):
        if (len(v1)<=12 or len(v1)==10 and len(v1!=11)):
        #Create Email Details
            msgMultiPart=email.mime.multipart.MIMEMultipart()
            msgMultiPart["subject"]="Appointment Notification"
            msgMultiPart["From"]="nvnpunia001@gmail.com"
            msgMultiPart["To"]=etxtTo.get()
            #Create Body may be plain or html
            body="Dear Sir I want a appointment, My name is "+ename.get()+"\n"+ "My age is "+eage.get()+"\n"+"My Phone Number is "+ephone.get()+"\n"+"I m from "+eaddress.get()
            msgMultiPart.attach(MIMEText(body,'plain'))

            smtpobj=smtplib.SMTP(host="smtp.gmail.com",port=587)

            smtpobj.starttls()
            smtpobj.login("nvnpunia001@gmail.com","8222050457")
            smtpobj.sendmail("nvnpunia001@gmail.com",etxtTo.get(),msgMultiPart.as_string())
            box.showinfo("Info","Email send successfully")
            root.destroy()
            print("Sucess")
        else:
            box.showerror("Error","Please enter the valid phone number")
    else:
        box.showerror("Warning","Please fill all the required entry in correct way")
#=========================================Back function for doctor list================================

def back():
    root.destroy()
    import Nav4_Doctorlist_1 as dc_1
    dc_1.doctor_appointment("please wait, Opening list of best doctors for breast cancer treatment")

#=================================================================================================================

def GMIL():
    global root
    global To
    global etxtTo,e,ename,eaddress,eage,ephone
    root=Tk()
    root.title("Gmail")
    root.config(bg="pink")
    root.state('z')

    #=================================================Frame============================================================

#    Tops0 = Frame(root, width=1380, height=1000, bg="white", bd=0, relief="raise")
#    Tops0.place(x=0, y=0)
    Tops = Frame(root, width=1400, height=90, bg="blue", relief="raise")
    Tops.pack(side=TOP)

    #======================================================Variable==================================================

    To=StringVar()
    Name=StringVar()
    Age=StringVar()
    Phone=StringVar()
    Address=StringVar()

    #=========================================================Labels========================================================

    lblFrom = Label(Tops, text="From", font=('aria', 16, 'bold'),bg="blue", fg="black", bd=20).grid(row=0, column=0)
    lblTo = Label(Tops, text="To", font=('aria', 16, 'bold'),height=2,bg="blue", fg="black", bd=20).grid(row=1, column=0)
    lblFrom1 = Label(Tops, text="nvnpunia001@gmail.com", font=('aria', 16, 'bold'),bg="blue" ,bd=16, width=30, justify='left')
    lblFrom1.grid(row=0, column=1)

    lblName = Label(Tops, text="Name", font=('aria', 16, 'bold'), bg="blue", fg="black", bd=20).grid(row=2, column=0)
    lblFrom = Label(Tops, text="Age", font=('aria', 16, 'bold'), bg="blue", fg="black", bd=20).grid(row=3, column=0)
    lblFrom = Label(Tops, text="Phone No.", font=('aria', 16, 'bold'), bg="blue", fg="black", bd=20).grid(row=4, column=0)
    lblFrom = Label(Tops, text="Address", font=('aria', 16, 'bold'), bg="blue", fg="black", bd=20).grid(row=5, column=0)

    #======================================================Text Part=======================================================
    etxtTo = Entry(Tops,textvariable=To, font=('aria', 16, 'bold'), width=30, justify='left')
    etxtTo.grid(row=1, column=1)
    ename = Entry(Tops, textvariable=Name, font=('aria', 16, 'bold'), width=30, justify='left')
    ename.grid(pady=20,row=2, column=1)
    eage = Entry(Tops, textvariable=Age, font=('aria', 16, 'bold'), width=30, justify='left')
    eage.grid(pady=20,row=3, column=1)
    ephone = Entry(Tops, textvariable=Phone, font=('aria', 16, 'bold'), width=30, justify='left')
    ephone.grid(pady=20,row=4, column=1)
    ephone.insert(0,"91")
    eaddress = Entry(Tops, textvariable=Address, font=('aria', 16, 'bold'), width=30, justify='left')
    eaddress.grid(pady=20,row=5, column=1)

    #=======================================================Button=======================================================

    btnSendMail = ttk.Button(Tops, text='Send Mail',width=15,command=SendEmail)
    btnSendMail.grid(row=6, column=1)
    btnback=ttk.Button(Tops,text="Back",width=15,command=back)
    btnback.grid(row=6,column=2)
    root.mainloop()
if __name__=="__main__":
    GMIL()


# -*- coding: utf-8 -*-
"""Script for showing modification datetime of migrated tables.

Notes
-----
- For each source and target table the latest modification datime is displayed.
- If some source table has latest modification datetime younger than the target
  one, it is flagged.
"""
__version__ = '0.3.0'
__status__ = 'Beta'
__author__ = 'Libor Gabaj'
__copyright__ = 'Copyright 2019, ' + __author__
__credits__ = [__author__]
__license__ = 'MIT'
__maintainer__ = __author__
__email__ = 'libor.gabaj@gmail.com'

# Standard library modules
import os
import argparse
import logging
import mysql.connector as mysql
import datetime

# Third party modules
import dbconfig as db
import sql

###############################################################################
# Script global variables
###############################################################################
cmdline = None  # Object with command line arguments
logger = None  # Object with standard logging

###############################################################################
# Enumeration and parameter classes
###############################################################################
class Script:
    """Script parameters."""

    (
        fullname, basename, name,
    ) = ('', '', '',)

class Source:
    """Status parameters of the data source."""

    (
        conn, query, cursor, table, database,
    ) = (None, None, None, None, None,)

class Target:
    """Status parameters of the data target."""

    (
        conn, query, cursor, table, database,
    ) = (None, None, None, None, None,)

###############################################################################
# Actions
###############################################################################
def connect_db(config):
    """Connect to a database.

    Arguments
    ---------
    config : dict
        Connection configuration to a database.

    Returns
    -------
    connection : object
        Connection object to a database.

    Raises
    -------
    mysql.connector.Error
        Native exception of the database connector.

    """
    try:
        conn = mysql.connect(**config)
        logger.debug('Database %s connected', config['database'])
        return conn
    except mysql.Error as err:
        if err.errno == mysql.errorcode.ER_ACCESS_DENIED_ERROR:
            logger.error('Bad database %s credentials', config['database'])
        elif err.errno == mysql.errorcode.ER_BAD_DB_ERROR:
            logger.error('Database %s does not exist', config['database'])
        else:
            logger.error(err)
        raise

def source_open():
    """Connect to a source database.

    Returns
    -------
    boolean
        Flag about successful processing.

    """
    if Source.conn is None:
        try:
            Source.conn = connect_db(db.source_config)
        except Exception:
            logger.error(
                'Cannot connect to the source database %s',
                Source.database
            )
            return False
    return True

def source_close():
    """Close cursor and connection to a source database."""
    # Close cursor
    if Source.cursor is not None:
        Source.cursor.close()
    # Close connection to database
    if Source.conn is not None:
        Source.conn.close()
    Source.conn = None
    Source.query = None
    Source.cursor = None
    Source.table = None

def target_open():
    """Connect to a target database.

    Returns
    -------
    boolean
        Flag about successful processing.

    """
    if Target.conn is None:
        try:
            Target.conn = connect_db(db.target_config)
        except Exception:
            logger.error(
                'Cannot connect to the target database %s',
                Target.database
                )
            return False
    return True

def target_close():
    """Close cursor and connection to a target database."""
    # Close cursor
    if Target.cursor is not None:
        Target.cursor.close()
    # Close connection to database
    if Target.conn is not None:
        Target.conn.close()

###############################################################################
# Setup functions
###############################################################################
def setup_params():
    """Determine script operational parameters."""
    Script.fullname = os.path.splitext(os.path.abspath(__file__))[0]
    Script.basename = os.path.basename(__file__)
    Script.name = os.path.splitext(Script.basename)[0]

def setup_cmdline():
    """Define command line arguments."""
    desc = \
        f'Recent modification datetimes for code lists and agendas, ' \
        f'version {__version__}'
    parser = argparse.ArgumentParser(description=desc)
    # Options
    parser.add_argument(
        '-V', '--version',
        action='version',
        version=__version__,
        help='Current version of the script.'
    )
    parser.add_argument(
        '-v', '--verbose',
        choices=['debug', 'info', 'warning', 'error', 'critical'],
        default='info',
        help='Level of logging to the console.'
    )
    parser.add_argument(
        '-c', '--codelists',
        action='store_true',
        help='Show codelist tables.'
    )
    parser.add_argument(
        '-a', '--agendas',
        action='store_true',
        help='Show agenda tables.'
    )
    # Process command line arguments
    global cmdline
    cmdline = parser.parse_args()

def setup_logger():
    """Configure logging facility."""
    global logger
    logging.basicConfig(
        level=getattr(logging, cmdline.verbose.upper()),
        format='%(levelname)s:%(name)s: %(message)s',
    )
    logger = logging.getLogger(Script.name)

def tablelist(table_prefix):
    """List of source and target tables with modification datetimes.

    Arguments
    ---------
    table_prefix : str
        Prefix of tables.

    Returns
    -------
    list : dict
        List of source-target records for codelists or agendas according to
        to provided table prefix.

    """
    format = '%d.%m.%Y %H:%M:%S'
    format_db = '%Y-%m-%d %H:%M:%S'
    tables = [{
                'source_table': source_table,
                'source_datetime': None,
                'source_timestamp': None,
                'source_count': None,
                'target_table': target['table_target'],
                'target_datetime': None,
                'target_timestamp': None,
                'target_count': None,
              }
              for source_table, target
              in sql.source.items()
              if source_table.startswith(table_prefix)
              ]
    for i, table in enumerate(tables):
        # Source datetime
        Source.query = sql.compose_select(
            table['source_table'],
            'MAX(GREATEST(modified, created)), COUNT(*)'
        )
        Source.cursor = Source.conn.cursor()
        try:
            Source.cursor.execute(Source.query)
            record = Source.cursor.fetchone()
            timestamp = record[0]
            if isinstance(timestamp, str):
                timestamp = datetime.datetime.strptime(timestamp, format_db)
            tables[i]['source_timestamp'] = timestamp
            tables[i]['source_count'] = record[1]
            try:
                tables[i]['source_datetime'] = timestamp.strftime(format)
            except AttributeError as err:
                tables[i]['source_datetime'] = 'N/A'
                # errmsg = \
                #     f"{table['source_table']}" \
                #     f".modified" \
                #     f" - {err}"
                # logger.error(errmsg)
        except mysql.Error as err:
            logger.error(err)
        finally:
            Source.cursor.close()
        # Target datetime
        Target.query = sql.compose_select(
            table['target_table'],
            'MAX(GREATEST(modified, created)), COUNT(*)'
        )
        Target.cursor = Target.conn.cursor()
        try:
            Target.cursor.execute(Target.query)
            record = Target.cursor.fetchone()
            timestamp = record[0]
            if isinstance(timestamp, str):
                timestamp = datetime.datetime.strptime(timestamp, format_db)
            tables[i]['target_timestamp'] = timestamp
            tables[i]['target_count'] = record[1]
            try:
                tables[i]['target_datetime'] = timestamp.strftime(format)
            except AttributeError as err:
                tables[i]['target_datetime'] = 'N/A'
                # errmsg = \
                #     f"{table['target_table']}" \
                #     f".modified" \
                #     f" - {err}"
                # logger.error(errmsg)
        except mysql.Error as err:
            logger.error(err)
        finally:
            Target.cursor.close()
    return tables

def main():
    """Fundamental control function."""
    def esc(code):
        """ANSI Escape Codes."""
        return f'\033[{code}m'
    setup_params()
    setup_cmdline()
    setup_logger()
    if cmdline.codelists or cmdline.agendas:
        # Connect to source database
        Source.database = db.source_config['database']
        if not source_open():
            return
        # Connect to target database
        Target.database = db.target_config['database']
        if not target_open():
            return
        # Migrated tables
        sources = {
            'codelists': sql.source_table_prefix_codelist,
            'agendas': sql.source_table_prefix_agenda,
        }
        for source, prefix in sources.items():
            if not eval(f'cmdline.{source}'):
                continue
            tables = tablelist(prefix)
            print()
            print(f'{source.capitalize()}:')
            for table in tables:
                ansi = esc(0)
                if table['source_timestamp'] is None \
                or table['target_timestamp'] is None:
                    prefix = '???'
                    ansi = esc(96)  # Cyan
                elif table['source_timestamp'] > table['target_timestamp']:
                    prefix = '!!!'
                    ansi = esc(31)  # Red
                elif table['source_timestamp'] < table['target_timestamp']:
                    prefix = '<'
                    ansi = esc(93)  # Yellow
                elif table['source_timestamp'] == table['target_timestamp']:
                    prefix = '='
                else:
                    prefix = ''
                msg = \
                    f"{prefix.ljust(4)}" \
                    f"{table['source_table']} (" \
                    f"{table['source_datetime']}" \
                    f", {table['source_count']}" \
                    f") -> " \
                    f"{table['target_table']} (" \
                    f"{table['target_datetime']}" \
                    f", {table['target_count']}" \
                    f")"
                print(ansi + msg)
        print(esc(0))
        source_close()
        target_close()
    else:
        logger.warning('Nothing to show, see --help')

if __name__ == '__main__':
    main()


import asyncio
import logging
from datetime import datetime, timezone
from queue import Queue
from threading import Thread
from typing import Dict, Optional

from aws_embedded_metrics.logger.metrics_logger import MetricsLogger
from aws_embedded_metrics.unit import Unit
from shapely.affinity import translate
from shapely.geometry import Polygon

from aws.osml.model_runner.app_config import MetricLabels
from aws.osml.model_runner.common import GeojsonDetectionField
from aws.osml.model_runner.database import FeatureTable
from aws.osml.model_runner.inference import Detector
from aws.osml.model_runner.tile_worker import FeatureRefinery

class TileWorker(Thread):
    def __init__(
        self,
        in_queue: Queue,
        feature_detector: Detector,
        feature_refinery: Optional[FeatureRefinery],
        feature_table: FeatureTable,
        metrics: MetricsLogger,
    ) -> None:
        super().__init__()
        self.in_queue = in_queue
        self.feature_detector = feature_detector
        self.feature_refinery = feature_refinery
        self.feature_table = feature_table
        self.metrics = metrics

    def run(self) -> None:
        thread_event_loop = asyncio.new_event_loop()
        asyncio.set_event_loop(thread_event_loop)
        while True:
            image_info: Dict = self.in_queue.get()

            if image_info is None:
                logging.info("All images processed. Stopping tile worker.")
                logging.info(
                    "Feature Detector Stats: {} requests with {} errors".format(
                        self.feature_detector.request_count, self.feature_detector.error_count
                    )
                )
                break

            try:
                logging.info("Invoking Feature Detector Endpoint")
                with open(image_info["image_path"], mode="rb") as payload:
                    feature_collection = self.feature_detector.find_features(payload)

                # Convert the features to reference the full image
                features = []
                ulx = image_info["region"][0][1]
                uly = image_info["region"][0][0]
                if isinstance(feature_collection, dict) and "features" in feature_collection:
                    logging.info("SM Model returned {} features".format(len(feature_collection["features"])))
                    for feature in feature_collection["features"]:
                        # model returned a mask
                        scaled_polygon = None
                        if GeojsonDetectionField.GEOM in feature["properties"]:
                            polygon = Polygon(feature["properties"][GeojsonDetectionField.GEOM])
                            scaled_polygon = translate(polygon, xoff=ulx, yoff=uly)
                            feature["properties"][GeojsonDetectionField.GEOM] = list(scaled_polygon.exterior.coords)
                        # model returned a bounding box
                        if GeojsonDetectionField.BOUNDS in feature["properties"]:
                            tile_bbox = feature["properties"][GeojsonDetectionField.BOUNDS]
                            scaled_bbox = translate(
                                Polygon(FeatureRefinery.imcoords_bbox_to_polygon(tile_bbox)), xoff=ulx, yoff=uly
                            )
                            feature["properties"][GeojsonDetectionField.BOUNDS] = scaled_bbox.bounds
                        elif scaled_polygon is not None:
                            feature["properties"][GeojsonDetectionField.BOUNDS] = scaled_polygon.bounds
                        else:
                            logging.warning(f"There isn't a valid detection shape for feature: {feature}")
                        feature["properties"]["image_id"] = image_info["image_id"]
                        feature["properties"]["inferenceTime"] = datetime.now(tz=timezone.utc).isoformat()
                        FeatureRefinery.feature_property_transformation(feature)
                        features.append(feature)

                logging.info("# Features Created: {}".format(len(features)))
                if len(features) > 0:
                    if self.feature_refinery is not None:
                        # Create a geometry for each feature in the result. The geographic coordinates of these
                        # features are computed using the sensor model provided in the image metadata
                        self.feature_refinery.refine_features_for_tile(features)
                        logging.info(f"Created Geographic Coordinates for {len(features)} features")

                    self.feature_table.add_features(features)
            except Exception as e:
                logging.error("Failed to process region tile!")
                logging.exception(e)
                if self.metrics:
                    self.metrics.put_metric(MetricLabels.TILE_PROCESSING_ERROR, 1, str(Unit.COUNT.value))
                    self.metrics.put_metric(MetricLabels.TILE_CREATION_FAILURE, 1, str(Unit.COUNT.value))
            finally:
                self.in_queue.task_done()

        try:
            thread_event_loop.stop()
            thread_event_loop.close()
        except Exception as e:
            logging.warning("Failed to stop and close the thread event loop")
            logging.exception(e)


"""Test DocugamiLoader."""
from pathlib import Path

import pytest

from langchain_community.document_loaders import DocugamiLoader

DOCUGAMI_XML_PATH = Path(__file__).parent / "test_data" / "docugami-example.xml"

@pytest.mark.requires("dgml_utils")
def test_docugami_loader_local() -> None:
    """Test DocugamiLoader."""
    loader = DocugamiLoader(file_paths=[DOCUGAMI_XML_PATH])
    docs = loader.load()

    assert len(docs) == 25

    assert "/docset:DisclosingParty" in docs[1].metadata["xpath"]
    assert "h1" in docs[1].metadata["structure"]
    assert "DisclosingParty" in docs[1].metadata["tag"]
    assert docs[1].page_content.startswith("Disclosing")

def test_docugami_initialization() -> None:
    """Test correct initialization in remote mode."""
    DocugamiLoader(access_token="test", docset_id="123")


import math

import pygame

from DrawHandlers import MachineDrawHandler2, SelectionDrawHandler, BackgroundDrawHandler2, MachineHologramDrawHandler
from RateHandlers import FrameRateHandler
from InputHandler import InputHandler
from RCGame import ResourceConsumerGame, GenericMachine
from RCScreenGUI import RCScreenGUI

class RCScreen(object):
    # class to handle the overall window, interaction and rendering of a ResourceConsumerGame object
    def __init__(self, rcg: ResourceConsumerGame, callback_outgoing_queue_func):
        self.rcg = rcg
        self.callback_outgoing = callback_outgoing_queue_func

        self.input_handler = InputHandler()

        pygame.init()

        # set up window
        self.window_size = (1000, 750)
        self.clock = pygame.time.Clock()

        self.screen = pygame.display.set_mode(self.window_size)
        self.screen.fill((0, 100, 100))  # base colour
        pygame.display.set_caption("resource consumer screen")

        # set up surfaces
        self.bg_surface = pygame.Surface(self.window_size)
        self.machine_surface = pygame.Surface(self.window_size, pygame.SRCALPHA)  # per pixel alpha
        self.hud_surface = pygame.Surface(self.window_size, pygame.SRCALPHA)  # per pixel alpha
        self.selection_surface = pygame.Surface(self.window_size, pygame.SRCALPHA)  # per pixel alpha

        # set up GUI and placements
        self.gui = RCScreenGUI(self.rcg, self.set_selected_machine)
        self.selected_machine = None  # used for the gui to callback to
        self.selected_rotation = 0

        # set up selection
        self.selection = {"down": [None, None], "cur": [None, None]}

        # zoom and camera position limits and speeds
        self.tile_size_min = 10
        self.tile_size_max = 75
        self.offsets_max = (1000, 1000)  # (x, y) pairs or (h, v) pairs
        self.offsets_min = (-1000, -1000)  # (x, y) pairs or (h, v) pairs
        # can be changed to be smooth/have acceleration
        self.offset_move_speed = 10  # n pixels change per frame
        self.tile_size_zoom_speed = 2  # n pixels change per frame

        # set up base zoom and camera position
        self.tile_size = 50
        self.offsets = [0, 0]

        # run flag for main
        self.run = False

        self.background_draw_handler = BackgroundDrawHandler2(self.rcg.game_map.size,
                                                              self.rcg.game_map.background_map,
                                                              self.rcg.game_map.background_addition_map,
                                                              (self.tile_size, self.tile_size))
        self.machine_draw_handler = MachineDrawHandler2(self.rcg.placed_objects, (self.tile_size, self.tile_size))
        self.selection_draw_handler = SelectionDrawHandler((self.tile_size, self.tile_size))
        self.block_hologram_draw_handler = MachineHologramDrawHandler((self.tile_size, self.tile_size))

    def draw(self):
        # draw a single frame of the game

        # reset the surfaces to blank
        self.bg_surface.fill((0, 0, 0))
        self.machine_surface.fill((0, 0, 0, 0))  # alpha value of 0 needed
        self.selection_surface.fill((0, 0, 0, 0))
        # self.hud_surface.fill((0, 0, 0, 0))  # don't need to re-draw hud on every frame

        # draw the background
        self.background_draw_handler.draw_background(self.bg_surface, self.offsets, (self.tile_size, self.tile_size))

        # draw the machines
        # MachineDrawHandler
        # for machine in self.rcg.placed_objects:
        #     machine.draw_handler.draw(self.machine_surface, self.offsets, (self.tile_size, self.tile_size))
        # MachineDrawHandler2
        self.machine_draw_handler.draw_machines(self.machine_surface, self.offsets, (self.tile_size, self.tile_size))
        # draw the selection
        self.selection_draw_handler.draw(self.selection_surface,
                                         self.offsets,
                                         (self.tile_size, self.tile_size),
                                         self.selection)

        # draw the gui
        self.gui.draw(self.hud_surface)

        # draw the block hologram
        self.block_hologram_draw_handler.draw(self.selection_surface,
                                              (self.tile_size, self.tile_size),
                                              pygame.mouse.get_pos(),
                                              self.selected_machine,
                                              self.selected_rotation)

        # Update screen surface
        self.screen.blit(self.bg_surface, (0, 0))
        self.screen.blit(self.machine_surface, (0, 0))
        self.screen.blit(self.selection_surface, (0, 0))
        self.screen.blit(self.hud_surface, (0, 0))
        pygame.display.flip()

    def adjust_camera_offset(self, direction):
        # takes direction tuple, (x, y) or (h, v) of either -1, 0, 1 and moves one time in that direction

        # handle horizontal
        if direction[0] != 0:
            if direction[0] > 0:
                self.offsets[0] = min(self.offsets[0] + self.offset_move_speed, self.offsets_max[0])
            elif direction[0] < 0:
                self.offsets[0] = max(self.offsets[0] - self.offset_move_speed, self.offsets_min[0])
        # handle vertical
        if direction[1] != 0:
            if direction[1] > 0:
                self.offsets[1] = min(self.offsets[1] + self.offset_move_speed, self.offsets_max[1])
            elif direction[1] < 0:
                self.offsets[1] = max(self.offsets[1] - self.offset_move_speed, self.offsets_min[1])

    def adjust_camera_zoom(self, direction):
        # takes direction value, either -1, 0, 1 and zooms one time in that direction
        if direction != 0:  # for later, if this is called every tick with a smooth camera
            if direction > 0:
                self.tile_size = min(self.tile_size + self.tile_size_zoom_speed, self.tile_size_max)
            elif direction < 0:
                self.tile_size = max(self.tile_size - self.tile_size_zoom_speed, self.tile_size_min)

    def handle_events(self):
        # uses the input_handler to record the key states
        # updates the input_handler with input changes
        # handles the inputs given by the input handler

        # Get and process events, including keypress and mouse position
        mouse_pos = pygame.mouse.get_pos()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                self.run = False  # handle exit flag
            elif event.type == pygame.KEYDOWN:
                self.input_handler.kb_input_start(event.key)
            elif event.type == pygame.KEYUP:
                self.input_handler.kb_input_stop(event.key)
            elif event.type == pygame.MOUSEBUTTONDOWN:
                for m_button, pressed in enumerate(pygame.mouse.get_pressed()):
                    if pressed == 1:
                        self.input_handler.m_down(m_button, mouse_pos, self.px_to_game_tile(mouse_pos))
            elif event.type == pygame.MOUSEBUTTONUP:
                for m_button, pressed in enumerate(pygame.mouse.get_pressed()):
                    if pressed == 0:
                        self.input_handler.m_up(m_button, mouse_pos, self.px_to_game_tile(mouse_pos))

        # handle inputHandler inputs, this can be moved into the input_handler
        kb_inputs, m_inputs = self.input_handler.get_inputs()

        # handle keyboard
        camera_offset_adjust = [0, 0]
        camera_zoom_adjust = 0
        if kb_inputs.get("pan_left"):
            camera_offset_adjust[0] += 1
        if kb_inputs.get("pan_right"):
            camera_offset_adjust[0] -= 1
        if kb_inputs.get("pan_up"):
            camera_offset_adjust[1] += 1
        if kb_inputs.get("pan_down"):
            camera_offset_adjust[1] -= 1
        if kb_inputs.get("zoom_in"):
            camera_zoom_adjust += 1
        if kb_inputs.get("zoom_out"):
            camera_zoom_adjust -= 1

        if kb_inputs.get("rotate"):
            self.selected_rotation = (self.selected_rotation + 1) % 4
            self.input_handler.kb_set_input("rotate", False)
            print("ROT", self.selected_rotation)

        # apply the camera offset and zoom changes
        self.adjust_camera_offset(camera_offset_adjust)
        self.adjust_camera_zoom(camera_zoom_adjust)

        # handle mouse
        # left click place
        if m_inputs[0]["up_pos"] != [None, None]:
            if self.selected_machine is not None:  # check if a building is selected
                if self.is_on_map(m_inputs[0]["up_pos"][1]):
                    # need to check if on gui and ignore if so
                    if m_inputs[0]["down_pos"][1] == m_inputs[0]["up_pos"][1]:  # compare the game pos
                        print("place building at", m_inputs[0]["up_pos"][1], self.selected_machine)

                        machine = self.selected_machine(list(m_inputs[0]["up_pos"][1]), self.selected_rotation)
                        if self.rcg.can_build_machine(machine):
                            # add to the staging callback queue so the client can send to server
                            self.callback_outgoing("placements", machine.to_json_serialisable())

        # right click drag selection
        if m_inputs[2]["down_pos"] != [None, None]:
            self.selection["down"] = m_inputs[2]["down_pos"]
            self.selection["cur"] = [mouse_pos, self.px_to_game_tile(mouse_pos)]
        else:
            # on no selection - default to [None, None]
            self.selection["down"] = [None, None]
            self.selection["cur"] = [None, None]

        # on right click release, send selection to client to send to server for dismantling
        if m_inputs[2]["up_pos"] != [None, None]:
            tile_selection = {"start": self.selection["down"][1], "end": self.selection["cur"][1]}
            print(tile_selection)
            self.callback_outgoing("removal_sel", tile_selection)

        # update the gui with the mouse position and mouse input handler
        self.gui.update(mouse_pos, m_inputs)

    def is_on_map(self, game_pos):
        # game pos is tuple (x, y) of already converted px_to_game_tile() result for a position
        # checks if this position is within the map
        map_size = self.rcg.game_map.size
        if 0 <= game_pos[0] < map_size[0] and 0 <= game_pos[1] < map_size[1]:
            return True
        else:
            return False

    def px_to_game_tile(self, pixel_pos):
        # pixel_pos is a tuple (x, y) of a pixel position
        # converts the pixel_pos to the game_pos tuple (x, y) for the correct game tile

        tile_x = math.floor((pixel_pos[0] - self.offsets[0]) / self.tile_size)
        tile_y = math.floor((pixel_pos[1] - self.offsets[1]) / self.tile_size)

        return tile_x, tile_y

    def set_selected_machine(self, machine):
        # setter for setting the currently selected machine - used by the RCScreenGUI for callback
        assert issubclass(machine, GenericMachine)  # using issubclass() because not inited

        # handle un-selecting a machine
        if self.selected_machine == machine:
            self.selected_machine = None
        else:
            self.selected_machine = machine

    async def main(self):
        # main loop for handling every frame of the screen
        framerate_handler = FrameRateHandler(30)

        self.run = True
        while self.run:
            framerate_handler.period_start()  # start the framerate_handler's frame
            # use input_handler and method to apply all the needed inputs/actions
            self.handle_events()

            # entire draw routine
            self.draw()

            # dynamically await using the framerate_handler
            await framerate_handler.period_end()


import pickle
from datetime import date, datetime, timedelta
from typing import Any, List, Tuple

import numpy as np
import pandas as pd

from wikipedia_cleanup.predictor import CachedPredictor

class BaselineMinPrecision(CachedPredictor):
    def __init__(
        self,
        use_cache: bool = True,
    ) -> None:
        super().__init__(use_cache)
        self.classifier: dict = {}

    def get_relevant_ids(self, identifier: Tuple) -> List[Tuple]:
        return []

    def get_relevant_attributes(self) -> List[str]:
        return ["value_valid_from"]

    def _load_cache_file(self, file_object: Any) -> bool:
        self.classifier = pickle.load(file_object)
        return True

    def _get_cache_object(self) -> Any:
        return self.classifier

    def _fit_classifier(
        self, train_data: pd.DataFrame, last_day: datetime, keys: List[str]
    ) -> None:

        bins = pd.date_range(
            last_day - timedelta(364),
            last_day,
        )
        total_days = len(bins)
        bins = pd.cut(
            train_data["value_valid_from"].astype(np.datetime64), bins, labels=False
        )
        train_data["bins"] = bins
        train_data = train_data[train_data["bins"].notna()]
        train_data["bins"] = train_data["bins"].astype(int)

        def _create_time_series(a, duration):
            series = np.zeros(duration)
            uniques, counts = np.unique(a, return_counts=True)

            series[uniques] = counts
            return series

        def _aggregate_labels(labels: np.ndarray, n: int) -> np.ndarray:
            if n == 1:
                return labels
            if 365 % n != 0:
                cut_labels = labels[:, : -(365 % n)]
            else:
                cut_labels = labels
            cut_labels = cut_labels.reshape((labels.shape[0], -1, n))
            return np.any(cut_labels, axis=2)

        train_data_grp = train_data.groupby(["key"])["bins"].apply(
            _create_time_series, duration=total_days
        )
        x = np.vstack(train_data_grp.to_numpy())

        self.classifier[1] = set(train_data_grp.index[x.sum(axis=1) >= 365 * 0.85])
        self.classifier[7] = set(
            train_data_grp.index[_aggregate_labels(x, 7).sum(axis=1) >= 365 // 7 * 0.85]
        )
        self.classifier[30] = set(
            train_data_grp.index[
                _aggregate_labels(x, 30).sum(axis=1) >= 365 // 30 * 0.85
            ]
        )
        self.classifier[365] = set(
            train_data_grp.index[_aggregate_labels(x, 365).sum(axis=1) >= 1]
        )

    def predict_timeframe(
        self,
        data_key: np.ndarray,
        additional_data: np.ndarray,
        columns: List[str],
        first_day_to_predict: date,
        timeframe: int,
    ) -> Any:  # can be bool for actual predictions or float for confidence scores
        key_col = columns.index("key")
        if len(data_key) == 0:
            return False
        current_key = data_key[0, key_col]
        return current_key in self.classifier[timeframe]


import pendulum
from airflow import DAG
from airflow.decorators import dag, task

hosts = []

@task(multiple_outputs=True)
def parse_arguments(**kwargs):
    """
    #### Parse Arguments to the Task
    """
    global hosts
    hosts = eval(kwargs['dag_run'].conf['hosts'])

@task()
def run_task(host_ip):
    f"""
    #### Runs on {host_ip}
    """
    print(f"Running on {host_ip}")

with DAG(
    'parallel_pipeline2',
    schedule_interval=None,
    start_date=pendulum.datetime(2022, 4, 20, tz="UTC"),
    catchup=False,
    tags=['pipeline'],
) as dag:
    tasks = []
    for host in hosts:
        tasks.append(lambda: run_task(host))

    parse_arguments.set_downstream(tasks)


# 75. Sort Colors
# Given an array nums with n objects colored red, white, or blue, sort them in-place so that objects of the same color are adjacent, with the colors in the order red, white, and blue.
# We will use the integers 0, 1, and 2 to represent the color red, white, and blue, respectively.

from typing import List

class Solution:
    def sortColors(self, nums: List[int]) -> None:
        """
        Do not return anything, modify nums in-place instead.
        """
        i, j = 0, len(nums) - 1
        m = 0
        while i < j and m <= j:
            while nums[i] == 0 and i < j:
                i += 1
            while nums[j] == 2 and i < j:
                j -= 1
            if m < i:
                m = i
            while m <= j:
                if nums[m] == 0:
                    nums[i], nums[m] = nums[m], nums[i]
                    i += 1
                    break
                if nums[m] == 2:
                    nums[j], nums[m] = nums[m], nums[j]
                    j -= 1
                    break
                m += 1

if __name__ == '__main__':
    s = Solution()
    nums = [2, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0]
    s.sortColors(nums)
    print(nums)


import gzip
import json
import logging
import os
import time

import boa
from airflow.hooks.S3_hook import S3Hook
from airflow.hooks.base_hook import BaseHook
from airflow.models import BaseOperator, Variable
from google_plugin.hooks.google_hook import GoogleHook

from six import BytesIO

class GoogleSheetsToS3Operator(BaseOperator):
    """
    Google Sheets To S3 Operator

    :param google_conn_id:    The Google connection id.
    :type google_conn_id:     string
    :param sheet_id:          The id for associated report.
    :type sheet_id:           string
    :param sheet_names:       The name for the relevent sheets in the report.
    :type sheet_names:        string/array
    :param range:             The range of of cells containing the relevant data.
                              This must be the same for all sheets if multiple
                              are being pulled together.
                              Example: Sheet1!A2:E80
    :type range:              string
    :param include_schema:    If set to true, infer the schema of the data and
                              output to S3 as a separate file
    :type include_schema:     boolean
    :param s3_conn_id:        The s3 connection id.
    :type s3_conn_id:         string
    :param s3_key:            The S3 key to be used to store the
                              retrieved data.
    :type s3_key:             string
    """

    template_fields = ('s3_key',)

    def __init__(self,
                 google_conn_id,
                 sheet_id,
                 s3_conn_id,
                 s3_key,
                 compression_bound,
                 include_schema=False,
                 sheet_names=[],
                 range=None,
                 output_format='json',
                 *args,
                 **kwargs):
        super().__init__(*args, **kwargs)

        self.google_conn_id = google_conn_id
        self.sheet_id = sheet_id
        self.sheet_names = sheet_names
        self.s3_conn_id = s3_conn_id
        self.s3_key = s3_key
        self.include_schema = include_schema
        self.range = range
        self.output_format = output_format.lower()
        self.compression_bound = compression_bound
        if self.output_format not in ('json'):
            raise Exception('Acceptable output formats are: json.')

        if self.sheet_names and not isinstance(self.sheet_names, (str, list, tuple)):
            raise Exception('Please specify the sheet names as a string or list.')

    def execute(self, context):
        g_conn = GoogleHook(self.google_conn_id)

        if isinstance(self.sheet_names, str) and ',' in self.sheet_names:
            sheet_names = self.sheet_names.split(',')
        else:
            sheet_names = self.sheet_names

        sheets_object = g_conn.get_service_object('sheets', 'v4')
        logging.info('Retrieved Sheets Object')

        response = sheets_object.spreadsheets().get(spreadsheetId=self.sheet_id,
                                                    includeGridData=True).execute()

        title = response.get('properties').get('title')
        sheets = response.get('sheets')

        final_output = dict()

        total_sheets = []
        for sheet in sheets:
            name = sheet.get('properties').get('title')
            name = boa.constrict(name)
            total_sheets.append(name)

            if self.sheet_names:
                if name not in sheet_names:
                    logging.info('{} is not found in available sheet names.'.format(name))
                    continue

            table_name = name
            data = sheet.get('data')[0].get('rowData')
            output = []

            for row in data:
                row_data = []
                values = row.get('values')
                for value in values:
                    ev = value.get('effectiveValue')
                    if ev is None:
                        row_data.append(None)
                    else:
                        for v in ev.values():
                            row_data.append(v)

                output.append(row_data)

            if self.output_format == 'json':
                headers = output.pop(0)
                output = [dict(zip(headers, row)) for row in output]

            final_output[table_name] = output

        s3 = S3Hook(self.s3_conn_id)

        for sheet in final_output:
            output_data = final_output.get(sheet)

            file_name, file_extension = os.path.splitext(self.s3_key)

            output_name = ''.join([file_name, '_', sheet, file_extension])

            if self.include_schema is True:
                schema_name = ''.join([file_name, '_', sheet, '_schema', file_extension])

            self.output_manager(s3, output_name, output_data, context, sheet, schema_name)

        dag_id = context['ti'].dag_id

        var_key = '_'.join([dag_id, self.sheet_id])
        Variable.set(key=var_key, value=json.dumps(total_sheets))
        time.sleep(10)

        return boa.constrict(title)

    def output_manager(self, s3, output_name, output_data, context, sheet_name, schema_name=None):
        self.s3_bucket = BaseHook.get_connection(self.s3_conn_id).host
        if self.output_format == 'json':
            output = '\n'.join([json.dumps({boa.constrict(str(k)): v
                                            for k, v in record.items()})
                                for record in output_data])

            enc_output = str.encode(output, 'utf-8')

            # if file is more than bound then apply gzip compression
            if len(enc_output) / 1024 / 1024 >= self.compression_bound:
                logging.info("File is more than {}MB, gzip compression will be applied".format(self.compression_bound))
                output = gzip.compress(enc_output, compresslevel=5)
                self.xcom_push(context, key='is_compressed_{}'.format(sheet_name), value="compressed")
                self.load_bytes(s3,
                                bytes_data=output,
                                key=output_name,
                                bucket_name=self.s3_bucket,
                                replace=True
                                )
            else:
                logging.info("File is less than {}MB, compression will not be applied".format(self.compression_bound))
                self.xcom_push(context, key='is_compressed_{}'.format(sheet_name), value="non-compressed")
                s3.load_string(
                    string_data=output,
                    key=output_name,
                    bucket_name=self.s3_bucket,
                    replace=True
                )

            if self.include_schema is True:
                output_keys = output_data[0].keys()
                schema = [{'name': boa.constrict(a),
                           'type': 'varchar(512)'} for a in output_keys if a is not None]
                schema = {'columns': schema}

                s3.load_string(
                    string_data=json.dumps(schema),
                    key=schema_name,
                    bucket_name=self.s3_bucket,
                    replace=True
                )

            logging.info('Successfully output of "{}" to S3.'.format(output_name))

        # TODO -- Add support for csv output

        # elif self.output_format == 'csv':
        #     with NamedTemporaryFile("w") as f:
        #         writer = csv.writer(f)
        #         writer.writerows(output_data)
        #         s3.load_file(
        #             filename=f.name,
        #             key=output_name,
        #             bucket_name=self.s3_bucket,
        #             replace=True
        #         )
        #
        #     if self.include_schema is True:
        #         pass

    # TODO: remove when airflow version is upgraded to 1.10
    def load_bytes(self, s3,
                   bytes_data,
                   key,
                   bucket_name=None,
                   replace=False):
        if not bucket_name:
            (bucket_name, key) = s3.parse_s3_url(key)

        if not replace and s3.check_for_key(key, bucket_name):
            raise ValueError("The key {key} already exists.".format(key=key))

        filelike_buffer = BytesIO(bytes_data)

        client = s3.get_conn()
        client.upload_fileobj(filelike_buffer, bucket_name, key, ExtraArgs={})