import os, io, sys, json, pickle, re, mimetypes, shutil
from scripts.util.thread_events import check_stop_script
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload

# Value to represent no part
NO_PART = "NO_PART"

# Custom Exception for when credentials fail to load
class CredentialsError(Exception):
    pass

# Returns whether or not the script is running in an exe/frozen file
def is_frozen():
    return getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS')

# Builds the drive service
def build_service():
    check_stop_script()
    return build('drive', 'v3', credentials=fetch_credentials())

# Fetches the user's credentials, assume credential files are in root directory
def fetch_credentials():
    """Loads the user's credentials from credentials.json or a token
    """
    # If modifying these scopes, delete the file token.pickle.
    SCOPES = ['https://www.googleapis.com/auth/drive']

    creds = None
    # The file token.pickle stores the user's access and refresh tokens, and is
    # created automatically when the authorization flow completes for the first
    # time.
    res_paths = parse_options('res_paths.json')
    token_path = resourcePath(os.path.join(res_paths['res-path'], 'res/token.pickle'))
    if os.path.exists(token_path):
        with open(token_path, 'rb') as token:
            creds = pickle.load(token)
    # If there are no (valid) credentials available, let the user log in.
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            creds_path = res_paths['creds-path']
            try:
                flow = InstalledAppFlow.from_client_secrets_file(
                    resourcePath(creds_path), SCOPES)
                creds = flow.run_local_server(port=0)
            except (UnicodeDecodeError, ValueError, OSError):
                res_paths['creds-path'] = ''
                write_options(res_paths, 'res_paths.json')
                raise CredentialsError(f'ERROR: Credentials file "{os.path.basename(creds_path)}" invalid. Please restart the application.')

        # Save the credentials for the next run
        with open(resourcePath(token_path), 'wb') as token:
            pickle.dump(creds, token)
    return creds

# Path to resources
def resourcePath(rel_path):
    """ Get absolute path to resource, works for dev and for PyInstaller """
    if is_frozen():
        return os.path.join(getattr(sys, '_MEIPASS', ''), rel_path)
    return rel_path

# Make Application Data files
def make_application_data(data_dir, recopy=False):
    path_exists = os.path.exists(data_dir)
    if path_exists and not recopy: return

    if not path_exists:
        os.makedirs(data_dir)

    src = resourcePath("./res")
    shutil.copytree(src, os.path.join(data_dir, 'res'), dirs_exist_ok=True)

    # Remove token.pickle file
    pickle = os.path.join(data_dir, 'res/token.pickle')
    if os.path.isfile(pickle):
        os.remove(pickle)

# Gets the path to the specified location within the resources directory
def get_full_path(partial_path):
    with open(resourcePath('res/options/res_paths.json')) as f:
        res_path = json.load(f)['res-path']

    if (res_path == '' or res_path == '.'):
        return resourcePath(partial_path)
    elif is_frozen():
        return os.path.join(res_path, partial_path)
    else:
        return partial_path

# Write to options json file
def write_options(options, filename, path = "res/options/"):
    full_path = os.path.join(get_full_path(path), filename)

    try:
        with open(full_path, 'w') as f:
            json.dump(options, f, indent=4)
    except OSError:
        print(f'ERROR: could not write to file "{full_path}"')

# Parse json options file
def parse_options(filename, path = "res/options/", from_=None):
    if not from_ or not os.path.isdir(from_):
        full_path = os.path.join(get_full_path(path), filename)
    else:
        full_path = os.path.join(from_, path, filename)

    try:
        with open(full_path) as f:
            return json.load(f)
    except OSError:
        print(f'ERROR: could not parse file "{full_path}"')
        return None

# Parse title of a file into [chartname, partname (if applicable), mimeType]
def parse_file(filename, alias_map=None):
    # title - part format
    match = re.search('(.*) - (.*)\.(.*)', filename)
    if match:
        return match.group(1), (alias_map.get(match.group(2)) or NO_PART) if alias_map else None, mimetypes.guess_type(filename)[0]

    # other file type format
    match = re.search('(.*)\.[\w+]', filename)
    if match:
        return match.group(1), None, mimetypes.guess_type(filename)[0]
    return None, None, None

# Convert dict of Part Name : [Valid Asiases] to dict of Alias : Part Name
def make_alias_map(parts_dict):
    alias_map = {}
    for part in parts_dict.keys():
        for alias in parts_dict[part]:
            alias_map[alias] = part
    return alias_map

# Gets the desired chart folder, searching in the directories in the id list. Returns a dictionary containing chart id and parent id
def get_chart_id(service, chart, id_list):
    for id in id_list:
        results = service.files().list(corpora="user",
                                       pageSize=1000,
                                       fields="files(id)",
                                       q=f'mimeType = "application/vnd.google-apps.folder" and'
                                         f'"{id}" in parents and '
                                         f'name="{chart}"',
                                       spaces="drive").execute()
        items = results.get('files', [])
        if len(items) == 1:
            return { "chart_id": items[0].get('id'), "parent_id": id }

    print(f'ERROR: "{chart}" folder not found in Digital Library directory')
    return { "chart_id": None, "parent_id": None }

# Gets the chart's parts folder, return its id (or None)
def get_parts_and_audio_folders(service, chart, chart_id):
    output = []
    for folder_name in ("Parts", "Audio"):
        # Search for parts folder
        folder_results = service.files().list(corpora="user",
                                            fields="files(id)",
                                            pageSize=1000,
                                            q=f'mimeType = "application/vnd.google-apps.folder" and'
                                            f'"{chart_id}" in parents and'
                                            f'name="{folder_name}"',
                                            spaces="drive").execute()
        items = folder_results.get('files', [])
        if len(items) != 1:
            print(f"ERROR: {folder_name} folder not found for current chart with name:", chart)
            output.append(None)
        else:
            output.append(items[0].get('id'))

    return tuple(output)

# Creates a folder
def make_folder(service, name, parent, makePublic=False):

    file_metadata = {
        'name': name,
        'mimeType': 'application/vnd.google-apps.folder',
        'parents': [parent]
    }
    try:
        response = service.files().create(body=file_metadata, fields='id').execute()
        folderId = response.get('id')
        if makePublic:
            service.permissions().create(fileId=folderId, body={'type': 'anyone', 'role': 'reader'})
        return response.get('id')
    except HttpError:
        print(f'ERROR: Unable to create folder "{name}"')
        return None

# Moves a file (or folder)
def move_file(service, file_id, old_parent, new_parent):
    try:
        service.files().update(fileId=file_id,
                                addParents=new_parent,
                                removeParents=old_parent,
                                fields='id, parents').execute()
    except HttpError:
        print(f'ERROR: Unable to move file')

# Creates a shortcut
def make_shortcut(service, name, target_id, parent):
    shortcut_metadata = {
        'name': name,
        'mimeType': 'application/vnd.google-apps.shortcut',
        'shortcutDetails': {
            'targetId': target_id
        },
        'parents': [parent]
    }
    service.files().create(body=shortcut_metadata).execute()

# Update an existing file's metadata and content.
# Based on: https://developers.google.com/drive/api/v2/reference/files/update
# Modified to work with v3 api
def update_file(service, file_id, new_filename, new_title=None, new_description=None, new_mime_type=None):
    try:
        # First retrieve the file from the API.
        file = service.files().get(fileId=file_id).execute()

        # File's new metadata.
        del file["id"]
        if (new_title): file['title'] = new_title
        if (new_description): file['description'] = new_description
        if (new_mime_type): file['mimeType'] = new_mime_type

        # File's new content.
        media_body = MediaFileUpload(new_filename, mimetype=new_mime_type, resumable=True)

        # Send the request to the API.
        updated_file = service.files().update(
            fileId=file_id,
            body=file,
            media_body=media_body,
            fields='id').execute()
        return updated_file["id"]
    except HttpError:
        print(f'Error when attempting to update file: {new_filename}')
        return None

# Upload a file to a folder in the drive
def upload_file(service, new_filename, display_name, parent, title=None, mime_type=None):
    try:
        # Create file metadata
        file_metadata = {
            'name' : display_name,
            'parents': [parent]
        }
        if title: file_metadata['title'] = title
        if mime_type: file_metadata['mimeType'] = mime_type

        # Make the file
        media = MediaFileUpload(new_filename, mimetype=mime_type)
        return service.files().create(body=file_metadata, media_body=media, fields='id').execute().get('id')
    except HttpError:
        print(f'WARNING: Unable to create file "{display_name}"')
        return None

# Downloads a file (or folder) from the drive
def download_file(service, file_id, dir, file_name, verbose=False):
    try:
        # Get target file
        request = service.files().get_media(fileId=file_id)

        # Resolve destination
        if not os.path.exists(dir): os.makedirs(dir)
        fh = io.FileIO(os.path.join(dir, file_name), "w")

        # Execute download
        downloader = MediaIoBaseDownload(fh, request)
        done = False
        if verbose: print(f'Starting download of "{file_name}"')
        while done is False:
            status, done = downloader.next_chunk()
            if verbose: print(f'Progress: {status.progress() * 100}%')
        if verbose: print("Completed download")
    except (OSError, HttpError):
        print(f'WARNING: Unable to download "{file_name}"')

# Returns the id(s) of a folder with the given id or name (optionally can specify parent directory)
def get_folder_ids(service, id = None, name = None, parent = None):
    # Check folder by id
    if (id != None):
        res = service.files().get(fileId=id, fields="id").execute()
        if res.get("id") and res["id"] == id: return [id]
        return None

    if (name == None): return None

    # Check folder by name + parent (optional)
    qstring = f'name="{name}" and mimeType="application/vnd.google-apps.folder"'
    if (parent != None): qstring += f' and "{parent}" in parents'

    results = service.files().list(q=qstring,
                                    corpora='user',
                                    pageSize=1000,
                                    fields='files(id)',
                                    includeItemsFromAllDrives=True,
                                    supportsAllDrives=True).execute()
    return [ res.get("id") for res in results.get("files") ] if results.get("files") else None

# Verify and return the digital library
def get_digital_library(service):
    """Verify that the DigitalLibrary folder and subfolders are in the correct locations.
    """
    check_stop_script()
    library_id, full_dig_id, current_id, future_id, archive_id = None, None, None, None, None

    # DigitalLibrary folder
    library_res = get_folder_ids(service, name="DigitalLibrary", parent="root")
    if not has_unique_folder(library_res, "DigitalLibrary", "My Drive"): return {}
    library_id = library_res[0]

    # Digitized Chart Data folder
    full_dig_res = get_folder_ids(service, name="LSJUMB Full Digitized Chart Data", parent=library_id)
    if not has_unique_folder(full_dig_res, "LSJUMB Full Digitized Chart Data", "DigitalLibrary"): return {}
    full_dig_id = full_dig_res[0]

    # Current Chartz folder
    current_res = get_folder_ids(service, name="Current Chartz", parent=full_dig_id)
    if not has_unique_folder(current_res, "Current Chartz", "LSJUMB Full Digitized Chart Data"): return {}
    current_id = current_res[0]

    # Old Chartz folder
    past_res = get_folder_ids(service, name="Old Chartz", parent=full_dig_id)
    if not has_unique_folder(past_res, "Old Chartz", "LSJUMB Full Digitized Chart Data"): return {}
    past_id = past_res[0]

    # Future Chartz folder
    future_res = get_folder_ids(service, name="Future Chartz", parent=full_dig_id)
    if not has_unique_folder(future_res, "Future Chartz", "LSJUMB Full Digitized Chart Data"): return {}
    future_id = future_res[0]

    # Archived Chartz folder
    archive_res = get_folder_ids(service, name="Archived Chartz", parent=full_dig_id)
    if not has_unique_folder(archive_res, "Archived Chartz", "LSJUMB Full Digitized Chart Data"): return {}
    archive_id = archive_res[0]

    return {"library_id": library_id, "current_id": current_id, "past_id": past_id, "future_id": future_id, "archive_id": archive_id}

# Get seperated section or separated sibelius parts folders
def get_separated_folders(service, library_id):
    check_stop_script()
    separated_ids = {}
    folder_names = [("Separated Sibelius Files", "sib"), ("Separated Section Parts", "sec"), ("Separated Part Audio", "aud")]
    for folder_name, abbr in folder_names:
        # Look for folder within Digital Library
        ids = get_folder_ids(service, name=folder_name, parent=library_id)
        if not has_unique_folder(ids, folder_name, "Digital Library"): return None
        separated_ids[abbr] = ids[0]

        # Look for Current, Old, Future, Archived Chartz folders
        for age_name, age in [("Current Chartz", "curr"), ("Old Chartz", "old"), ("Future Chartz", "future")]:
            age_ids = get_folder_ids(service, name=age_name, parent=ids[0])
            if not has_unique_folder(age_ids, age_name, folder_name): return None
            separated_ids[f'{abbr}_{age}'] = age_ids[0]

    return separated_ids

# Search folder for specific files/folders
def get_drive_files(service, id, file_types=None, files_only=True, folders_only=False, name=None, is_shortcut=False):
    output = []
    q = f'"{id}" in parents'
    if files_only: q += ' and not mimeType = "application/vnd.google-apps.folder"'
    if folders_only: q += ' and mimeType = "application/vnd.google-apps.folder"'
    if name: q += f' and name contains "{name}"'

    fields = f'files(id, name{", shortcutDetails" if is_shortcut else ""})'

    # Get all files/folders in the folder
    file_results = service.files().list(corpora="user",
                                        pageSize=1000,
                                        fields=fields,
                                        q=q,
                                        spaces="drive").execute()
    items = file_results.get('files', [])

    # Check that file extensions match
    for item in items:
        if not file_types or item.get('name')[-4:] in file_types:
            output.append(item)

    return output

# Return list of files in a given directory with a specific extension
def get_dir_files(dir, file_types):
    try:
        files = [ file for file in os.listdir(dir) if file[-4:] in file_types ]
        if len(files) == 0: raise OSError
        return files
    except OSError:
        print(f'ERROR: No supported files found in directory "{dir}"')
        sys.exit()

# A helper function that outputs some useful error messages
def has_unique_folder(res, target, parent):
    if res == None or len(res) == 0:
        print(f'ERROR: "{target}" folder not found in "{parent}" directory, please check Google Drive')
        return False
    elif len(res) > 1:
        print(f'ERROR: Multiple instances of "{target}" folder found in "{parent}" directory, please check Google Drive')
        print(f'Double-check the Trash section of Google Drive, as it still belongs to the "{parent}" folder')
        return False
    else:
        return True


'''
TODO: 未完成（难，优先度低）
动态的validator set的生成机制
new validators to join and existing validators to leave
#first version: finished
#second version: deposit mechanism and first dynasty initialization finished
#third verison: transaction need to do
'''
import copy
import logging

logger = logging.getLogger(__name__)
from configuration.generalParameters import INIT_DYNASTY

class Dynasty:
    def __init__(self):
        # [(new,expert,retired)] d+2
        self.dynasties = [[[], INIT_DYNASTY, []], [[], [], []], [[], [], []]]
        self.current_epoch = 0  # d
        self.join_community = [] + INIT_DYNASTY
        # {validator_addr:[deposit,epoch]}
        self.deposit_bank = {}
        self.withdraw_delay = 1

    def joinDynasty(self, validator_address, deposit=1):
        '''
        func: apply to join the validator set
        args:
            validatorAddress: the identity of new validator
            deposit: the deposit of the new validator
        '''

        if not deposit or deposit <= 0:
            logger.warning('warning: validator %s please pay the deposit' % validator_address)
            return

        # 如果曾经加入过，就再也不能加入了
        if validator_address not in self.join_community:
            self.deposit_bank[validator_address] = [deposit, self.current_epoch]
            self.dynasties[self.current_epoch + 2][0].append(validator_address)
            # join
            self.join_community.append(validator_address)

    def quitDynasty(self, validator_address):
        '''
        func: apply to quit the validator set
        args:
            validatorAddress: the identity of new validator
        '''
        if validator_address in self.dynasties[self.current_epoch][1]:
            # join_dynasty changed to quit dynasty(d+2+5)
            self.deposit_bank[validator_address][1] = self.current_epoch + 2 + self.withdraw_delay
            self.dynasties[self.current_epoch + 2][2].append(validator_address)

        elif validator_address in self.dynasties[self.current_epoch + 1][0]:
            # join_dynasty changed to quit dynasty(d+2+5)
            self.deposit_bank[validator_address][1] = self.current_epoch + 2 + self.withdraw_delay
            self.dynasties[self.current_epoch + 2][0].remove(validator_address)
            self.dynasties[self.current_epoch + 2][2].append(validator_address)
        else:
            logger.warning('warning: %s not in the new and expert validator', str(validator_address))

    def dynastyChange(self):
        '''
        func: dynasty change
        '''
        self.current_epoch += 1

        self.dynasties.append([[], [], []])

        current_dynasty = self.dynasties[self.current_epoch]
        previous_dynasty = self.dynasties[self.current_epoch - 1]

        previous_new = set(copy.deepcopy(previous_dynasty[0]))
        previous_og = set(copy.deepcopy(previous_dynasty[1]))

        current_retire = set(copy.deepcopy(current_dynasty[2]))
        current_og = list(previous_og.union(previous_new) - current_retire)

        current_dynasty[1] = current_og
        #
        # self.dynasties.append(copy.deepcopy(self.dynasties[-1]))
        # # new validator move to expert validator
        # new_people = copy.deepcopy(self.dynasties[-1][0])
        # # print(self.dynasties[-1][1])
        # # print(new_people)
        # # self.dynasties[-1][1]=new_people
        # for validatorAddress in new_people:
        #     self.dynasties[-1][1].append(validatorAddress)
        # self.dynasties[-1][0].clear()
        # # print('current bank',self.deposit_bank)
        # removed_key = []
        # for k,v in self.deposit_bank.items():
        #     # print('-----------',k,v)
        #     if v[1] == self.current_epoch:
        #         removed_key.append(k)
        # if removed_key:
        #     for key in removed_key:
        #         self.deposit_bank.pop(key)
        #     removed_key = []
        #

# if __name__ == '__main__':
#     a = Dynasty()
#     #------join
#     a.join(1,10)
#     a.join(2,10)
#     a.join(3,0)
#     print('current dynasty ',a.current_epoch,'total dynasty',a.dynasties)
#     print('deposit bank ',a.deposit_bank)
#     #------dynastyChange
#     a.dynastyChange()
#     print('current dynasty ',a.current_epoch,'total dynasty',a.dynasties)
#     # #------
#     a.join(4,22)
#     print('current dynasty ',a.current_epoch,'total dynasty',a.dynasties)
#     print('deposit bank ', a.deposit_bank)
#     # #------wrong quit
#     a.quit(5)
#     print(a.dynasties)
#     # #------right quit
#     a.quit(1)
#     print('current dynasty ',a.current_epoch,'total dynasty',a.dynasties)
#     print('deposit bank ', a.deposit_bank)
#     # #------dynasty change
#     a.dynastyChange()
#     print('current dynasty ',a.current_epoch,'total dynasty',a.dynasties)
#     print('deposit bank ', a.deposit_bank)
#     a.dynastyChange()
#     print('current dynasty ', a.current_epoch, 'total dynasty', a.dynasties)
#     print('deposit bank ', a.deposit_bank)
#     a.dynastyChange()
#     print('current dynasty ', a.current_epoch, 'total dynasty', a.dynasties)
#     print('deposit bank ', a.deposit_bank)
#     print(a.join_community)


import os
import traceback

import grpc

from tensorflow.core.debug import debug_service_pb2
from tensorflow.python.client import session
from tensorflow.python.debug.lib import grpc_debug_test_server
from tensorflow.python.debug.lib import source_remote
from tensorflow.python.debug.lib import source_utils
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import math_ops
# Import resource_variable_ops for the variables-to-tensor implicit conversion.
from tensorflow.python.ops import resource_variable_ops  # pylint: disable=unused-import
from tensorflow.python.ops import variables
from tensorflow.python.platform import googletest
from tensorflow.python.platform import test
from tensorflow.python.util import tf_inspect

def line_number_above():
  return tf_inspect.stack()[1][2] - 1

class SendTracebacksTest(test_util.TensorFlowTestCase):

  @classmethod
  def setUpClass(cls):
    test_util.TensorFlowTestCase.setUpClass()
    (cls._server_port, cls._debug_server_url, cls._server_dump_dir,
     cls._server_thread,
     cls._server) = grpc_debug_test_server.start_server_on_separate_thread(
         poll_server=True)
    cls._server_address = "localhost:%d" % cls._server_port
    (cls._server_port_2, cls._debug_server_url_2, cls._server_dump_dir_2,
     cls._server_thread_2,
     cls._server_2) = grpc_debug_test_server.start_server_on_separate_thread()
    cls._server_address_2 = "localhost:%d" % cls._server_port_2
    cls._curr_file_path = os.path.normpath(os.path.abspath(__file__))

  @classmethod
  def tearDownClass(cls):
    # Stop the test server and join the thread.
    cls._server.stop_server().wait()
    cls._server_thread.join()
    cls._server_2.stop_server().wait()
    cls._server_thread_2.join()
    test_util.TensorFlowTestCase.tearDownClass()

  def tearDown(self):
    ops.reset_default_graph()
    self._server.clear_data()
    self._server_2.clear_data()
    super(SendTracebacksTest, self).tearDown()

  def _findFirstTraceInsideTensorFlowPyLibrary(self, op):
    """Find the first trace of an op that belongs to the TF Python library."""
    for trace in op.traceback:
      if source_utils.guess_is_tensorflow_py_library(trace.filename):
        return trace

  def testSendGraphTracebacksToSingleDebugServer(self):
    this_func_name = "testSendGraphTracebacksToSingleDebugServer"
    with session.Session() as sess:
      a = variables.Variable(21.0, name="a")
      a_lineno = line_number_above()
      b = variables.Variable(2.0, name="b")
      b_lineno = line_number_above()
      math_ops.add(a, b, name="x")
      x_lineno = line_number_above()

      send_stack = traceback.extract_stack()
      send_lineno = line_number_above()
      source_remote.send_graph_tracebacks(
          self._server_address, "dummy_run_key", send_stack, sess.graph)

      tb = self._server.query_op_traceback("a")
      self.assertIn((self._curr_file_path, a_lineno, this_func_name), tb)
      tb = self._server.query_op_traceback("b")
      self.assertIn((self._curr_file_path, b_lineno, this_func_name), tb)
      tb = self._server.query_op_traceback("x")
      self.assertIn((self._curr_file_path, x_lineno, this_func_name), tb)

      self.assertIn(
          (self._curr_file_path, send_lineno, this_func_name),
          self._server.query_origin_stack()[-1])

      self.assertEqual(
          "      a = variables.Variable(21.0, name=\"a\")",
          self._server.query_source_file_line(__file__, a_lineno))
      # Files in the TensorFlow code base shouldn not have been sent.
      tf_trace = self._findFirstTraceInsideTensorFlowPyLibrary(a.op)
      tf_trace_file_path = tf_trace.filename
      with self.assertRaises(ValueError):
        self._server.query_source_file_line(tf_trace_file_path, 0)
      self.assertEqual([debug_service_pb2.CallTraceback.GRAPH_EXECUTION],
                       self._server.query_call_types())
      self.assertEqual(["dummy_run_key"], self._server.query_call_keys())
      self.assertEqual(
          [sess.graph.version], self._server.query_graph_versions())

  def testSendGraphTracebacksToTwoDebugServers(self):
    this_func_name = "testSendGraphTracebacksToTwoDebugServers"
    with session.Session() as sess:
      a = variables.Variable(21.0, name="two/a")
      a_lineno = line_number_above()
      b = variables.Variable(2.0, name="two/b")
      b_lineno = line_number_above()
      x = math_ops.add(a, b, name="two/x")
      x_lineno = line_number_above()

      send_traceback = traceback.extract_stack()
      send_lineno = line_number_above()

      with test.mock.patch.object(
          grpc, "insecure_channel",
          wraps=grpc.insecure_channel) as mock_grpc_channel:
        source_remote.send_graph_tracebacks(
            [self._server_address, self._server_address_2],
            "dummy_run_key", send_traceback, sess.graph)
        mock_grpc_channel.assert_called_with(
            test.mock.ANY,
            options=[("grpc.max_receive_message_length", -1),
                     ("grpc.max_send_message_length", -1)])

      servers = [self._server, self._server_2]
      for server in servers:
        tb = server.query_op_traceback("two/a")
        self.assertIn((self._curr_file_path, a_lineno, this_func_name), tb)
        tb = server.query_op_traceback("two/b")
        self.assertIn((self._curr_file_path, b_lineno, this_func_name), tb)
        tb = server.query_op_traceback("two/x")
        self.assertIn((self._curr_file_path, x_lineno, this_func_name), tb)

        self.assertIn(
            (self._curr_file_path, send_lineno, this_func_name),
            server.query_origin_stack()[-1])

        self.assertEqual(
            "      x = math_ops.add(a, b, name=\"two/x\")",
            server.query_source_file_line(__file__, x_lineno))
        tf_trace = self._findFirstTraceInsideTensorFlowPyLibrary(a.op)
        tf_trace_file_path = tf_trace.filename
        with self.assertRaises(ValueError):
          server.query_source_file_line(tf_trace_file_path, 0)
        self.assertEqual([debug_service_pb2.CallTraceback.GRAPH_EXECUTION],
                         server.query_call_types())
        self.assertEqual(["dummy_run_key"], server.query_call_keys())
        self.assertEqual([sess.graph.version], server.query_graph_versions())

  def testSendEagerTracebacksToSingleDebugServer(self):
    this_func_name = "testSendEagerTracebacksToSingleDebugServer"
    send_traceback = traceback.extract_stack()
    send_lineno = line_number_above()
    source_remote.send_eager_tracebacks(self._server_address, send_traceback)

    self.assertEqual([debug_service_pb2.CallTraceback.EAGER_EXECUTION],
                     self._server.query_call_types())
    self.assertIn((self._curr_file_path, send_lineno, this_func_name),
                  self._server.query_origin_stack()[-1])

  def testGRPCServerMessageSizeLimit(self):
    """Assert gRPC debug server is started with unlimited message size."""
    with test.mock.patch.object(
        grpc, "server", wraps=grpc.server) as mock_grpc_server:
      (_, _, _, server_thread,
       server) = grpc_debug_test_server.start_server_on_separate_thread(
           poll_server=True)
      mock_grpc_server.assert_called_with(
          test.mock.ANY,
          options=[("grpc.max_receive_message_length", -1),
                   ("grpc.max_send_message_length", -1)])
    server.stop_server().wait()
    server_thread.join()

if __name__ == "__main__":
  googletest.main()


# coding=utf-8
"""test distillnation(Edition 2), not use."""

from __future__ import absolute_import, division, print_function
import argparse
import csv
import logging
import os
import random
import sys

import numpy as np
import torch
from torch.utils.data import (DataLoader, RandomSampler, SequentialSampler,
                              TensorDataset)
from tqdm import tqdm, trange
from torch.nn import CrossEntropyLoss, MSELoss
from scipy.stats import pearsonr, spearmanr
from sklearn.metrics import matthews_corrcoef, f1_score
from ..transformer.file_utils import WEIGHTS_NAME, CONFIG_NAME
from ..Bi_LSTM.modeling import BiLSTM
from ..Bi_LSTM.tokenization import BiLstmTokenizer

csv.field_size_limit(sys.maxsize)

log_format = '%(asctime)s %(message)s'
logging.basicConfig(stream=sys.stdout, level=logging.INFO,
                    format=log_format, datefmt='%m/%d %I:%M:%S %p')
fh = logging.FileHandler('debug_layer_loss.log')
fh.setFormatter(logging.Formatter(log_format))
logging.getLogger().addHandler(fh)
logger = logging.getLogger()

oncloud = True
try:
    import moxing as mox
except:
    oncloud = False

class InputExample(object):
    """A single training/test example for simple sequence classification."""

    def __init__(self, guid, text_a, text_b=None, label=None):
        """Constructs a InputExample.

        Args:
            guid: Unique id for the example.
            text_a: string. The untokenized text of the first sequence. For single
            sequence tasks, only this sequence must be specified.
            text_b: (Optional) string. The untokenized text of the second sequence.
            Only must be specified for sequence pair tasks.
            label: (Optional) string. The label of the example. This should be
            specified for train and dev examples, but not for test examples.
        """
        self.guid = guid
        self.text_a = text_a
        self.text_b = text_b
        self.label = label

class InputFeatures(object):
    """A single set of features of data."""

    def __init__(self, input_ids, input_mask, segment_ids, label_id, seq_length=None):
        self.input_ids = input_ids
        self.input_mask = input_mask
        self.segment_ids = segment_ids
        self.seq_length = seq_length
        self.label_id = label_id

class DataProcessor(object):
    """Base class for data converters for sequence classification data sets."""

    def get_train_examples(self, data_dir):
        """Gets a collection of `InputExample`s for the train set."""
        raise NotImplementedError()

    def get_dev_examples(self, data_dir):
        """Gets a collection of `InputExample`s for the dev set."""
        raise NotImplementedError()

    def get_labels(self):
        """Gets the list of labels for this data set."""
        raise NotImplementedError()

    @classmethod
    def _read_tsv(cls, input_file, quotechar=None):
        """Reads a tab separated value file."""
        with open(input_file, "r", encoding="utf-8") as f:
            reader = csv.reader(f, delimiter="\t", quotechar=quotechar)
            lines = []
            for line in reader:
                if sys.version_info[0] == 2:
                    line = list(unicode(cell, 'utf-8') for cell in line)
                lines.append(line)
            return lines

class Sst2Processor(DataProcessor):
    """Processor for the SST-2 data set (GLUE version)."""

    def get_train_examples(self, data_dir):
        """See base class."""
        return self._create_examples(
            self._read_tsv(os.path.join(data_dir, "train.tsv")), "train")

    def get_dev_examples(self, data_dir):
        """See base class."""
        return self._create_examples(
            self._read_tsv(os.path.join(data_dir, "dev.tsv")), "dev")

    def get_aug_examples(self, data_dir):
        return self._create_examples(
            self._read_tsv(os.path.join(data_dir, "train_aug.tsv")), "aug")

    def get_labels(self):
        """See base class."""
        return ["0", "1"]

    def _create_examples(self, lines, set_type):
        """Creates examples for the training and dev sets."""
        examples = []
        for (i, line) in enumerate(lines):
            if i == 0:
                continue
            guid = "%s-%s" % (set_type, i)
            text_a = line[0]
            label = line[1]
            examples.append(
                InputExample(guid=guid, text_a=text_a, text_b=None, label=label))
        return examples

def convert_examples_to_features4lstm(examples, label_list, max_seq_length,
                                 tokenizer, output_mode):
    """Loads a data file into a list of `InputBatch`s."""

    label_map = {label: i for i, label in enumerate(label_list)}

    features = []
    for (ex_index, example) in enumerate(examples):
        if ex_index % 10000 == 0:
            logger.info("Writing example %d of %d" % (ex_index, len(examples)))

        tokens = tokenizer.tokenize(example.text_a)

        input_ids = tokenizer.convert_tokens_to_ids(tokens)
        seq_length = len(input_ids)
        # input_mask = [1] * len(input_ids)

        padding = [0] * (max_seq_length - len(input_ids))
        input_ids += padding
        # input_mask += padding

        assert len(input_ids) == max_seq_length
        # assert len(input_mask) == max_seq_length

        if output_mode == "classification":
            label_id = label_map[example.label]
        elif output_mode == "regression":
            label_id = float(example.label)
        else:
            raise KeyError(output_mode)

        if ex_index < 1:
            logger.info("*** Example ***")
            logger.info("guid: %s" % (example.guid))
            logger.info("tokens: %s" % " ".join(
                [str(x) for x in tokens]))
            logger.info("input_ids: %s" % " ".join([str(x) for x in input_ids]))
            # logger.info("input_mask: %s" % " ".join([str(x) for x in input_mask]))
            logger.info("label: {}".format(example.label))
            logger.info("label_id: {}".format(label_id))

        features.append(
            InputFeatures(input_ids=input_ids,
                          input_mask=None,
                          segment_ids=None,
                          label_id=label_id,
                          seq_length=seq_length))
    return features

def _truncate_seq_pair(tokens_a, tokens_b, max_length):
    """Truncates a sequence pair in place to the maximum length."""
    while True:
        total_length = len(tokens_a) + len(tokens_b)
        if total_length <= max_length:
            break
        if len(tokens_a) > len(tokens_b):
            tokens_a.pop()
        else:
            tokens_b.pop()

def simple_accuracy(preds, labels):
    return (preds == labels).mean()

def acc_and_f1(preds, labels):
    acc = simple_accuracy(preds, labels)
    f1 = f1_score(y_true=labels, y_pred=preds)
    return {
        "acc": acc,
        "f1": f1,
        "acc_and_f1": (acc + f1) / 2,
    }

def pearson_and_spearman(preds, labels):
    pearson_corr = pearsonr(preds, labels)[0]
    spearman_corr = spearmanr(preds, labels)[0]
    return {
        "pearson": pearson_corr,
        "spearmanr": spearman_corr,
        "corr": (pearson_corr + spearman_corr) / 2,
    }

def compute_metrics(task_name, preds, labels):
    assert len(preds) == len(labels)
    if task_name == "cola":
        return {"mcc": matthews_corrcoef(labels, preds)}
    elif task_name == "sst-2":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "mrpc":
        return acc_and_f1(preds, labels)
    elif task_name == "sts-b":
        return pearson_and_spearman(preds, labels)
    elif task_name == "qqp":
        return acc_and_f1(preds, labels)
    elif task_name == "mnli":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "mnli-mm":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "qnli":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "rte":
        return {"acc": simple_accuracy(preds, labels)}
    elif task_name == "wnli":
        return {"acc": simple_accuracy(preds, labels)}
    else:
        raise KeyError(task_name)

def get_tensor_data(output_mode, features):
    if output_mode == "classification":
        all_label_ids = torch.tensor([f.label_id for f in features], dtype=torch.long)
    elif output_mode == "regression":
        all_label_ids = torch.tensor([f.label_id for f in features], dtype=torch.float)

    all_seq_lengths = torch.tensor([f.seq_length for f in features], dtype=torch.long)
    all_input_ids = torch.tensor([f.input_ids for f in features], dtype=torch.long)
    all_input_mask = torch.tensor([f.input_mask for f in features], dtype=torch.long)
    all_segment_ids = torch.tensor([f.segment_ids for f in features], dtype=torch.long)
    tensor_data = TensorDataset(all_input_ids, all_input_mask, all_segment_ids,
                                all_label_ids, all_seq_lengths)
    return tensor_data, all_label_ids

def result_to_file(result, file_name):
    with open(file_name, "a") as writer:
        logger.info("***** Eval results *****")
        for key in sorted(result.keys()):
            logger.info("  %s = %s", key, str(result[key]))
            writer.write("%s = %s\n" % (key, str(result[key])))

def do_eval(model, task_name, eval_dataloader,
            device, output_mode, eval_labels, num_labels):
    eval_loss = 0
    nb_eval_steps = 0
    preds = []

    for batch_ in tqdm(eval_dataloader, desc="Evaluating"):
        batch_ = tuple(t.to(device) for t in batch_)
        with torch.no_grad():
            input_ids, _, _, label_ids, seq_lengths = batch_
            _, logits, _ = model(input_ids, seq_lengths)

        # create eval loss and other metric required by the task
        if output_mode == "classification":
            loss_fct = CrossEntropyLoss()
            tmp_eval_loss = loss_fct(logits.view(-1, num_labels), label_ids.view(-1))
        elif output_mode == "regression":
            loss_fct = MSELoss()
            tmp_eval_loss = loss_fct(logits.view(-1), label_ids.view(-1))

        eval_loss += tmp_eval_loss.mean().item()
        nb_eval_steps += 1
        if len(preds) == 0:
            preds.append(logits.detach().cpu().numpy())
        else:
            preds[0] = np.append(
                preds[0], logits.detach().cpu().numpy(), axis=0)

    eval_loss = eval_loss / nb_eval_steps

    preds = preds[0]
    if output_mode == "classification":
        preds = np.argmax(preds, axis=1)
    elif output_mode == "regression":
        preds = np.squeeze(preds)
    result = compute_metrics(task_name, preds, eval_labels.numpy())
    result['eval_loss'] = eval_loss

    return result

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("--data_dir",
                        default=None,
                        type=str,
                        required=True,
                        help="The input data dir. Should contain the .tsv files (or other data files) for the task.")
    parser.add_argument("--teacher_model",
                        default=None,
                        type=str,
                        help="The teacher model dir.")
    parser.add_argument("--student_model",
                        default=None,
                        type=str,
                        required=True,
                        help="The student model dir.")
    parser.add_argument("--task_name",
                        default=None,
                        type=str,
                        required=True,
                        help="The name of the task to train.")
    parser.add_argument("--output_dir",
                        default=None,
                        type=str,
                        required=True,
                        help="The output directory where the model predictions and checkpoints will be written.")
    parser.add_argument("--cache_dir",
                        default="",
                        type=str,
                        help="Where do you want to store the pre-trained models downloaded from s3")
    parser.add_argument("--max_seq_length",
                        default=128,
                        type=int,
                        help="The maximum total input sequence length after WordPiece tokenization. \n"
                             "Sequences longer than this will be truncated, and sequences shorter \n"
                             "than this will be padded.")
    parser.add_argument("--do_eval",
                        action='store_true',
                        help="Whether to run eval on the dev set.")
    parser.add_argument("--do_lower_case",
                        action='store_true',
                        help="Set this flag if you are using an uncased model.")
    parser.add_argument("--train_batch_size",
                        default=32,
                        type=int,
                        help="Total batch size for training.")
    parser.add_argument("--eval_batch_size",
                        default=32,
                        type=int,
                        help="Total batch size for eval.")
    parser.add_argument("--learning_rate",
                        default=5e-5,
                        type=float,
                        help="The initial learning rate for Adam.")
    parser.add_argument('--weight_decay', '--wd',
                        default=1e-4,
                        type=float,
                        metavar='W',
                        help='weight decay')
    parser.add_argument("--num_train_epochs",
                        default=3.0,
                        type=float,
                        help="Total number of training epochs to perform.")
    parser.add_argument("--warmup_proportion",
                        default=0.1,
                        type=float,
                        help="Proportion of training to perform linear learning rate warmup for. "
                             "E.g., 0.1 = 10%% of training.")
    parser.add_argument("--no_cuda",
                        action='store_true',
                        help="Whether not to use CUDA when available")
    parser.add_argument('--seed',
                        type=int,
                        default=42,
                        help="random seed for initialization")
    parser.add_argument('--gradient_accumulation_steps',
                        type=int,
                        default=1,
                        help="Number of updates steps to accumulate before performing a backward/update pass.")

    # added arguments
    parser.add_argument('--aug_train',
                        action='store_true')
    parser.add_argument('--eval_step',
                        type=int,
                        default=50)
    parser.add_argument('--data_url',
                        type=str,
                        default="")
    parser.add_argument('--temperature',
                        type=float,
                        default=1.)
    parser.add_argument('--embedding_size',
                        type=int,
                        default=300)
    parser.add_argument('--hidden_size4lstm',
                        type=float,
                        default=600)

    args = parser.parse_args()
    logger.info('The args: {}'.format(args))

    processors = {
        "sst-2": Sst2Processor,
    }

    output_modes = {
        "sst-2": "classification",
    }

    # intermediate distillation default parameters
    default_params = {
        "sst-2": {"num_train_epochs": 10, "max_seq_length": 64},
    }

    acc_tasks = ["mnli", "mrpc", "sst-2", "qqp", "qnli", "rte"]
    corr_tasks = ["sts-b"]
    mcc_tasks = ["cola"]

    # Prepare devices
    device = torch.device("cuda" if torch.cuda.is_available() and not args.no_cuda else "cpu")
    n_gpu = torch.cuda.device_count()

    logging.basicConfig(format='%(asctime)s - %(levelname)s - %(name)s -   %(message)s',
                        datefmt='%m/%d/%Y %H:%M:%S',
                        level=logging.INFO)

    logger.info("device: {} n_gpu: {}".format(device, n_gpu))

    # Prepare seed
    random.seed(args.seed)
    np.random.seed(args.seed)
    torch.manual_seed(args.seed)
    if n_gpu > 0:
        torch.cuda.manual_seed_all(args.seed)

    # Prepare task settings
    if os.path.exists(args.output_dir) and os.listdir(args.output_dir):
        raise ValueError("Output directory ({}) already exists and is not empty.".format(args.output_dir))
    if not os.path.exists(args.output_dir):
        os.makedirs(args.output_dir)

    task_name = args.task_name.lower()

    if task_name in default_params:
        args.max_seq_len = default_params[task_name]["max_seq_length"]

    if not args.do_eval:
        if task_name in default_params:
            args.num_train_epoch = default_params[task_name]["num_train_epochs"]

    if task_name not in processors:
        raise ValueError("Task not found: %s" % task_name)

    processor = processors[task_name]()
    output_mode = output_modes[task_name]
    label_list = processor.get_labels()
    num_labels = len(label_list)

    tokenizer = BiLstmTokenizer(vocab_file=args.glove_vocab_file)

    if not args.do_eval:
        if not args.aug_train:
            train_examples = processor.get_train_examples(args.data_dir)
        else:
            train_examples = processor.get_aug_examples(args.data_dir)
        if args.gradient_accumulation_steps < 1:
            raise ValueError("Invalid gradient_accumulation_steps parameter: {}, should be >= 1".format(
                args.gradient_accumulation_steps))

        args.train_batch_size = args.train_batch_size // args.gradient_accumulation_steps

        num_train_optimization_steps = int(
            len(train_examples) / args.train_batch_size / args.gradient_accumulation_steps) * args.num_train_epochs

        # 这里需要生成两种 train_dataloader
        train_features = convert_examples_to_features4lstm(train_examples, label_list,
                                                                args.max_seq_length, tokenizer, output_mode)
        train_data, _ = get_tensor_data(output_mode, train_features)
        train_sampler = RandomSampler(train_data)
        train_dataloader = DataLoader(train_data, sampler=train_sampler, batch_size=args.train_batch_size)

    eval_examples = processor.get_dev_examples(args.data_dir)
    eval_features = convert_examples_to_features4lstm(eval_examples, label_list, args.max_seq_length, tokenizer, output_mode)
    eval_data, eval_labels = get_tensor_data(output_mode, eval_features)
    eval_sampler = SequentialSampler(eval_data)
    eval_dataloader = DataLoader(eval_data, sampler=eval_sampler, batch_size=args.eval_batch_size)

    lstm_model = BiLSTM(embedding_size=args.embedding_size, n_hidden1=args.hidden_size4lstm, n_class=num_labels, max_len=args.max_seq_len)
    lstm_model.to(device)

    if args.do_eval:
        logger.info("***** Running evaluation *****")
        logger.info("  Num examples = %d", len(eval_examples))
        logger.info("  Batch size = %d", args.eval_batch_size)

        lstm_model.eval()
        result = do_eval(lstm_model, task_name, eval_dataloader,
                         device, output_mode, eval_labels, num_labels)
        logger.info("***** Eval results *****")
        for key in sorted(result.keys()):
            logger.info("  %s = %s", key, str(result[key]))
    else:
        logger.info("***** Running training *****")
        logger.info("  Num examples = %d", len(train_examples))
        logger.info("  Batch size = %d", args.train_batch_size)
        logger.info("  Num steps = %d", num_train_optimization_steps)
        if n_gpu > 1:
            lstm_model = torch.nn.DataParallel(lstm_model)
        # Prepare optimizer
        param_optimizer = list(lstm_model.named_parameters())
        size = 0
        for n, p in lstm_model.named_parameters():
            logger.info('n: {}'.format(n))
            size += p.nelement()

        logger.info('Total parameters: {}'.format(size))
        no_decay = ['bias', 'LayerNorm.bias', 'LayerNorm.weight']
        optimizer_grouped_parameters = [
            {'params': [p for n, p in param_optimizer if not any(nd in n for nd in no_decay)], 'weight_decay': 0.01},
            {'params': [p for n, p in param_optimizer if any(nd in n for nd in no_decay)], 'weight_decay': 0.0}
        ]
        # schedule = 'none'
        # optimizer = BertAdam(optimizer_grouped_parameters,
        #                      schedule=schedule,
        #                      lr=args.learning_rate,
        #                      warmup=args.warmup_proportion,
        #                      t_total=num_train_optimization_steps)
        # Prepare loss functions
        optimizer = torch.optim.adadelta.Adadelta(optimizer_grouped_parameters)
        loss_mse = MSELoss()

        def soft_cross_entropy(predicts, targets):
            student_likelihood = torch.nn.functional.log_softmax(predicts, dim=-1)
            targets_prob = torch.nn.functional.softmax(targets, dim=-1)
            return (- targets_prob * student_likelihood).mean()

        # Train and evaluate
        global_step = 0
        best_dev_acc = 0.0
        output_eval_file = os.path.join(args.output_dir, "eval_results.txt")

        for epoch_ in trange(int(args.num_train_epochs), desc="Epoch"):
            tr_loss = 0.

            lstm_model.train()
            nb_tr_examples, nb_tr_steps = 0, 0

            for step, batch in enumerate(tqdm(train_dataloader, desc="Iteration", ascii=True)):
                batch = tuple(t.to(device) for t in batch)

                input_ids, input_mask, segment_ids, label_ids, seq_lengths = batch
                if input_ids.size()[0] != args.train_batch_size:
                    continue

                input_ids, _, _, _, seq_lengths = batch
                _, _, logits, reps = lstm_model(input_ids, seq_lengths)

                loss_fct = CrossEntropyLoss()
                loss = loss_fct(logits.view(-1, num_labels), label_ids.view(-1))

                if n_gpu > 1:
                    loss = loss.mean()  # mean() to average on multi-gpu.
                if args.gradient_accumulation_steps > 1:
                    loss = loss / args.gradient_accumulation_steps

                loss.backward()

                tr_loss += loss.item()
                nb_tr_examples += label_ids.size(0)
                nb_tr_steps += 1

                if (step + 1) % args.gradient_accumulation_steps == 0:
                    optimizer.step()
                    optimizer.zero_grad()
                    global_step += 1

                if (global_step + 1) % args.eval_step == 0:
                    logger.info("***** Running evaluation *****")
                    logger.info("  Epoch = {} iter {} step".format(epoch_, global_step))
                    logger.info("  Num examples = %d", len(eval_examples))
                    logger.info("  Batch size = %d", args.eval_batch_size)

                    lstm_model.eval()

                    loss = tr_loss / (step + 1)

                    result = do_eval(lstm_model, task_name, eval_dataloader,
                                     device, output_mode, eval_labels, num_labels)
                    result['global_step'] = global_step
                    result['loss'] = loss

                    result_to_file(result, output_eval_file)

                    save_model = False

                    if task_name in acc_tasks and result['acc'] > best_dev_acc:
                        best_dev_acc = result['acc']
                        save_model = True

                    if task_name in corr_tasks and result['corr'] > best_dev_acc:
                        best_dev_acc = result['corr']
                        save_model = True

                    if task_name in mcc_tasks and result['mcc'] > best_dev_acc:
                        best_dev_acc = result['mcc']
                        save_model = True

                    if save_model:
                        logger.info("***** Save model *****")
                        model_to_save = lstm_model.module if hasattr(lstm_model, 'module') else lstm_model
                        model_name = WEIGHTS_NAME
                        output_model_file = os.path.join(args.output_dir, model_name)
                        torch.save(model_to_save, output_model_file)

                    lstm_model.train()

if __name__ == "__main__":
    main()


# ----------------------- #
# | SANJANA KUCHIBHOTLA | #
# | 15-112 TERM PROJECT | #
# ----------------------- #

from cmu_112_graphics import *
from classes import *
from floodfill import *

# SAMPLE IMAGES:
# dog image from google: https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.petfinder.com%2Fdog-breeds%2F&psig=AOvVaw3Fm5UKfPEVjxaWVddBDKIY&ust=1669057075474000&source=images&cd=vfe&ved=0CA8QjRxqFwoTCPDvvqS4vfsCFQAAAAAdAAAAABAD
# duck image from google: https://www.google.com/url?sa=i&url=https%3A%2F%2Fwww.bestprintingonline.com%2Fresolution.htm&psig=AOvVaw0fu7WJ7dQ9LvwCfQCWN03z&ust=1670468964572000&source=images&cd=vfe&ved=0CA8QjRxqFwoTCLCY84XE5vsCFQAAAAAdAAAAABAD
# square image from google: https://www.google.com/url?sa=i&url=https%3A%2F%2Fillustoon.com%2F%3Fid%3D7281&psig=AOvVaw0h_pkpfIxYlsMN0T5FgrDe&ust=1670469096949000&source=images&cd=vfe&ved=0CA8QjRxqFwoTCOD81LrE5vsCFQAAAAAdAAAAABAD

# ############################################### #
# --------------- SPLASH SCREEN ----------------- #
# ############################################### #

def splashScreenMode_keyPressed(app, event):
    if event.key=='Space':
        app.mode = 'userMode'

def splashScreenMode_mousePressed(app, event):
    if clickedHere(app, event.x, event.y):
        app.mode = 'startMode'

def clickedHere(app, x, y):
    return app.width/3 < x < 2*app.width/3 and 5*app.height/8 < y < 6*app.height/8

def drawClickHere(app, canvas):
    canvas.create_rectangle(app.width/3-10, 5*app.height/8-10,
                            2*app.width/3+10, 6*app.height/8+10, outline='dark green', width=5)
    canvas.create_rectangle(app.width/3, 5*app.height/8,
                            2*app.width/3, 6*app.height/8, fill='black', activefill='dark green')
    canvas.create_text(app.width/2, 5.5*app.height/8, text='Click Here to Edit!',
                       font='Copperplate 30 bold', fill = 'SlateGray1')

def drawTitle(app, canvas):
    font = 'Copperplate 50 bold'
    canvas.create_rectangle(app.width/3, app.height/5,
                            2*app.width/3, 7*app.height/15,
                            outline='dark green', width=5)
    canvas.create_rectangle(app.width/3-15, app.height/5-15,
                            2*app.width/3+15, 7*app.height/15+15,
                            outline='dark green', width=5)
    canvas.create_text(7.5*app.width/15, 4.5*app.height/15, text='Image',
                       font=font, fill='SlateGray1')
    canvas.create_text(7.5*app.width/15, 5.5*app.height/15, text='Editor',
                       font=font, fill='SlateGray1')

def splashScreenMode_redrawAll(app, canvas):
    canvas.create_rectangle(0,0,app.width,app.height,fill='DarkOliveGreen4')
    canvas.create_rectangle(30, 30, app.width-30, app.height-30,
                            outline='dark green', width=3)
    canvas.create_rectangle(app.margin, app.margin,
                            app.width-app.margin, app.height-app.margin,
                            outline='dark green',width=10)

    drawTitle(app, canvas)
    drawClickHere(app, canvas)

# ############################################ #
# --------------- START MODE ----------------- #
# ############################################ #

def drawOpenButton(app, canvas):
    cx, cy = app.width/2, app.height/2
    canvas.create_rectangle(cx - 100, cy - 30, cx + 100, cy + 30, fill='LightSteelBlue1',
                  activefill='LightSteelBlue3')
    canvas.create_text(cx, cy, text='Pick image', font='Courier 20 bold')

def openButtonClicked(app, x, y):
    cx, cy = app.width/2, app.height/2
    return cx - 100 <= x <= cx + 100 and cy - 30 <= y <= cy + 30

def startMode_mousePressed(app, event):
    if openButtonClicked(app, event.x, event.y):
        userInput = app.getUserInput('Open Image:')
        try:
            im = EditedImage(app.loadImage(userInput), (app.cxCanvas, app.cyCanvas))
            im.filename = userInput
            app.layers.addLayer(SingleLayer(im))
            app.mode = 'userMode'
        except:
            pass

def startMode_redrawAll(app, canvas):
    canvas.create_rectangle(0,0,app.width,app.height,fill='DarkOliveGreen4')
    canvas.create_rectangle(30, 30, app.width-30, app.height-30,
                            outline='dark green', width=3)
    canvas.create_rectangle(app.margin, app.margin,
                            app.width-app.margin, app.height-app.margin,
                            outline='dark green',width=10)
    canvas.create_rectangle(app.width/2 - 110, app.height/2 - 40,
                            app.width/2 + 110, app.height/2 + 40,
                            fill=None, outline='dark green', width = 7)
    drawOpenButton(app, canvas)

# ########################################### #
# --------------- USER MODE ----------------- #
# ########################################### #

# initializing app variables:
def appStarted(app):
    app.margin = 10
    app.mode = 'splashScreenMode'
    app.cxCanvas = app.width/3
    app.cyCanvas = 5*app.height/14
    app.startingImage = Image.new(mode='RGB',size=(1,1),color=(255,255,255))
    app.im = EditedImage(app.startingImage, (app.cxCanvas, app.cyCanvas))
    app.layer1 = SingleLayer(app.im)
    app.imWidth = app.im.width
    app.imHeight = app.im.height

    # LAYERS
    app.layers = Layers([])
    if len(app.layers.layers) > 0:
        app.currentImage = app.layers.layers[len(app.layers.layers)-1].im
    app.changingLayer = False

    # STATES
    app.moving = False
    app.scaling = False
    app.isBW = False
    app.filling = False
    app.blurring = False
    app.sharpening = False
    app.picking = False

    app.pixelPicked = None

    # COLOR STATES
    app.increasingRed = False
    app.decreasingRed = False

    app.increasingGreen = False
    app.decreasingGreen = False

    app.increasingBlue = False
    app.decreasingBlue = False

    # SAVING
    app.finalImage = Image.new(mode='RGB',size=(int(2*app.width/3),int(5*app.height/7)),color=(255,255,255))

    # FLOODFILL
    app.fillcolor = (0,0,0)
    app.similarityVal = 40

    # USER MODE BUTTONS
    app.flipH = Button('flip horizontal', (5*app.width/6, app.height/8), 'dodgerblue', 'dodgerblue')
    app.flipV = Button('flip vertical', (5*app.width/6, 1.25*app.height/8), 'dodgerblue', 'dodgerblue')
    app.rotate = Button('rotate', (5*app.width/6, 1.5*app.height/8), 'dodgerblue', 'dodgerblue')
    app.save = Button('save', (18*app.width/19, app.margin + 20),'dodgerblue', 'dodgerblue')
    app.move = Button('move', (5*app.width/6, 0.5*app.height/8), 'dodgerblue', 'dodgerblue')
    app.bw = Button('b+w', (5*app.width/6,1.75*app.height/8), 'dodgerblue', 'dodgerblue')
    app.undo = Button('undo', (2*app.width/3 + 60, app.margin + 20), 'dodgerblue', 'dodgerblue')
    app.fill = Button('fill', (5*app.width/6, 22*app.height/36), 'dodgerblue', 'dodgerblue')
    app.sim = Button('similarity', (5*app.width/6, 26*app.height/36), 'slategray1', 'slategray1')
    app.colorPicker = Button('color picker', (5*app.width/6, 28*app.height/36), 'dodgerblue', 'dodgerblue')
    app.buttons = [app.flipH, app.flipV, app.rotate, app.move, app.bw, app.undo, app.save, app.fill, app.colorPicker]

    # USER MODE SLIDERS
    app.blur = Slider('blur', (5*app.width/6, 13*app.height/36), 'black')
    app.sharp = Slider('sharpen', (5*app.width/6, 12*app.height/36), 'black')
    app.red = Slider('red', (5*app.width/6, 17*app.height/36), 'red')
    app.green = Slider('green', (5*app.width/6, 18*app.height/36), 'green')
    app.blue = Slider('blue', (5*app.width/6, 19*app.height/36), 'blue')
    app.sliders = [app.blur, app.sharp, app.red, app.green, app.blue]

    # POSITIONS
    app.startRed = app.red.left
    app.endRed = app.red.getPos()

    app.startGreen = app.green.left
    app.endGreen = app.green.getPos()

    app.startBlue = app.blue.left
    app.endBlue = app.blue.getPos()

    app.startBlur = app.blur.left
    app.endBlur = app.blur.getPos()

    app.startSharp = app.sharp.left
    app.endSharp = app.sharp.getPos()

def userMode_keyPressed(app, event):
    if event.key == 'u':
        if len(app.currentImage.edits) > 0 and len(app.currentImage.centers) > 0:
            last = len(app.currentImage.edits) - 1
            lastc = len(app.currentImage.centers) - 1
            app.currentImage.image = app.currentImage.edits.pop(last)
            app.currentImage.center = app.currentImage.centers.pop(lastc)
            app.currentImage.cx = app.currentImage.center[0]
            app.currentImage.cy = app.currentImage.center[1]
            app.currentImage.width = app.currentImage.image.size[0]
            app.currentImage.height = app.currentImage.image.size[1]
            app.currentImage.left = app.currentImage.cx - app.currentImage.width/2
            app.currentImage.top = app.currentImage.cy - app.currentImage.height/2
            app.currentImage.right = app.currentImage.cx + app.currentImage.width/2
            app.currentImage.bottom = app.currentImage.cy + app.currentImage.height/2

def userMode_timerFired(app):
    if not app.changingLayer and len(app.layers.layers) > 0:
        app.currentImage = app.layers.layers[-1].im

def userMode_mousePressed(app, event):
    for button in app.buttons:
        if button.clicked(event.x, event.y):
            button.color = 'gray'

    # save the image with all the layers currently shown if the save button clicked
    if app.save.clicked(event.x,event.y):
        for layer in app.layers.layers:
            im = layer.im
            if not layer.isHidden():
                app.finalImage.paste(im.image, (int(im.cx-im.width/2), int(im.cy-im.height/2)))
        app.finalImage.save('final image.jpeg',format='jpeg')

    # flip the image horizontally if clicked
    if app.flipH.clicked(event.x,event.y):
        app.currentImage.flipH()

    # flip the image vertically if clicked
    if app.flipV.clicked(event.x,event.y):
        app.currentImage.flipV()

    # toggle the moving state and let the image be moved if the move button is clicked
    if app.move.clicked(event.x, event.y):
        app.moving = not app.moving
        if app.moving:
            app.move.name = 'moving...'
        else:
            app.move.name = 'move'

    # make the image black and white if the black and white button is clicked
    if app.bw.clicked(event.x, event.y):
        app.currentImage.makeBW()

    # rotate the image 90 degrees ccw if rotate is clicked
    if app.rotate.clicked(event.x, event.y):
        app.currentImage.rotate()

    # undo the last edit made if the undo button is clicked
    if app.undo.clicked(event.x, event.y) and len(app.currentImage.edits) > 0 and len(app.currentImage.centers) > 0:
        last = len(app.currentImage.edits) - 1
        lastc = len(app.currentImage.centers) - 1
        app.currentImage.image = app.currentImage.edits.pop(last)
        app.currentImage.center = app.currentImage.centers.pop(lastc)
        app.currentImage.cx = app.currentImage.center[0]
        app.currentImage.cy = app.currentImage.center[1]
        app.currentImage.width = app.currentImage.image.size[0]
        app.currentImage.height = app.currentImage.image.size[1]
        app.currentImage.left = app.currentImage.cx - app.currentImage.width/2
        app.currentImage.top = app.currentImage.cy - app.currentImage.height/2
        app.currentImage.right = app.currentImage.cx + app.currentImage.width/2
        app.currentImage.bottom = app.currentImage.cy + app.currentImage.height/2

    # fill the image if the filling state is true
    if app.filling:
        if (app.currentImage.right >= event.x >= app.currentImage.left and \
            app.currentImage.top <= event.y <= app.currentImage.bottom):
            app.currentImage.fill(app.fillcolor, event.x, event.y, app.similarityVal)

    if app.picking and app.currentImage.clicked(event.x,event.y):
        app.pixelPicked = app.currentImage.getPixelAtCoord(event.x,event.y)

    # add a layer if the add layer button is clicked
    if addLayerClicked(app, event.x, event.y):
        userInput = app.getUserInput('Add a layer:')
        try:
            im = app.loadImage(userInput)
            eIm = EditedImage(im, (app.cxCanvas, app.cyCanvas))
            app.layers.addLayer(SingleLayer(eIm))
            eIm.filename = userInput
            app.currentImage = app.layers.layers[-1].im
            for slider in app.sliders:
                slider.reset()
        except:
            pass

    # switch to layer clicked if a certain layer is clicked
    if layerClicked(app, event.x, event.y):
        idx = layerClicked(app, event.x, event.y)
        app.changingLayer = True
        app.currentImage = app.layers.layers[idx-1].im
        for slider in app.sliders:
            slider.reset()

    # toggle layer hiding if hide/show button clicked
    if hideClicked(app, event.x, event.y):
        idx = hideClicked(app, event.x, event.y)
        layer = app.layers.layers[idx-1]
        if layer.isHidden():
            layer.show()
        else:
            layer.hide()

def userMode_mouseReleased(app, event):
    for button in app.buttons:
        if button.clicked(event.x, event.y):
            button.color = button.activecolor

    # fills the area of the image clicked if fill is clicked
    if app.fill.clicked(event.x, event.y):
        app.filling = not app.filling
        if not app.filling:
            app.fill.name = 'fill'
        if app.filling:
            color = app.getUserInput('input rgb value in the form (r,g,b):')
            try:
                app.fill.name = 'filling...'
                if color[0] == '(':
                    color = color[1:]
                if color[-1] == ')':
                    color = color[:-1]
                rgbVals = color.split(',')
                r,g,b = int(rgbVals[0]),int(rgbVals[1]),int(rgbVals[2])
                while not ((0 <= r <= 255) and (0 <= g <= 255) and (0 <= b <= 255)):
                    color = app.getUserInput('input rgb value in the form (r,g,b):')
                    color = color[1:-1]
                    rgbVals = color.split(',')
                    r,g,b = int(rgbVals[0]),int(rgbVals[1]),int(rgbVals[2])
                app.fillcolor = (r,g,b)
            except:
                pass

    # allows user to input a similarity value to check how similar the pixels next to each other are
    # (i.e. a higher similarity value will allow the user to fill more of the area clicked)
    if app.filling and app.sim.clicked(event.x, event.y):
        val = app.getUserInput('enter value between 10 and 70:')
        try:
            val = int(val)
            while not (10 <= val <= 70):
                val = app.getUserInput('enter value between 10 and 70:')
                val = int(val)
            app.similarityVal = val
        except:
            pass

    # increases the red value if red slider moved right
    if app.increasingRed:
        dx = app.endRed - app.startRed
        amount = app.red.getColorAmount(dx)
        app.currentImage.increaseRed(int(amount))
        app.startRed = app.endRed
        app.increasingRed = False

    # decreases the red value if red slider moved left
    if app.decreasingRed:
        dx = app.endRed - app.startRed
        amount = abs(app.red.getColorAmount(dx))
        app.currentImage.decreaseRed(int(amount))
        app.startRed = app.endRed # update new start pos
        app.decreasingRed = False

    # increases the green value if green slider moved right
    if app.increasingGreen:
        dx = app.endGreen - app.startGreen
        amount = app.green.getColorAmount(dx)
        app.currentImage.increaseGreen(int(amount))
        app.startGreen = app.endGreen # update new start pos
        app.increasingGreen = False

    # decreases the green value if green slider moved left
    if app.decreasingGreen:
        dx = app.endGreen - app.startGreen
        amount = abs(app.green.getColorAmount(dx))
        app.currentImage.decreaseGreen(int(amount))
        app.startGreen = app.endGreen # update new start pos
        app.decreasingGreen = False

    # increases the blue value if blue slider moved right
    if app.increasingBlue:
        dx = app.endBlue - app.startBlue
        amount = app.blue.getColorAmount(dx)
        app.currentImage.increaseBlue(int(amount))
        app.startBlue = app.endBlue # update new start pos
        app.increasingBlue = False

    # decreases the blue value if blue slider moved left
    if app.decreasingBlue:
        dx = app.endBlue - app.startBlue
        amount = abs(app.blue.getColorAmount(dx))
        app.currentImage.decreaseBlue(int(amount))
        app.startBlue = app.endBlue # update new start pos
        app.decreasingBlue = False

    # blurs the image if the blur slider is moved to the right
    if app.blurring:
        dx = app.endBlur - app.startBlur
        amount = dx/5
        app.currentImage.gaussianBlur(amount)
        app.startBlur = app.endBlur
        app.blurring = False

    # sharpens the image if the sharpen slider is moved to the right
    if app.sharpening:
        dx = app.endSharp - app.startSharp
        amount = dx/10
        app.currentImage.sharpen(amount)
        app.startSharp = app.endSharp
        app.sharpening = False

    if app.colorPicker.clicked(event.x, event.y):
        app.picking = not app.picking
        if app.picking:
            app.colorPicker.name = 'picking...'
        else:
            app.colorPicker.name = 'color picker'

def userMode_mouseDragged(app, event):
    # move slider if clicked and dragged
    for slider in app.sliders:
        if slider.clicked(event.x, event.y) and slider.left < event.x < slider.right:
            slider.pos = event.x

    # recenter image if moved outside canvas bounds
    if app.moving and app.currentImage.clicked(event.x, event.y):
        if app.margin < app.currentImage.cx - app.currentImage.width/2 and \
           app.margin < app.currentImage.cy - app.currentImage.height/2 and \
           app.currentImage.cx + app.currentImage.width/2 < 2*app.width/3 and \
           app.currentImage.cy + app.currentImage.height/2 < 5*app.height/7:
           app.currentImage.changeCenter(event.x, event.y)
        else: # change center to canvas center if moved outside canvas bounds
            app.currentImage.cx = app.width/3
            app.currentImage.cy = 2.5*app.height/7

    # blur image if slider moved
    if app.blur.clicked(event.x, event.y):
        app.endBlur = event.x
        if app.endBlur > app.startBlur:
            app.blurring = True

    # sharpen image if slider moved
    if app.sharp.clicked(event.x, event.y):
        app.endSharp = event.x
        if app.endSharp > app.startSharp:
            app.sharpening = True

    # SLIDERS FOR COLORS
    if app.red.clicked(event.x,event.y):
        app.endRed = event.x
        if app.endRed > app.startRed: # moving right
            app.increasingRed = True
        else:
            app.decreasingRed = True

    if app.green.clicked(event.x,event.y):
        app.endGreen = event.x
        if app.endGreen > app.startGreen: # moving right
            app.increasingGreen = True
        else:
            app.decreasingGreen = True

    if app.blue.clicked(event.x,event.y):
        app.endBlue = event.x
        if app.endBlue > app.startBlue: # moving right
            app.increasingBlue = True
        else:
            app.decreasingBlue = True

    if app.picking and app.currentImage.clicked(event.x,event.y):
        app.pixelPicked = app.currentImage.getPixelAtCoord(event.x,event.y)

# draws the background
def drawBackground(app, canvas):
    canvas.create_rectangle(0,0,app.width,app.height,fill='black')

# draws the canvas area
def drawCanvas(app, canvas):
    canvas.create_rectangle(app.margin,app.margin,
                            2*app.width/3-app.margin,
                            app.height-app.margin,fill='bisque2')

# draws a button given a button object
def drawButton(canvas, button):
    name = button.getName()
    (x,y) = button.getCenter()
    bWidth, bHeight = button.w/2, button.h/2
    buttonLeft = x - bWidth
    buttonRight = x + bWidth
    color = button.getColor()
    r = bHeight
    cx1, cy1 = x - bWidth + r, y - bHeight + r
    cx2, cy2 = x - bWidth + r, y + bHeight - r
    cx3, cy3 = x + bWidth - r, y - bHeight + r
    cx4, cy4 = x + bWidth - r, y + bHeight - r
    canvas.create_arc(cx1 - r, cy1 - r,
                      cx1 + r, cy1 + r,
                      outline = color, width = 1, style="arc",
                      start = 90, extent = 90)
    canvas.create_arc(cx2 - r, cy2 - r,
                      cx2 + r, cy2 + r,
                      outline = color, width = 1, style="arc",
                      start = -180, extent = 90)
    canvas.create_arc(cx3 - r, cy3 - r,
                      cx3 + r, cy3 + r,
                      outline = color, width = 1, style="arc",
                      start = 90, extent = -90)
    canvas.create_arc(cx4 - r, cy4 - r,
                      cx4 + r, cy4 + r,
                      outline = color, width = 1, style="arc",
                      start = 0, extent = -90)
    canvas.create_line(x - r, y - r, x + r, y - r, fill = color)
    canvas.create_line(x - r, y + r, x + r, y + r, fill = color)
    canvas.create_rectangle(buttonLeft + r, y - r, buttonRight - r, y + r,
                            fill=color, outline=color)
    canvas.create_oval(cx1 - r, cy1 - r, cx1 + r, cy1 + r,
                       fill=color, outline=color)
    canvas.create_oval(cx3 - r, cy3 - r, cx3 + r, cy3 + r,
                       fill=color, outline=color)
    canvas.create_text(x, y, text=name, font='Copperplate 11 bold', fill='black')

# draws a slider given slider object
def drawSlider(canvas, slider):
    (x,y) = slider.getCenter()
    sWidth = slider.w
    pos = slider.getPos()
    r = slider.r
    color = slider.getColor()
    name = slider.getName()
    namelen = len(name)
    canvas.create_text(x - sWidth - namelen*7, y, text=name, font='Courier 15')
    canvas.create_line(x - sWidth, y,
                       x + sWidth, y,
                       fill = 'black', width = 3)
    canvas.create_oval(pos - r, y - r,
                       pos + r, y + r, fill = color)

# given r,g,b values, returns hex value (from https://www.cs.cmu.edu/~112/notes/notes-graphics.html#customColors)
def rgbString(r,g,b):
    return f'#{r:02x}{g:02x}{b:02x}'

# draws current fill color
def drawFillColor(app, canvas):
    (r,g,b) = app.fillcolor
    color = rgbString(r,g,b)
    pos = app.fill.getCenter()[1]
    canvas.create_rectangle(5*app.width/6 - 20, pos + 30,
                            5*app.width/6 + 20, pos + 70,
                            fill=color, outline='black')

# draws area with all the layers
def drawLayerArea(app, canvas):
    xleft = app.margin
    color = 'NavajoWhite3'
    r = 20
    canvas.create_rectangle(0,5*app.height/7-5,2*app.width/3,app.height,
                            fill='black')
    canvas.create_oval(xleft, 5*app.height/7, xleft + 2*r, 5*app.height/7 + 2*r,
                       fill=color, outline=color)
    canvas.create_oval(2*app.width/3-app.margin-2*r, 5*app.height/7,
                       2*app.width/3-app.margin, 5*app.height/7 + 2*r,
                       fill=color, outline=color)
    canvas.create_oval(xleft, app.height - app.margin - 2*r, xleft + 2*r, app.height - app.margin,
                       fill=color, outline=color)
    canvas.create_oval(2*app.width/3-app.margin-2*r, app.height-app.margin-2*r,
                       2*app.width/3-app.margin, app.height-app.margin,
                       fill=color, outline=color)
    canvas.create_rectangle(app.margin+r, 5*app.height/7,
                            2*app.width/3-app.margin-r, app.height-app.margin,
                            fill = color, outline=color)
    canvas.create_rectangle(app.margin, 5*app.height/7+r,
                            2*app.width/3-app.margin, app.height-app.margin-r,
                            fill = color,outline=color)
    canvas.create_text(app.width/3-app.margin,5*app.height/7+20,
                       text='Layers:', font = 'Helvetica 20 bold italic')

# draws the add layer button
def drawAddLayer(app, canvas):
    boxleftx = app.width/11 - app.margin
    boxlefty = 11*app.height/14
    size = 50
    cxBox = boxleftx + size/2
    cyBox = boxlefty + size/2
    canvas.create_rectangle(boxleftx, boxlefty,
                            boxleftx + size, boxlefty + size,fill='lightgray',
                            outline='black', width=3, activefill='gray')
    canvas.create_text(cxBox, cyBox, text='Add', font = 'Helvetica 20 bold')

# draws button for each layer in the layer area
def drawLayers(app, canvas):
    numLayers = len(app.layers.layers)
    xleftLayer1 = app.width/11 - app.margin + 70
    yLayer = 11*app.height/14
    for n in range(numLayers):
        x0 = 10*n + xleftLayer1 + n*50
        y0 = yLayer
        x1 = 10*n + xleftLayer1 + (n+1)*50
        y1 = yLayer + 50
        canvas.create_rectangle(x0, y0, x1, y1, fill='lightgray', outline='black', activefill='gray',width=3)
        canvas.create_text((x0+x1)/2, (y0+y1)/2, text=f'Layer {n+1}', font = 'Helvetica 10 bold')
        # draws hide portion of button
        hideText = ''
        if app.layers.layers[n].isHidden():
            hideText = 'show'
        else:
            hideText = 'hide'
        hidey0 = y0 + 60
        hidey1 = y1 + 30
        canvas.create_rectangle(x0, hidey0, x1, hidey1, fill='azure', outline='black', activefill='gray',width=2)
        canvas.create_text((x0+x1)/2, (hidey0+hidey1)/2, text=hideText, font = 'Helvetica 10 bold')

# given a layer index, returns the bounds of the layer square
def getLayerBounds(app, idx):
    xleftLayer1 = app.width/11 - app.margin + 70
    yLayer = 11*app.height/14
    x0 = 10*idx + xleftLayer1 + idx*50
    y0 = yLayer
    x1 = 10*idx + xleftLayer1 + (idx+1)*50
    y1 = yLayer + 50
    return x0, y0, x1, y1

# returns which layer button was clicked, or False if none were clicked
def layerClicked(app, x, y):
    numLayers = len(app.layers.layers)
    for n in range(numLayers):
        x0, y0, x1, y1 = getLayerBounds(app, n)
        if x0 <= x <= x1 and y0 <= y <= y1:
            return n+1
    return False

# returns which layer was hidden if the hide button was clicked, or False if none were clicked
def hideClicked(app, x, y):
    numLayers = len(app.layers.layers)
    for n in range(numLayers):
        x0, y0, x1, y1 = getLayerBounds(app, n)
        y0 += 60
        y1 += 30
        if x0 <= x <= x1 and y0 <= y <= y1:
            return n+1
    return False

# returns true if the add layer button is clicked
def addLayerClicked(app, x, y):
    boxleftx = app.width/11 - app.margin
    boxlefty = 11*app.height/14
    size = 50
    return boxleftx <= x <= boxleftx + size and boxlefty <= y <= boxlefty + size

# draws the value of the pixel picked
def drawPixelPicked(app, canvas):
    pixel = str(app.pixelPicked)
    canvas.create_text(5*app.width/6, 29.5*app.height/36, text=pixel, font = 'Helvetica 20 bold')

# draws a box of the color that is picked on the canvas
def drawColorPicked(app, canvas):
    r = app.pixelPicked[0]
    g = app.pixelPicked[1]
    b = app.pixelPicked[2]
    color = rgbString(r,g,b)
    canvas.create_rectangle(5*app.width/6 - 20, 31.5*app.height/36 - 20,
                            5*app.width/6 + 20, 31.5*app.height/36 + 20,
                            fill=color)

# draws area with all tools shown
def drawToolArea(app, canvas):
    xleft = 2*app.width/3
    r = 20
    color = 'NavajoWhite3'
    canvas.create_oval(xleft, app.margin, xleft + 2*r, app.margin + 2*r,
                       fill=color, outline=color)
    canvas.create_oval(app.width-app.margin-2*r, app.margin, app.width - app.margin, app.margin + 2*r,
                       fill=color, outline=color)
    canvas.create_oval(xleft, app.height - app.margin - 2*r, xleft + 2*r, app.height - app.margin,
                       fill=color, outline=color)
    canvas.create_oval(app.width-app.margin-2*r, app.height-app.margin-2*r, app.width-app.margin, app.height-app.margin,
                       fill=color, outline=color)
    canvas.create_rectangle(xleft, app.margin + r, app.width-app.margin, app.height-app.margin-r,
                            fill=color, outline=color)
    canvas.create_rectangle(xleft + r, app.margin, app.width-app.margin-r, app.height-app.margin,
                            fill=color, outline=color)

def userMode_redrawAll(app, canvas):
    drawBackground(app, canvas)
    drawCanvas(app,canvas)
    drawLayerArea(app, canvas)
    drawAddLayer(app, canvas)
    drawLayers(app, canvas)
    drawToolArea(app, canvas)
    drawFillColor(app, canvas)
    for button in app.buttons:
        drawButton(canvas, button)
    if app.filling:
        drawButton(canvas, app.sim)
    for slider in app.sliders:
        drawSlider(canvas, slider)
    for layer in app.layers.layers:
        im = layer.im
        hidden = layer.isHidden()
        if not hidden:
            im.drawImage(canvas)
    if app.pixelPicked != None:
        drawPixelPicked(app, canvas)
        drawColorPicked(app, canvas)

width = 1200
height = 800

runApp(width=width,height=height,title='Editor')


import requests
import pandas as pd
import numpy as np
import os

local = '/home/innereye/alarms/'
if os.path.isdir(local):
    os.chdir(local)
    with open('/home/innereye/alarms/oath.txt') as f:
        oath = f.readlines()[0][:-1]
else:
    oath = os.environ['OAuth']
# get_coordinates(city_name)

def get_coordinates(city_name):
    city_name = city_name + ', ישראל'
    geocoder_url = f'https://maps.googleapis.com/maps/api/geocode/json?address={city_name}&key={oath}&language=iw'
    geocoding_result = requests.get(geocoder_url).json()
    if geocoding_result['results'] == []:
        lat = 0
        long = 0
    else:
        lat = geocoding_result['results'][0]['geometry']['location']['lat']
        long = geocoding_result['results'][0]['geometry']['location']['lng']
    return (lat, long)

missing = ['רחובות','חולון']
lt = []
lg = []
for miss in missing:
    lat, long = get_coordinates(miss)
    lt.append(lat)
    lg.append(long)
df = pd.DataFrame(missing, columns=['loc'])
df['lat'] = lt
df['long'] = lg
df.to_csv('test.csv', sep=',', index=False)


import sys
input = sys.stdin.readline

# n : 학생 수
# k : 졸고 있는 학생 수
# q : 지환이가 출석 코드를 보낼 학생 수
# m : 주어질 구간의 수
n, k, q, m = map(int, input().split())
k_list = list(map(int, input().split())) # k명 입장 번호
q_list = list(map(int, input().split())) # q명 입장 번호
dp = [1] * (n + 3) # dq 리스트 1로 초기화
dp_sum = [0] * (n + 3)

while q_list:
    start = q_list.pop(0)

    if start not in k_list: # 출석하면 0으로 바꿔줌 -> 특정 구간의 범위에서 1의 개수를 구해주면 된다
        for num in range(1, ((n + 2) // start) + 1):
            if start * num not in k_list:
                dp[start * num] = 0

for num in range(3, n + 3): # 누적합 이용해서 1의 개수를 따로 저장
    if num == 3:
        dp_sum[num] = dp[num]
    else:
        dp_sum[num] = dp[num] + dp_sum[num-1]

for _ in range(m):
    i, j = map(int, input().split()) # 구간의 범위 s, e
    print(dp_sum[j] - dp_sum[i-1])
    # 부분합 이용 : S(s~e) = S(s) - S(1~(e-1))


from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtWidgets import QMessageBox, QDialog

from gui.code_edit import Ui_CodeEditDialog
from src.gui.file_transfer_dialog import FileTransferDialog
from src.utility.settings import Settings

class CodeEditDialog(QDialog, Ui_CodeEditDialog):
    mcu_file_saved = pyqtSignal()

    def __init__(self, parent, connection):
        super(CodeEditDialog, self).__init__(None, Qt.WindowCloseButtonHint)
        self.setupUi(self)

        geometry = Settings().retrieve_geometry("editor")
        if geometry:
            self.restoreGeometry(geometry)

        self._connection = connection

        self.saveLocalButton.clicked.connect(self._save_local)
        self.saveMcuButton.clicked.connect(self._save_to_mcu)
        #self.runButton.clicked.connect(self._run_file)
        self.runButton.hide()

        fixed_font = QFontDatabase.systemFont(QFontDatabase.FixedFont)
        self.codeEdit.setFont(fixed_font)

        if connection and connection.is_connected():
            self.connected(connection)
        else:
            self.disconnected()

    def closeEvent(self, event):
        Settings().update_geometry("editor", self.saveGeometry())
        super(CodeEditDialog, self).closeEvent(event)

    def disconnected(self):
        self._connection = None
        self.saveMcuButton.setEnabled(False)

    def connected(self, connection):
        self._connection = connection
        self.saveMcuButton.setEnabled(True)

    def _save_local(self):
        path = self.localPathEdit.text()
        if not path:
            QMessageBox.warning(self, "Invalid path", "Enter correct path for local file.")
            return

        try:
            with open(path, "w") as file:
                file.write(self.codeEdit.toPlainText())
        except IOError:
            QMessageBox.critical(self, "Save operation failed", "Couldn't save the file. Check path and permissions.")

    def _save_to_mcu(self):
        name = self.remotePathEdit.text()
        if not name:
            QMessageBox.warning(self, "Invalid name", "Enter correct name for remote file.")
            return

        content = self.codeEdit.toPlainText()
        if not content:
            QMessageBox.warning(self, "Empty file", "Can't write empty file.")
            return

        progress_dlg = FileTransferDialog(FileTransferDialog.UPLOAD)
        progress_dlg.finished.connect(self.mcu_file_saved.emit)
        progress_dlg.show()
        self._connection.write_file(name, content, progress_dlg.transfer)

    def set_code(self, local_path, remote_path, code):
        self.codeEdit.clear()
        self.codeEdit.setText(code)
        self.localPathEdit.setText(local_path if local_path else "")
        self.remotePathEdit.setText(remote_path if remote_path else "")


# my task solution
def sum_of_intervals(intervals):
    if not intervals:
        return 0

    def merge_intervals(intervals):
        intervals.sort()
        res = []
        start, end = intervals[0][0], intervals[0][1]

        for i, interval in enumerate(intervals[1:]):
            s, e = interval[0], interval[1]
            if s > end:
                res.append([start, end])
                start, end = s, e
            else:
                end = max(e, end)
        res.append([start, end])
        return res

    intervals = merge_intervals(intervals)
    res = 0
    for s, e in intervals:
        res += e - s

    return res

print(sum_of_intervals([(1, 5)]))  #--> 4
print(sum_of_intervals([(1, 4), (7, 10), (3, 5)]))  #--> 7
print(sum_of_intervals([(1, 2), (6, 10), (11, 15)]))  #--> 9

# codewars task best solution
def sum_of_intervals(intervals):
    s, top = 0, float("-inf")
    for a, b in sorted(intervals):
        if top < a: top = a
        if top < b: s, top = s + b - top, b
    return s

# codewars task best solution
def sum_of_intervals(intervals):
    intervals.sort()
    lim, res = intervals[0][0], 0
    for i in range(len(intervals)):
        res += max(intervals[i][1], lim) - max(intervals[i][0], lim)
        lim = max(lim, intervals[i][1])
    return res

# codewars task best solution
def sum_of_intervals(intervals):
    intervals.sort()
    end, subtract = intervals[0][1], 0
    for i in intervals:
        if i[0] > end: subtract += i[0] - end
        if i[1] > end: end = i[1]
    return end - subtract - intervals[0][0]

# codewars task best solution
def sum_of_intervals(intervals):
    intervals.sort()
    x = 0
    while x < len(intervals) - 1:
        if intervals[x][0] <= intervals[
                x + 1][0] and intervals[x][1] >= intervals[x + 1][1]:
            intervals.pop(x + 1)
        elif intervals[x][0] <= intervals[
                x + 1][0] and intervals[x][1] >= intervals[
                    x + 1][0] and intervals[x][1] < intervals[x + 1][1]:
            intervals[x] = (intervals[x][0], intervals[x + 1][1])
            intervals.pop(x + 1)
        else:
            x += 1

    return sum(intervals[x][1] - intervals[x][0]
               for x in range(len(intervals)))

Task 163 Sum of Intervals/Sum of Intervals.py


# 직사각형 별찍기
# https://programmers.co.kr/learn/courses/30/lessons/12969?language=python3
# n과 m은 각각 1000 이하인 자연수입니다.
n, m = map(int, input().strip().split(' '))

for i in range(m):
    for e in range(n):
        print("*",end='')

    print()

#줄마다 몇개의 *을 출력!
#이중 for문??


"""
Given a string s, partition s such that every substring of the partition is a palindrome.

Return all possible palindrome partitioning of s.

Example:

Input: "aab"
Output:
[
  ["aa","b"],
  ["a","a","b"]
]
"""
class Solution:
    def partition(self, s):
        """
        :type s: str
        :rtype: List[List[str]]
        """
        # dp = [[0]*len(s) for _ in range(len(s))]
        memo,dp = [],[]

        for i in range(len(s)):
            memo.append([])
            dp.append([])
            for j in range(len(s)):
                memo[-1].append([])
                dp[-1].append(0)

        d = {}
        for i in range(len(s)-1,-1,-1):
            for j in range(len(s)-1,i-1,-1):
                    dp[i][j] = (j-i)+1 if s[i] ==  s[j] and (j-i<=1 or dp[i+1][j-1] > 0) else 0
                    if dp[i][j] > 0:
                        if i not in d:
                            d[i] = []
                        d[i].append((i,j))

        def get_result(start,end):
            if start>end:
                return []
            if not memo[start][end]:
                ret = []
                for pair in d[start]:
                    if pair[1]+1 <= end:
                        ret.extend([[s[pair[0]:pair[1]+1]]+i for i in get_result(pair[1]+1,end)])
                    else:
                        ret.append([s[pair[0]:pair[1]+1]])
                memo[start][end] = ret
            return memo[start][end]
        return get_result(0,len(s)-1)

if __name__ == '__main__':
    s = Solution()

    ss = 'aabacab'
    s.partition(ss)


import math

INPUT = 312051

def manhattan_to_node(mem_address):
    # using centered octagonal numbers
    plane = math.ceil(.5 * (1 + math.sqrt(mem_address)) - 1)
    prev_oct_num = int(math.pow(2 * plane - 1, 2))
    next_oct_num = int(math.pow(2 * (plane + 1) - 1, 2))
    octal_inc = (next_oct_num - prev_oct_num)//8
    # fixes the bug on "corner" cases
    if mem_address in [prev_oct_num + i * octal_inc for i in range(2, 8, 2)]:
        return int((mem_address - prev_oct_num) % octal_inc + plane + octal_inc)
    # I think this min works here. There was a problem about where we were counting from
    # and I think whatever number it's closest to is the right one. Only tried a few.
    return int(min((next_oct_num - mem_address), (mem_address - prev_oct_num)) % octal_inc + plane)

def run():
    return manhattan_to_node(INPUT)

node_transformers = [
    (-1, lambda n: (n[0], n[1] + 1)), # go up
    (0, lambda n: (n[0] - 1, n[1])),  # go left
    (0, lambda n: (n[0], n[1] - 1)),  # go down
    (1, lambda n: (n[0] + 1, n[1]))   # go right
]

def sum_adjacent_nodes(node, grid):
    result = 0
    for i in range(-1, 2):
        for j in range(-1, 2):
            if i == 0 and j == 0:
                continue
            adj_node = (node[0] + i, node[1] + j)
            try:
                result += grid[adj_node]
            except KeyError:
                continue  # treat errors as 0
    return result

def run_2():
    grid = {(0, 0): 1}
    plane = 1
    current_node = (1, 0)
    while True:
        for modifier, func in node_transformers:
            for _ in range(2 * plane + modifier):
                value = sum_adjacent_nodes(current_node, grid)
                grid[current_node] = value
                current_node = func(current_node)
                if value > INPUT:
                    return value
        plane += 1

def test():
    assert manhattan_to_node(2) == 1
    assert manhattan_to_node(3) == 2
    assert manhattan_to_node(13) == 4
    assert manhattan_to_node(15) == 2
    assert manhattan_to_node(23) == 2
    assert manhattan_to_node(1024) == 31
    assert manhattan_to_node(INPUT) == 430

if __name__ == '__main__':
    test()
    print(run())
    print(run_2())


#! /usr/bin/env python
# -*- coding: utf-8 -*-
# vim:fenc=utf-8
# Distributed under terms of the MIT license.

"""

"""
from pybatfish.client.commands import *
from pybatfish.question.question import load_questions, list_questions
from pybatfish.question import bfq
from pybatfish.datamodel.flow import HeaderConstraints
import os
import tempfile

def create_inline_snapshot(text, format="cisco"):
    """ Create a quick inline snapshot """

    HOSTNAME = "TEST_INLINE"
    ACL_TEXT = """
    !RANCID-CONTENT-TYPE: {format}
    hostname {hostname}
    {text}
    """.format(format=format, hostname=HOSTNAME, text=text)
    temp = tempfile.TemporaryDirectory(prefix='bf_acl.')
    snap_dir = os.path.join(temp.name, 'configs')
    snap_filename = os.path.join(snap_dir, HOSTNAME)
    os.makedirs(snap_dir)
    with open(snap_filename, 'w') as outfile:
        outfile.write(ACL_TEXT)

    bf_set_network('__inline_snap')
    return bf_init_snapshot(temp.name)

if __name__ == "__main__":
    acl_def = """
    no ip access-list extended TEST_ACL
    ip access-list extended TEST_ACL
    10 permit tcp any any
    20 permit udp any any
    30 deny tcp any any eq ssh
    """

    load_questions()
    snap_gen = create_inline_snapshot(acl_def)
    print("#"*100)
    print("TEST ACL LINE REACHABILITY (here seq 30 should be useless and thus 'unreachable')")
    reacha = bfq.filterLineReachability().answer(snap_gen)
    print(reacha.frame())
    print("#"*100)
    print("CHECK IF ACL REALLY BLOCKS THE PACKET SPECIFIED")
    test_behav = bfq.searchfilters(
        action="permit", headers=HeaderConstraints(
            applications=["ssh"], srcIps="1.2.3.4", dstIps="4.5.6.7"
            )
        ).answer()
    print(test_behav)
    print("#"*100)
    print("DEFINES ON WHICH ENTRY THE PACKET SPECIFIED IS MATCHED")
    test_acl = bfq.testfilters(
        headers=HeaderConstraints(
            applications=["ssh"], srcIps="1.2.3.4", dstIps="4.5.6.7"
            )
        ).answer()
    print(test_acl)


import stringcase
import giturlparse

from jinja2.ext import Extension

def as_hostname(value):
    # Replace underscores with dash - to allow using the value as a hostname
    return value.replace("_", "-")

def snake_to_pascal_case(value):
    return stringcase.pascalcase(value)

def as_git_path(value):
    if not value or not value.strip():
        return value

    res = value.strip()

    # value is already a git ssh url. Return it as is
    if value.startswith("git@"):
        return value

    res = res.replace("http://", "")
    res = res.replace("https://", "")

    # Strip .git suffix from a git http/https url
    if res.endswith(".git"):
        res = res[:-4]

    domain, rest = res.split("/", maxsplit=1)

    return f"git@{domain}:{rest}.git"

def get_url_path(value):
    """Takes a gitlab repository url and returns its path component

    Example:
    >>> get_url_path("https://gitlab.com/thorgate-public/django-project-template")
    >>> "thorgate-public/django-project-template"
    """
    if not value or not value.strip():
        return value

    res = value.strip()

    if res.startswith("git@") or res.endswith(".git"):
        try:
            if not res.startswith("git@") and not res.startswith("http"):
                # add https:// prefix to make giturlparse happy with the uris
                res = f"https://{res}"

            parts = giturlparse.parse(res)

            return "/".join(
                x
                for x in [
                    parts.owner,
                    parts.name,
                ]
                if x
            )

        except giturlparse.ParserError:
            pass

    res = res.replace("http://", "")
    res = res.replace("https://", "")

    domain, rest = res.split("/", maxsplit=1)

    return f"{rest}"

class TGFiltersModule(Extension):
    def __init__(self, environment):
        super().__init__(environment)

        environment.filters["as_hostname"] = as_hostname
        environment.filters["snake_to_pascal_case"] = snake_to_pascal_case
        environment.filters["as_git_path"] = as_git_path
        environment.filters["get_url_path"] = get_url_path

        environment.globals.update(
            SPA="spa",
            WEBAPP="webapp",
            ALPINE="alpine",
            DEBIAN="debian",
            YES="yes",
            NO="no",
            S3="S3",
            GCS="GCS",
        )