import sys
import os
import re
import datetime
import time
import json
import subprocess
import argparse
import configparser
import tempfile

import requests
import discogssmells

# grab the current year. Make sure to set the clock of your machine
# to the correct date or use NTP!
currentyear = datetime.datetime.utcnow().year

# grab the latest release from the API. Results tend to get cached
# by the Discogs nginx instance for some reason.
def get_latest_release(headers):
    latest = 'https://api.discogs.com/database/search?type=release&sort=date_added'
    r = requests.get(latest, headers=headers)
    if r.status_code != 200:
        return

    # now parse the response
    responsejson = r.json()
    if not 'results' in responsejson:
        return

    return responsejson['results'][0]['id']

# convenience method to check if roles are valid
def checkrole(artist, release_id, credits):
    invalidroles = []
    if not '[' in artist['role']:
        roles = map(lambda x: x.strip(), artist['role'].split(','))
        for role in roles:
            if role == '':
                continue
            if not role in credits:
                invalidroles.append(role)
    else:
        # sometimes there is an additional description in the role in
        # between [ and ]
        # This method is definitely not catching everything.
        rolesplit = artist['role'].split('[')
        for rs in rolesplit:
            if ']' in rs:
                rs_tmp = rs
                while ']' in rs_tmp:
                    rs_tmp = rs_tmp.split(']', 1)[1]
                roles = map(lambda x: x.strip(), rs_tmp.split(','))
                for role in roles:
                    if role == '':
                        continue
                    # ugly hack because sometimes the extra data between [ and ]
                    # appears halfway the words in a role, sigh.
                    if role == 'By':
                        continue
                    if not role in credits:
                        invalidroles.append(role)
    return invalidroles

# process the contents of a release
def processrelease(release, config_settings, count, credits, ibuddy, favourites):
    releaseurl = 'https://www.discogs.com/release/%s'

    # only process entries that have a status of 'Accepted'
    if release['status'] == 'Rejected':
        return count
    elif release['status'] == 'Draft':
        return count
    elif release['status'] == 'Deleted':
        return count

    errormsgs = []

    # store some data that is used by multiple checks
    founddeposito = False
    year = None
    release_id = release['id']

    # check for favourite artist, if defined
    for artist in release['artists']:
        if artist['name'] in favourites:
            if ibuddy != None:
                ibuddy.executecommand('HEART:WINGSHIGH:RED:GO:SHORTSLEEP:NOHEART:WINGSLOW:GO:SHORTSLEEP:HEART:LEFT::WINGSHIGH::GO:SHORTSLEEP:NOHEART:RIGHT:GO:HEART:GO:BLUE:SHORTSLEEP:WINGSLOW:GO:SHORTSLEEP:RESET')
                ibuddy.reset()
            if config_settings['use_notify_send']:
                count += 1
                errormsgs.append('%8d -- Favourite Artist (%s): https://www.discogs.com/release/%s' % (count, artist['name'], str(release_id)))

    # check for misspellings of Czechoslovak and Czech releases
    # People use 0x115 instead of 0x11B, which look very similar but 0x115
    # is not valid in the Czech alphabet. Check for all data except
    # the YouTube playlist.
    # https://www.discogs.com/group/thread/757556
    # This is important for the following elements:
    # * tracklist (title, subtracks not supported yet)
    # * artist and extraartists (including extraartists in tracklist)
    # * notes
    # * BaOI identifiers (both value and description)
    if config_settings['check_spelling_cs']:
        if 'country' in release:
            if release['country'] == 'Czechoslovakia' or release['country'] == 'Czech Republic':
                for t in release['tracklist']:
                    if chr(0x115) in t['title']:
                        count += 1
                        errormsgs.append('%8d -- Czech character (0x115, tracklist: %s): https://www.discogs.com/release/%s' % (count, t['position'], str(release_id)))
                    if 'extraartists' in t:
                        for artist in t['extraartists']:
                            if chr(0x115) in artist['name']:
                                count += 1
                                errormsgs.append('%8d -- Czech character (0x115, artist name at: %s): https://www.discogs.com/release/%s' % (count, t['position'], str(release_id)))
                if 'artists' in release:
                    for artist in release['artists']:
                        if chr(0x115) in artist['name']:
                            count += 1
                            errormsgs.append('%8d -- Czech character (0x115, artist name: %s): https://www.discogs.com/release/%s' % (count, artist['name'], str(release_id)))
                if 'extraartists' in release:
                    for artist in release['extraartists']:
                        if chr(0x115) in artist['name']:
                            count += 1
                            errormsgs.append('%8d -- Czech character (0x115, artist name: %s): https://www.discogs.com/release/%s' % (count, artist['name'], str(release_id)))
                for i in release['identifiers']:
                    if chr(0x115) in i['value']:
                        count += 1
                        errormsgs.append('%8d -- Czech character (0x115, BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
                    if 'description' in i:
                        if chr(0x115) in i['description']:
                            count += 1
                            errormsgs.append('%8d -- Czech character (0x115, BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
                if 'notes' in release:
                    if chr(0x115) in release['notes']:
                        count += 1
                        errormsgs.append('%8d -- Czech character (0x115, Notes): https://www.discogs.com/release/%s' % (count, str(release_id)))

    # check credit roles in three places:
    # 1. artists
    # 2. extraartists (release level)
    # 3. extraartists (track level)
    if 'check_credits' in config_settings:
        if config_settings['check_credits']:
            if 'artists' in release:
                for artist in release['artists']:
                    if 'role' in artist:
                        invalidroles = checkrole(artist, release_id, credits)
                        for role in invalidroles:
                            count += 1
                            errormsgs.append('%8d -- Role \'%s\' invalid: https://www.discogs.com/release/%s' % (count, role, str(release_id)))
            if 'extraartists' in release:
                for artist in release['extraartists']:
                    if 'role' in artist:
                        invalidroles = checkrole(artist, release_id, credits)
                        for role in invalidroles:
                            count += 1
                            errormsgs.append('%8d -- Role \'%s\' invalid: https://www.discogs.com/release/%s' % (count, role, str(release_id)))
            for t in release['tracklist']:
                if 'extraartists' in t:
                    for artist in t['extraartists']:
                        if 'role' in artist:
                            invalidroles = checkrole(artist, release_id, credits)
                            for role in invalidroles:
                                count += 1
                                errormsgs.append('%8d -- Role \'%s\' invalid: https://www.discogs.com/release/%s' % (count, role, str(release_id)))

    # check release month and year
    if 'released' in release:
        if config_settings['check_month']:
            if '-' in release['released']:
                monthres = re.search('-(\d+)-', release['released'])
                if monthres != None:
                    monthnr = int(monthres.groups()[0])
                    if monthnr == 0:
                        count += 1
                        errormsgs.append('%8d -- Month 00: https://www.discogs.com/release/%s' % (count, str(release_id)))
                    elif monthnr > 12:
                        count += 1
                        errormsgs.append('%8d -- Month impossible (%d): https://www.discogs.com/release/%s' % (count, monthnr, str(release_id)))
        try:
            year = int(release['released'].split('-', 1)[0])
            # TODO: check for implausible old years
        except ValueError:
            if config_settings['check_year']:
                count += 1
                errormsgs.append('%8d -- Year \'%s\' invalid: https://www.discogs.com/release/%s' % (count, release['released'], str(release_id)))

    # check the tracklist
    tracklistcorrect = True
    tracklistpositions = set()
    formattexts = set()
    if config_settings['check_tracklisting'] and len(release['formats']) == 1:
        formattext = release['formats'][0]['name']
        formattexts.add(formattext)
        formatqty = int(release['formats'][0]['qty'])
        for t in release['tracklist']:
            if tracklistcorrect:
                if formattext in ['Vinyl', 'Cassette', 'Shellac', '8-Track Cartridge']:
                    try:
                        int(t['position'])
                        count += 1
                        errormsgs.append('%8d -- Tracklisting (%s): https://www.discogs.com/release/%s' % (count, formattext, str(release_id)))
                        tracklistcorrect = False
                        break
                    except:
                        pass
                if formatqty == 1:
                    if t['position'].strip() != '' and t['position'].strip() != '-' and t['type_'] != 'heading' and t['position'] in tracklistpositions:
                        count += 1
                        errormsgs.append('%8d -- Tracklisting reuse (%s, %s): https://www.discogs.com/release/%s' % (count, formattext, t['position'], str(release_id)))
                    tracklistpositions.add(t['position'])

    # various checks for labels
    for l in release['labels']:
        # check for several identifiers being used as catalog numbers
        if 'catno' in l:
            if config_settings['check_label_code']:
                if l['catno'].lower().startswith('lc'):
                    falsepositive = False
                    # American releases on Epic (label 1005 in Discogs)
                    # sometimes start with LC
                    if l['id'] == 1005:
                        falsepositive = True
                    if not falsepositive:
                        if discogssmells.labelcodere.match(l['catno'].lower()) != None:
                            count += 1
                            errormsgs.append('%8d -- Possible Label Code (in Catalogue Number): https://www.discogs.com/release/%s' % (count, str(release_id)))
            if config_settings['check_deposito']:
                # now check for D.L.
                dlfound = False
                for d in discogssmells.depositores:
                    result = d.search(l['catno'])
                    if result != None:
                        for depositovalre in discogssmells.depositovalres:
                            if depositovalre.search(l['catno']) != None:
                                dlfound = True
                                break

                if dlfound:
                    count += 1
                    errormsgs.append('%8d -- Possible Depósito Legal (in Catalogue Number): https://www.discogs.com/release/%s' % (count, str(release_id)))
        if 'name' in l:
            if config_settings['check_label_name']:
                if l['name'] == 'London' and l['id'] == 26905:
                    count += 1
                    errormsgs.append('%8d -- Wrong label (London): https://www.discogs.com/release/%s' % (count, str(release_id)))
    '''
    if name == 'format':
        for (k,v) in attrs.items():
            if k == 'name':
                if v == 'CD':
                    self.iscd = True
                self.formattexts.add(v)
            elif k == 'qty':
                if self.formatmaxqty == 0:
                    self.formatmaxqty = max(self.formatmaxqty, int(v))
                else:
                    self.formatmaxqty += int(v)
    '''

    # various checks for the formats
    formattexts = set()
    for f in release['formats']:
        if 'descriptions' in f:
            if 'Styrene' in f['descriptions']:
                pass
        # store the names of the formats. This is useful later for SID code checks
        if 'name' in f:
            formattexts.add(f['name'])
        if 'text' in f:
            if f['text'] != '':
                if config_settings['check_spars_code']:
                    tmpspars = f['text'].lower().strip()
                    for s in ['.', ' ', '•', '·', '[', ']', '-', '|', '/']:
                        tmpspars = tmpspars.replace(s, '')
                    if tmpspars in discogssmells.validsparscodes:
                        count += 1
                        errormsgs.append('%8d -- Possible SPARS Code (in Format): https://www.discogs.com/release/%s' % (count, str(release_id)))
                if config_settings['check_label_code']:
                    if f['text'].lower().startswith('lc'):
                        if discogssmells.labelcodere.match(f['text'].lower()) != None:
                            count += 1
                            errormsgs.append('%8d -- Possible Label Code (in Format): https://www.discogs.com/release/%s' % (count, str(release_id)))

    # walk through the BaOI identifiers
    for identifier in release['identifiers']:
        v = identifier['value']
        if config_settings['check_creative_commons']:
            if 'creative commons' in v.lower():
                count += 1
                errormsgs.append('%8d -- Creative Commons reference: https://www.discogs.com/release/%s' % (count, str(release)))
            if 'description' in identifier:
                if 'creative commons' in identifier['description'].lower():
                    count += 1
                    errormsgs.append('%8d -- Creative Commons reference: https://www.discogs.com/release/%s' % (count, str(release)))
        if config_settings['check_spars_code']:
            if identifier['type'] == 'SPARS Code':
                if v.lower() != "none":
                    # Sony format codes
                    # https://www.discogs.com/forum/thread/339244
                    # https://www.discogs.com/forum/thread/358285
                    if v == 'CDC' or v == 'CDM':
                        count += 1
                        errormsgs.append('%8d -- Sony Format Code in SPARS: https://www.discogs.com/release/%s' % (count, str(release_id)))
                    else:
                        tmpspars = v.lower().strip()
                        for s in ['.', ' ', '•', '·', '[', ']', '-', '|', '/']:
                            tmpspars = tmpspars.replace(s, '')
                        if not tmpspars in discogssmells.validsparscodes:
                            count += 1
                            errormsgs.append('%8d -- SPARS Code (format): https://www.discogs.com/release/%s' % (count, str(release_id)))
            else:
                # first check the description free text field
                sparsfound = False
                if 'description' in identifier:
                    for spars in discogssmells.spars_ftf:
                        if spars in identifier['description'].lower():
                            sparsfound = True
                # then also check the value to see if there is a valid SPARS
                if v.lower() in discogssmells.validsparscodes:
                    sparsfound = True
                else:
                    if 'd' in v.lower():
                        tmpspars = v.strip()
                        for s in ['.', ' ', '•', '·', '[', ']', '-', '|', '/']:
                            tmpspars = tmpspars.replace(s, '')
                        if tmpspars in discogssmells.validsparscodes:
                            sparsfound = True
                # print error if some SPARS code reference was found
                if sparsfound:
                    count += 1
                    errormsgs.append('%8d -- SPARS Code (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
        if config_settings['check_label_code']:
            if identifier['type'] == 'Label Code':
                # check how many people use 'O' instead of '0'
                if v.lower().startswith('lc'):
                    if 'O' in identifier['value']:
                        errormsgs.append('%8d -- Spelling error in Label Code): https://www.discogs.com/release/%s' % (count, str(release_id)))
                        sys.stdout.flush()
                if discogssmells.labelcodere.match(v.lower()) is None:
                    count += 1
                    errormsgs.append('%8d -- Label Code (value): https://www.discogs.com/release/%s' % (count, str(release_id)))
            else:
                if identifier['type'] == 'Rights Society':
                    if v.lower().startswith('lc'):
                        if discogssmells.labelcodere.match(v.lower()) != None:
                            count += 1
                            errormsgs.append('%8d -- Label Code (in Rights Society): https://www.discogs.com/release/%s' % (count, str(release_id)))
                elif identifier['type'] == 'Barcode':
                    if v.lower().startswith('lc'):
                        if discogssmells.labelcodere.match(v.lower()) != None:
                            count += 1
                            errormsgs.append('%8d -- Label Code (in Barcode): https://www.discogs.com/release/%s' % (count, str(release_id)))
                else:
                    if 'description' in identifier:
                        if identifier['description'].lower() in discogssmells.label_code_ftf:
                            count += 1
                            errormsgs.append('%8d -- Label Code: https://www.discogs.com/release/%s' % (count, str(release_id)))
        if config_settings['check_rights_society']:
            if identifier['type'] != 'Rights Society':
                foundrightssociety = False
                for r in discogssmells.rights_societies:
                    if v.replace('.', '') == r or v.replace(' ', '') == r:
                        count += 1
                        foundrightssociety = True
                        if identifier['type'] == 'Barcode':
                            errormsgs.append('%8d -- Rights Society (Barcode): https://www.discogs.com/release/%s' % (count, str(release_id)))
                        else:
                            errormsgs.append('%8d -- Rights Society (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
                        break
                if not foundrightssociety and 'description' in identifier:
                    if identifier['description'].lower() in discogssmells.rights_societies_ftf:
                        count += 1
                        errormsgs.append('%8d -- Rights Society: https://www.discogs.com/release/%s' % (count, str(release_id)))

        # temporary hack, move to own configuration option
        asinstrict = False
        if config_settings['check_asin']:
            if identifier['type'] == 'ASIN':
                if not asinstrict:
                    tmpasin = v.strip().replace('-', '')
                else:
                    tmpasin = v
                if not len(tmpasin.split(':')[-1].strip()) == 10:
                    count += 1
                    errormsgs.append('%8d -- ASIN (wrong length): https://www.discogs.com/release/%s' % (count, str(release_id)))
            else:
                if 'description' in identifier:
                    if identifier['description'].lower().startswith('asin'):
                        count += 1
                        errormsgs.append('%8d -- ASIN (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
        if config_settings['check_isrc']:
            if identifier['type'] == 'ISRC':
                # Check the length of ISRC fields. According to the
                # specifications these should be 12 in length. Some ISRC
                # identifiers that have been recorded in the database
                # span a range of tracks. These will be reported as wrong ISRC
                # codes. It is unclear what needs to be done with those.

                # first get rid of cruft
                isrc_tmp = v.strip().upper()
                if isrc_tmp.startswith('ISRC'):
                    isrc_tmp = isrc_tmp.split('ISRC')[-1].strip()
                if isrc_tmp.startswith('CODE'):
                    isrc_tmp = isrc_tmp.split('CODE')[-1].strip()

                # replace a few characters
                isrc_tmp = isrc_tmp.replace('-', '')
                isrc_tmp = isrc_tmp.replace(' ', '')
                isrc_tmp = isrc_tmp.replace('.', '')
                isrc_tmp = isrc_tmp.replace(':', '')
                isrc_tmp = isrc_tmp.replace('–', '')
                if not len(isrc_tmp) == 12:
                    count += 1
                    errormsgs.append('%8d -- ISRC (wrong length): https://www.discogs.com/release/%s' % (count, str(release_id)))
            else:
                if 'description' in identifier:
                    if identifier['description'].lower().startswith('isrc'):
                        count += 1
                        errormsgs.append('%8d -- ISRC Code (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
                    elif identifier['description'].lower().startswith('issrc'):
                        count += 1
                        errormsgs.append('%8d -- ISRC Code (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
                    else:
                        for isrc in discogssmells.isrc_ftf:
                            if isrc in identifier['description'].lower():
                                count += 1
                                errormsgs.append('%8d -- ISRC Code (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
        if identifier['type'] == 'Barcode':
            pass

        # check depósito legal in BaOI
        if config_settings['check_deposito']:
            if 'country' in release:
                if release['country'] == 'Spain':
                    if identifier['type'] == 'Depósito Legal':
                        founddeposito = True
                        if v.strip().endswith('.'):
                            count += 1
                            errormsgs.append('%8d -- Depósito Legal (formatting): https://www.discogs.com/release/%s' % (count, str(release_id)))
                        if year != None:
                            # now try to find the year
                            depositoyear = None
                            if v.strip().endswith('℗'):
                                count += 1
                                errormsgs.append('%8d -- Depósito Legal (formatting, has ℗): https://www.discogs.com/release/%s' % (count, str(release_id)))
                                # ugly hack, remove ℗ to make at least be able to do some sort of check
                                v = v.strip().rsplit('℗', 1)[0]
                            # several separators, including some Unicode ones
                            for sep in ['-', '–', '/', '.', ' ', '\'', '_']:
                                try:
                                    depositoyeartext = v.strip().rsplit(sep, 1)[-1]
                                    if sep == '.' and len(depositoyeartext) == 3:
                                        continue
                                    if '.' in depositoyeartext:
                                        depositoyeartext = depositoyeartext.replace('.', '')
                                    depositoyear = int(depositoyeartext)
                                    if depositoyear < 100:
                                        # correct the year. This won't work correctly after 2099.
                                        if depositoyear <= currentyear - 2000:
                                            depositoyear += 2000
                                        else:
                                            depositoyear += 1900
                                    break
                                except:
                                    pass

                            # TODO, also allow (year), example: https://www.discogs.com/release/265497
                            if depositoyear != None:
                                if depositoyear < 1900:
                                    count += 1
                                    errormsgs.append("%8d -- Depósito Legal (impossible year): https://www.discogs.com/release/%s" % (count, str(release_id)))
                                elif depositoyear > currentyear:
                                    count += 1
                                    errormsgs.append("%8d -- Depósito Legal (impossible year): https://www.discogs.com/release/%s" % (count, str(release_id)))
                                elif year < depositoyear:
                                    count += 1
                                    errormsgs.append("%8d -- Depósito Legal (release date earlier): https://www.discogs.com/release/%s" % (count, str(release_id)))
                            else:
                                count += 1
                                errormsgs.append("%8d -- Depósito Legal (year not found): https://www.discogs.com/release/%s" % (count, str(release_id)))
                    elif identifier['type'] == 'Barcode':
                        for depositovalre in discogssmells.depositovalres:
                            if depositovalre.match(v.lower()) != None:
                                founddeposito = True
                                count += 1
                                errormsgs.append('%8d -- Depósito Legal (in Barcode): https://www.discogs.com/release/%s' % (count, str(release_id)))
                                break
                    else:
                        if v.startswith("Depósito"):
                            founddeposito = True
                            count += 1
                            errormsgs.append('%8d -- Depósito Legal (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
                        elif v.startswith("D.L."):
                            founddeposito = True
                            count += 1
                            errormsgs.append('%8d -- Depósito Legal (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))
                        else:
                            if 'description' in identifier:
                                found = False
                                for d in discogssmells.depositores:
                                    result = d.search(identifier['description'].lower())
                                    if result != None:
                                        found = True
                                        break

                                # sometimes the depósito value itself can be found in the free text field
                                if not found:
                                    for depositovalre in discogssmells.depositovalres:
                                        deposres = depositovalre.match(identifier['description'].lower())
                                        if deposres != None:
                                            found = True
                                            break

                                if found:
                                    founddeposito = True
                                    count += 1
                                    errormsgs.append('%8d -- Depósito Legal (BaOI): https://www.discogs.com/release/%s' % (count, str(release_id)))

        # temporary hack, move to own configuration option
        mould_sid_strict = False
        if config_settings['check_mould_sid']:
            if identifier['type'] == 'Mould SID Code':
                if v.strip() != 'none':
                    # cleanup first for not so heavy formatting booboos
                    mould_tmp = v.strip().lower().replace(' ', '')
                    mould_tmp = mould_tmp.replace('-', '')
                    # some people insist on using ƒ instead of f
                    mould_tmp = mould_tmp.replace('ƒ', 'f')
                    res = discogssmells.mouldsidre.match(mould_tmp)
                    if res is None:
                        count += 1
                        errormsgs.append('%8d -- Mould SID Code (value): https://www.discogs.com/release/%s' % (count, str(release_id)))
                    else:
                        if mould_sid_strict:
                            mould_split = mould_tmp.split('ifpi', 1)[-1]
                            for ch in ['i', 'o', 's', 'q']:
                                if ch in mould_split[-2:]:
                                    count += 1
                                    errormsgs.append('%8d -- Mould SID Code (strict value): https://www.discogs.com/release/%s' % (count, str(release_id)))
                        # rough check to find SID codes for formats other than CD/CD-like
                        if len(formattexts) == 1:
                            for fmt in set(['Vinyl', 'Cassette', 'Shellac', 'File', 'VHS', 'DCC', 'Memory Stick', 'Edison Disc']):
                                if fmt in formattexts:
                                    count += 1
                                    errormsgs.append('%8d -- Mould SID Code (Wrong Format: %s): https://www.discogs.com/release/%s' % (count, fmt, str(release_id)))
                                    break
                        if year != None:
                            if year < 1993:
                                count += 1
                                errormsgs.append('%8d -- SID Code (wrong year): https://www.discogs.com/release/%s' % (count, str(release_id)))

            else:
                if 'description' in identifier:
                    description = identifier['description'].lower()
                    # squash repeated spaces
                    description = re.sub('\s+', ' ', description)
                    description = description.strip()
                    if description in ['source identification code', 'sid', 'sid code', 'sid-code']:
                        count += 1
                        errormsgs.append('%8d -- Unspecified SID Code: https://www.discogs.com/release/%s' % (count, str(release_id)))
                    elif description in discogssmells.mouldsids:
                        count += 1
                        errormsgs.append('%8d -- Mould SID Code: https://www.discogs.com/release/%s' % (count, str(release_id)))

        if config_settings['check_mastering_sid']:
            if identifier['type'] == 'Mastering SID Code':
                if v.strip() != 'none':
                    # cleanup first for not so heavy formatting booboos
                    master_tmp = v.strip().lower().replace(' ', '')
                    master_tmp = master_tmp.replace('-', '')
                    # some people insist on using ƒ instead of f
                    master_tmp = master_tmp.replace('ƒ', 'f')
                    res = discogssmells.masteringsidre.match(master_tmp)
                    if res is None:
                        count += 1
                        errormsgs.append('%8d -- Mastering SID Code (value): https://www.discogs.com/release/%s' % (count, str(release_id)))
                    else:
                        # rough check to find SID codes for formats other than CD/CD-like
                        if len(formattexts) == 1:
                            for fmt in set(['Vinyl', 'Cassette', 'Shellac', 'File', 'VHS', 'DCC', 'Memory Stick', 'Edison Disc']):
                                if fmt in formattexts:
                                    count += 1
                                    errormsgs.append('%8d -- Mastering SID Code (Wrong Format: %s): https://www.discogs.com/release/%s' % (count, fmt, str(release_id)))
                        if year != None:
                            if year < 1993:
                                count += 1
                                errormsgs.append('%8d -- SID Code (wrong year): https://www.discogs.com/release/%s' % (count, str(release_id)))
            else:
                if 'description' in identifier:
                    description = identifier['description'].lower()
                    # squash repeated spaces
                    description = re.sub('\s+', ' ', description)
                    description = description.strip()
                    if description in ['source identification code', 'sid', 'sid code', 'sid-code']:
                        count += 1
                        errormsgs.append('%8d -- Unspecified SID Code: https://www.discogs.com/release/%s' % (count, str(release_id)))
                    elif description in discogssmells.masteringsids:
                        count += 1
                        errormsgs.append('%8d -- Mastering SID Code: https://www.discogs.com/release/%s' % (count, str(release_id)))
                    elif description in ['sid code matrix', 'sid code - matrix', 'sid code (matrix)', 'sid-code, matrix', 'sid-code matrix', 'sid code (matrix ring)', 'sid code, matrix ring', 'sid code: matrix ring']:
                        count += 1
                        errormsgs.append('%8d -- Possible Mastering SID Code: https://www.discogs.com/release/%s' % (count, str(release_id)))
        if config_settings['check_pkd']:
            if 'country' in release:
                if release['country'] == 'India':
                    if 'pkd' in v.lower() or "production date" in v.lower():
                        if year != None:
                            # try a few variants
                            pkdres = re.search("\d{1,2}/((?:19|20)?\d{2})", v)
                            if pkdres != None:
                                pkdyear = int(pkdres.groups()[0])
                                if pkdyear < 100:
                                    # correct the year. This won't work correctly after 2099.
                                    if pkdyear <= currentyear - 2000:
                                        pkdyear += 2000
                                    else:
                                        pkdyear += 1900
                                if pkdyear < 1900:
                                    count += 1
                                    errormsgs.append("%8d -- Indian PKD (impossible year): https://www.discogs.com/release/%s" % (count, str(release_id)))
                                elif pkdyear > currentyear:
                                    count += 1
                                    errormsgs.append("%8d -- Indian PKD (impossible year): https://www.discogs.com/release/%s" % (count, str(release_id)))
                                elif year < pkdyear:
                                    count += 1
                                    errormsgs.append("%8d -- Indian PKD (release date earlier): https://www.discogs.com/release/%s" % (count, str(release_id)))
                        else:
                            count += 1
                            errormsgs.append('%8d -- India PKD code (no year): https://www.discogs.com/release/%s' % (count, str(release_id)))
                    else:
                        # now check the description
                        if 'description' in identifier:
                            description = identifier['description'].lower()
                            if 'pkd' in description or "production date" in description:
                                if year != None:
                                    # try a few variants
                                    pkdres = re.search("\d{1,2}/((?:19|20)?\d{2})", attrvalue)
                                    if pkdres != None:
                                        pkdyear = int(pkdres.groups()[0])
                                        if pkdyear < 100:
                                            # correct the year. This won't work correctly after 2099.
                                            if pkdyear <= currentyear - 2000:
                                                pkdyear += 2000
                                            else:
                                                pkdyear += 1900
                                        if pkdyear < 1900:
                                            count += 1
                                            errormsgs.append("%8d -- Indian PKD (impossible year): https://www.discogs.com/release/%s" % (count, str(release_id)))
                                        elif pkdyear > currentyear:
                                            count += 1
                                            errormsgs.append("%8d -- Indian PKD (impossible year): https://www.discogs.com/release/%s" % (count, str(release_id)))
                                        elif year < pkdyear:
                                            count += 1
                                            errormsgs.append("%8d -- Indian PKD (release date earlier): https://www.discogs.com/release/%s" % (count, str(release_id)))
                                    else:
                                        count += 1
                                        errormsgs.append('%8d -- India PKD code (no year): https://www.discogs.com/release/%s' % (count, str(release_id)))
        # check Czechoslovak manufacturing dates
        if config_settings['check_manufacturing_date_cs']:
            # config hack, needs to be in its own configuration option
            strict_cs = False
            strict_cs = True
            if 'country' in release:
                if release['country'] == 'Czechoslovakia':
                    if 'description' in identifier:
                        description = identifier['description'].lower()
                        if 'date' in description:
                            if year != None:
                                manufacturing_date_res = re.search("(\d{2})\s+\d$", identifier['value'].rstrip())
                                if manufacturing_date_res != None:
                                    manufacturing_year = int(manufacturing_date_res.groups()[0])
                                    if manufacturing_year < 100:
                                        manufacturing_year += 1900
                                        if manufacturing_year > year:
                                            count += 1
                                            errormsgs.append("%8d -- Czechoslovak manufacturing date (release year wrong): https://www.discogs.com/release/%s" % (count, str(release_id)))
                                        # possibly this check makes sense, but not always
                                        elif manufacturing_year < year and strict_cs:
                                            count += 1
                                            errormsgs.append("%8d -- Czechoslovak manufacturing date (release year possibly wrong): https://www.discogs.com/release/%s" % (count, str(release_id)))

    # finally check the notes for some errors
    if 'notes' in release:
        if '카지노' in release['notes']:
            # Korean casino spam that pops up every once in a while
            errormsgs.append('Spam: https://www.discogs.com/release/%s' % str(release_id))
        if 'country' in release:
            if release['country'] == 'Spain':
                if config_settings['check_deposito'] and not founddeposito:
                    # sometimes "deposito legal" can be found in the "notes" section
                    content_lower = release['notes'].lower()
                    for d in discogssmells.depositores:
                        result = d.search(content_lower)
                        if result != None:
                            count += 1
                            found = True
                            errormsgs.append('%8d -- Depósito Legal (Notes): https://www.discogs.com/release/%s' % (count, str(release_id)))
                            break
        if config_settings['check_html']:
            # see https://support.discogs.com/en/support/solutions/articles/13000014661-how-can-i-format-text-
            if '<a href="http://www.discogs.com/release/' in release['notes'].lower():
                count += 1
                errormsgs.append('%8d -- old link (Notes): https://www.discogs.com/release/%s' % (count, str(release_id)))
        if config_settings['check_creative_commons']:
            ccfound = False
            for cc in discogssmells.creativecommons:
                if cc in release['notes']:
                    count += 1
                    errormsgs.append('%8d -- Creative Commons reference (%s): https://www.discogs.com/release/%s' % (count, cc, str(release)))
                    ccfound = True
                    break

                if not ccfound:
                    if 'creative commons' in reales['notes'].lower():
                        count += 1
                        errormsgs.append('%8d -- Creative Commons reference: https://www.discogs.com/release/%s' % (count, str(release)))
                        ccfound = True
                        break

    for e in errormsgs:
        print(e)
        if config_settings['use_notify_send']:
            p = subprocess.Popen(['notify-send', "-t", "3000", "Error", e], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            (stanout, stanerr) = p.communicate()
    sys.stdout.flush()
    return count

def main(argv):
    parser = argparse.ArgumentParser()

    # the following options are provided on the commandline
    parser.add_argument("-c", "--config", action="store", dest="cfg", help="path to configuration file", metavar="FILE")
    parser.add_argument("-s", "--startvalue", action="store", dest="startvalue", help="start value for releases", metavar="STARTVALUE")
    parser.add_argument("-l", "--latest", action="store", dest="latest_value", help="value for latest release", metavar="LATEST")
    args = parser.parse_args()

    # some checks for the configuration file
    if args.cfg is None:
        parser.error("Configuration file missing")

    if not os.path.exists(args.cfg):
        parser.error("Configuration file does not exist")

    config = configparser.ConfigParser()

    configfile = open(args.cfg, 'r')

    try:
        config.read_file(configfile)
    except Exception:
        print("Cannot read configuration file", file=sys.stderr)
        sys.exit(1)

    startvalue = None
    # check for a startvalue
    if args.startvalue != None:
        try:
            startvalue = int(args.startvalue)
        except:
            parser.error("start value is not a valid integer, exciting")

    latest_release = None
    # check for a startvalue
    if args.latest_value != None:
        try:
            latest_release = int(args.latest_value)
        except:
            parser.error("latest value is not a valid integer, exciting")

    # process the configuration file and store settings
    config_settings = {}

    for section in config.sections():
        if section == 'cleanup':
            # store settings for depósito legal checks
            try:
                if config.get(section, 'deposito') == 'yes':
                    config_settings['check_deposito'] = True
                else:
                    config_settings['check_deposito'] = False
            except Exception:
                config_settings['check_deposito'] = True

            # store settings for rights society checks
            try:
                if config.get(section, 'rights_society') == 'yes':
                    config_settings['check_rights_society'] = True
                else:
                    config_settings['check_rights_society'] = False
            except Exception:
                config_settings['check_rights_society'] = True

            # store settings for label code checks
            try:
                if config.get(section, 'label_code') == 'yes':
                    config_settings['check_label_code'] = True
                else:
                    config_settings['check_label_code'] = False
            except Exception:
                config_settings['check_label_code'] = True

            # store settings for label name checks
            try:
                if config.get(section, 'label_name') == 'yes':
                    config_settings['check_label_name'] = True
                else:
                    config_settings['check_label_name'] = False
            except Exception:
                config_settings['check_label_name'] = True

            # store settings for ISRC checks
            try:
                if config.get(section, 'isrc') == 'yes':
                    config_settings['check_isrc'] = True
                else:
                    config_settings['check_isrc'] = False
            except Exception:
                config_settings['check_isrc'] = True

            # store settings for ASIN checks
            try:
                if config.get(section, 'asin') == 'yes':
                    config_settings['check_asin'] = True
                else:
                    config_settings['check_asin'] = False
            except Exception:
                config_settings['check_asin'] = True

            # store settings for mastering SID checks
            try:
                if config.get(section, 'mastering_sid') == 'yes':
                    config_settings['check_mastering_sid'] = True
                else:
                    config_settings['check_mastering_sid'] = False
            except Exception:
                config_settings['check_mastering_sid'] = True

            # store settings for mould SID checks
            try:
                if config.get(section, 'mould_sid') == 'yes':
                    config_settings['check_mould_sid'] = True
                else:
                    config_settings['check_mould_sid'] = False
            except Exception:
                config_settings['check_mould_sid'] = True

            # store settings for SPARS Code checks
            try:
                if config.get(section, 'spars') == 'yes':
                    config_settings['check_spars_code'] = True
                else:
                    config_settings['check_spars_code'] = False
            except Exception:
                config_settings['check_spars_code'] = True

            # store settings for Indian PKD checks
            try:
                if config.get(section, 'pkd') == 'yes':
                    config_settings['check_pkd'] = True
                else:
                    config_settings['check_pkd'] = False
            except Exception:
                config_settings['check_pkd'] = True

            # check for Czechoslovak manufacturing dates
            try:
                if config.get(section, 'manufacturing_date_cs') == 'yes':
                    config_settings['check_manufacturing_date_cs'] = True
                else:
                    config_settings['check_manufacturing_date_cs'] = False
            except Exception:
                config_settings['check_manufacturing_date_cs'] = True

            # check for Czechoslovak and Czech spelling (0x115 used instead of 0x11B)
            try:
                if config.get(section, 'spelling_cs') == 'yes':
                    config_settings['check_spelling_cs'] = True
                else:
                    config_settings['check_spelling_cs'] = False
            except Exception:
                config_settings['check_spelling_cs'] = True

            # store settings for tracklisting checks, default True
            try:
                if config.get(section, 'tracklisting') == 'yes':
                    config_settings['check_tracklisting'] = True
                else:
                    config_settings['check_tracklisting'] = False
            except Exception:
                config_settings['check_tracklisting'] = True

            # store settings for credits list checks
            try:
                if config.get(section, 'credits') == 'yes':
                    creditsfile = config.get(section, 'creditsfile')
                    if os.path.exists(creditsfile):
                        config_settings['creditsfile'] = creditsfile
                        config_settings['check_credits'] = True
                else:
                    config_settings['check_credits'] = False
            except Exception:
                config_settings['check_credits'] = False

            # store settings for URLs in Notes checks
            try:
                if config.get(section, 'html') == 'yes':
                    config_settings['check_html'] = True
                else:
                    config_settings['check_html'] = False
            except Exception:
                config_settings['check_html'] = True

            # month is 00 check: default is False
            try:
                if config.get(section, 'month') == 'yes':
                    config_settings['check_month'] = True
                else:
                    config_settings['check_month'] = False
            except Exception:
                config_settings['check_month'] = False

            # year is wrong check: default is False
            try:
                if config.get(section, 'year') == 'yes':
                    config_settings['check_year'] = True
                else:
                    config_settings['check_year'] = False
            except Exception:
                config_settings['check_year'] = False

            # reporting all: default is False
            try:
                if config.get(section, 'reportall') == 'yes':
                    config_settings['reportall'] = True
                else:
                    config_settings['reportall'] = False
            except Exception:
                config_settings['reportall'] = False

            # debug: default is False
            try:
                if config.get(section, 'debug') == 'yes':
                    config_settings['debug'] = True
                else:
                    config_settings['debug'] = False
            except Exception:
                config_settings['debug'] = False

            # report creative commons references: default is False
            try:
                if config.get(section, 'creative_commons') == 'yes':
                    config_settings['check_creative_commons'] = True
                else:
                    config_settings['check_creative_commons'] = False
            except Exception:
                config_settings['check_creative_commons'] = False

        elif section == 'api':
            # data directory to store JSON files
            try:
                storedir = config.get(section, 'storedir')
                if not os.path.exists(os.path.normpath(storedir)):
                    config_settings['storedir'] = None
                else:
                    # test if the directory is writable
                    testfile = tempfile.mkstemp(dir=storedir)
                    os.fdopen(testfile[0]).close()
                    os.unlink(testfile[1])
                    config_settings['storedir'] = storedir
            except Exception:
                config_settings['storedir'] = None
                break
            try:
                token = config.get(section, 'token')
                config_settings['token'] = token
            except Exception:
                config_settings['token'] = None
            try:
                username = config.get(section, 'username')
                config_settings['username'] = username
            except Exception:
                config_settings['username'] = None

            # skipdownloaded: default is False
            config_settings['skipdownloaded'] = False
            try:
                if config.get(section, 'skipdownloaded') == 'yes':
                    config_settings['skipdownloaded'] = True
            except Exception:
                pass

            # skip404: default is True
            config_settings['skip404'] = True
            try:
                if config.get(section, 'skip404') == 'yes':
                    config_settings['skip404'] = True
                else:
                    config_settings['skip404'] = False
            except Exception:
                pass

            # record404: default is True
            config_settings['record404'] = True
            try:
                if config.get(section, 'record404') == 'yes':
                    config_settings['record404'] = True
                else:
                    config_settings['record404'] = False
            except Exception:
                pass

            # specify location of 404 file
            try:
                release404 = os.path.normpath(config.get(section, '404file'))
                config_settings['404file'] = release404
            except:
                pass

            # specify whether or not notify-send (Linux desktops
            # should be used or not. Not recommended.
            config_settings['use_notify_send'] = True
            try:
                if config.get(section, 'notify') == 'yes':
                    config_settings['use_notify_send'] = True
                else:
                    config_settings['use_notify_send'] = False
            except Exception:
                pass

    if config_settings['use_notify_send']:
        try:
            p = subprocess.Popen(['notify-send', "-t", "3000", "Test for notify-send"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            (stanout, stanerr) = p.communicate()
        except Exception:
            config_settings['use_notify_send'] = False

    configfile.close()
    if config_settings['storedir'] is None:
        print("Data store directory non-existent or not writable, exiting.", file=sys.stderr)
        sys.exit(1)
    if config_settings['token'] is None:
        print("Token not specified, exiting.", file=sys.stderr)
        sys.exit(1)
    if config_settings['username'] is None:
        print("Discogs user name not specified, exiting.", file=sys.stderr)
        sys.exit(1)

    # a list of accepted roles. This is an external file, generated with extractcredits.py
    # from the 'helper-scripts' directory.
    credits = set()
    if 'check_credits' in config_settings:
        if config_settings['check_credits']:
            creditsfile = open(config_settings['creditsfile'], 'r')
            credits = set(map(lambda x: x.strip(), creditsfile.readlines()))
            creditsfile.close()

    # a file with release numbers that give a 404 error
    # This needs more work
    if config_settings['skip404']:
        if '404file' in config_settings:
            if not os.path.isabs(config_settings['404file']):
                release404filename = os.path.join(config_settings['storedir'], config_settings['404file'])
                if not os.path.exists(release404filename):
                    release404file = open(release404filename, 'w')
                    release404file.close()
            else:
                release404filename = config_settings['404file']
        else:
            # simply create the file
            pass

    # use a (somewhat) exponential backoff in case too many requests have been made
    ratelimitbackoff = 5

    # set the User Agent and Authorization header for each user request
    useragentstring = "DiscogsCleanupForUser-%s/0.1" % config_settings['username']
    headers = {'user-agent': useragentstring,
               'Authorization': 'Discogs token=%s' % config_settings['token']
              }

    if latest_release is None:
        latest_release = get_latest_release(headers)
        if latest_release is None:
            print("Something went wrong, try again later", file=sys.stderr)
            sys.exit(1)

    # if no start value has been provided start with the latest from the
    # Discogs website.
    if startvalue is None:
        startvalue = latest_release

    # populate a set with all the 404s that were found.
    skip404s = set()
    count = 0
    if config_settings['skip404']:
        release404file = open(release404filename, 'r')
        for l in release404file:
            # needs to be made more robust
            skip404s.add(int(l.strip()))
        release404file.close()
        # now open again for writing, so new 404 errors can be
        # stored.
        release404file = open(release404filename, 'a')

    # This is just something very silly: if you have an iBuddy device and
    # have the corresponding Python module installed it will respond to
    # data it finds (currently only favourite artists).
    #
    # https://github.com/armijnhemel/py3buddy
    #
    # Not recommended.
    ibuddy_enabled = False
    try:
        import py3buddy
        ibuddy_enabled = True
    except:
        pass

    ibuddy = None
    if ibuddy_enabled:
        ibuddy_config = {}
        ibuddy = py3buddy.iBuddy(ibuddy_config)
        if ibuddy.dev is None:
            ibuddy = None
            ibuddy_enabled = False

    # example:
    #favourites = set(['Bob Dylan', 'Iron Maiden', 'The Beatles'])
    favourites = set()

    newsleep = 600

    # now start a big loop
    # https://www.discogs.com/developers/#page:authentication
    while True:
        for releasenr in range(startvalue, latest_release+1):
            if startvalue == latest_release:
                break
            targetfilename = os.path.join(storedir, "%d" % (releasenr//1000000), "%d.json" % releasenr)
            os.makedirs(os.path.join(storedir, "%d" % (releasenr//1000000)), exist_ok=True)
            if config_settings['skip404']:
                if releasenr in skip404s:
                    continue
            if config_settings['skipdownloaded']:
                if os.path.exists(targetfilename):
                    if os.stat(targetfilename).st_size != 0:
                        responsejsonfile = open(targetfilename, 'r')
                        responsejson = json.loads(responsejsonfile.read())
                        responsejsonfile.close()
                        count = processrelease(responsejson, config_settings, count, credits, ibuddy, favourites)
                        continue
            print("downloading: %d" % releasenr, file=sys.stderr)
            r = requests.get('https://api.discogs.com/releases/%d' % releasenr, headers=headers)

            # now first check the headers to see if it is OK to do more requests
            if r.status_code != 200:
                if r.status_code == 404:
                    print("%d" % releasenr, file=release404file)
                    release404file.flush()
                if r.status_code == 429:
                    if 'Retry-After' in r.headers:
                        try:
                            retryafter = int(r.headers['Retry-After'])
                            print("Rate limiting, sleeping for %d seconds" % retryafter, file=sys.stderr)
                            time.sleep(retryafter)
                            sys.stderr.flush()
                        except:
                            print("Rate limiting, sleeping for %d seconds" % 60, file=sys.stderr)
                            time.sleep(60)
                            sys.stderr.flush()
                    else:
                        print("Rate limiting, sleeping for %d seconds" % 60, file=sys.stderr)
                        time.sleep(60)
                        sys.stderr.flush()
                # TODO: the current release will not have been downloaded and processed
                continue

            # in case there is no 429 response check the headers
            if 'X-Discogs-Ratelimit-Remaining' in r.headers:
                ratelimit = int(r.headers['X-Discogs-Ratelimit-Remaining'])
            if ratelimit == 0:
                # no more requests are allowed, so sleep for some
                # time, max 60 seconds
                time.sleep(ratelimitbackoff)
                print("Rate limiting, sleeping for %d seconds" % ratelimitbackoff, file=sys.stderr)
                sys.stderr.flush()
                if ratelimitbackoff < 60:
                    ratelimitbackoff = min(60, ratelimitbackoff * 2)
            else:
                ratelimitbackoff = 5

            # now process the response. This should be JSON, so decode it,
            # and also write the JSON data to a separate file for offline
            # processing (if necessary).
            try:
                responsejson = r.json()
                jsonreleasefile = open(targetfilename, 'w')
                jsonreleasefile.write(r.text)
                jsonreleasefile.close()
            except:
                # response doesn't contain JSON, so something is wrong.
                # sleep a bit then continue
                time.sleep(2)
                continue
            # now process the JSON content
            count = processrelease(responsejson, config_settings, count, credits, ibuddy, favourites)
            # be gentle for Discogs and sleep
            time.sleep(0.2)
            sys.stderr.flush()

        # now set startvalue to latest_release
        startvalue = latest_release

        # and find the newest release again
        print("Grabbing new data", file=sys.stderr)
        latest_release = get_latest_release(headers)
        if latest_release is None:
            print("Something went wrong, try again later", file=sys.stderr)
            break
        if latest_release < startvalue:
            pass
        print("Latest = %d" % latest_release, file=sys.stderr)
        print("Sleeping for %d seconds" % newsleep, file=sys.stderr)
        sys.stderr.flush()

        # sleep for ten minutes to make sure some new things
        # have been added to Discogs
        time.sleep(newsleep)
    release404file.close()

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


import time
import quina
from quina.widgets import Widget
from quina.core import PointF, Qt
from quina.gui import Painter, Pen

class NumberWidget(Widget):
    def __init__(self, value: str, parent: Widget):
        super().__init__(parent)
        self.value = value
        self.background_color = Qt.black

    def paint(self, painter: Painter):
        painter.setPen(Pen(Qt.white))
        right_bottom = PointF(self.width(), self.height())
        painter.draw_text_center(right_bottom / 2, self.value)

if __name__ == '__main__':
    widget = Widget()
    widget.setMinimumSize(400, 300)

    timer = quina.core.Timer()
    timer.start(0.1)

    @quina.connect_with(timer.timeout)
    def replace_center_widget():
        sub_widget = NumberWidget(str(time.time()), widget)
        widget.set_square_widget(sub_widget)

    widget.exec()


from good_morning.calibrations.ultility import get_target, readout_convertor
from good_morning.fittings.fit_resonance import fit_resonance
from good_morning.fittings.fit_rabi_osc import fit_ramsey

from core_tools.sweeps.pulse_lib_wrappers.PSB_exp import run_qubit_exp
from core_tools.utility.variable_mgr.var_mgr import variable_mgr
from core_tools.sweeps.sweeps import scan_generic, do0D, do1D
from core_tools.job_mgnt.job_mgmt import job_wrapper

from pulse_templates.coherent_control.single_qubit_gates.single_qubit_gates import single_qubit_gate_spec
from pulse_lib.segments.utility.looping import linspace

from dev_V2.six_qubit_QC_v2.system import six_dot_sample
from pulse_templates.utility.plotting import plot_seg

import qcodes as qc
import numpy as np

import good_morning.static.J12 as J12
import good_morning.static.J23 as J23
import good_morning.static.J34 as J34
import good_morning.static.J45 as J45
import good_morning.static.J56 as J56

def CROT_cali_meas(pair, target, ancilla, old_freq, time, flip):
	s = six_dot_sample(qc.Station.default.pulse)

	var_mgr = variable_mgr()

	pair = str(int(pair))
	s.init(pair[0], pair[1])
	s.add(s.pre_pulse)

	s.add(s.wait(10000))

	gate_set = getattr(s, f'q{pair}')
	target_gate_set = getattr(s, f'q{pair[target-1]}')
	ancilla_gate_set = getattr(s, f'q{pair[ancilla-1]}')

	if flip:
		s.add(ancilla_gate_set.X180)

	scan_range = single_qubit_gate_spec(f'qubit{target}_MW',
                                    linspace(old_freq*1e9-15e6, old_freq*1e9+15e6,30, axis= 0, name='freq', unit='Hz'),
                                    time, getattr(var_mgr, f'CROT{pair}_MW_power'), padding=20)

	s.add(getattr(gate_set, f'CROT{target}{ancilla}') , gate_spec = scan_range)

	s.add(s.wait(50e3))
	s.read(pair[target-1])

	sequence, minstr, name = run_qubit_exp(f'crot-z_crot_cal_q{pair}_target{pair[target-1]}', s.sequencer)

	return sequence, minstr, name

@job_wrapper
def CROT_calib(pair, target, ancilla, plot=False):
	'''
	calibrates a single qubit phase for a target qubit

	Args:
		target (int) : qubit number to target (1-6)
		ancillary (list) : qubit that need to be initialized to make this work
	'''
	var_mgr = variable_mgr()
	pair = str(int(pair))

	old_crot = getattr(var_mgr, f'crot{pair[target-1]}{pair[ancilla-1]}')
	# old_z_crot = getattr(var_mgr, f'z_crot{pair[target-1]}{pair[ancilla-1]}')
	crot_time = getattr(var_mgr, f'pi_crot{pair[target-1]}')
	# z_crot_time = getattr(var_mgr, f'pi_z_crot{pair[target-1]}')

	gate_time= crot_time#min(crot_time,z_crot_time)

	sequence, minstr, name = CROT_cali_meas(pair, target, ancilla, old_crot, gate_time, 1)
	ds_one = scan_generic(sequence, minstr, name=name).run()
	# sequence, minstr, name = CROT_cali_meas(pair, target, ancilla, old_z_crot, gate_time, 0)
	# ds_two = scan_generic(sequence, minstr, name=name).run()

	frequency_one = ds_one(readout_convertor(f'read{pair[target-1]}')).x()
	probabilities_one = ds_one(readout_convertor(f'read{pair[target-1]}')).y()
	fit_freq_one = fit_resonance(frequency_one[5:], probabilities_one[5:], plot=plot)

	# frequency_two = ds_two(readout_convertor(f'read{pair[target-1]}')).x()
	# probabilities_two = ds_two(readout_convertor(f'read{pair[target-1]}')).y()
	# fit_freq_two = fit_resonance(frequency_two[5:], probabilities_two[5:], plot=plot)

	crot_res = fit_freq_one#max(fit_freq_one, fit_freq_two)
	# z_crot_res = min(fit_freq_one, fit_freq_two)

	# old_z_crot = getattr(var_mgr, f'z_crot{pair[target-1]}{pair[ancilla-1]}')
	# setattr(var_mgr, f'z_crot{pair[target-1]}{pair[ancilla-1]}', round(z_crot_res*1e-9,6))
	old_crot = getattr(var_mgr, f'crot{pair[target-1]}{pair[ancilla-1]}')
	setattr(var_mgr, f'crot{pair[target-1]}{pair[ancilla-1]}', round(crot_res*1e-9,6))
	# print(f'calibrated z_crot_res for qubit pair {pair}, target {pair[target-1]} '+
	# 	f'Old z_crot_res : {round(old_z_crot,6)} \n New z_crot_res : {round(z_crot_res*1e-9,6)} \n')
	print(f'calibrated crot_res for qubit pair {pair}, target {pair[target-1]} '+
		f'Old crot_res : {round(old_crot,6)} \n New z_crot_res : {round(crot_res*1e-9,6)} \n')

def crot_calib_seq(target, CROT_target_gate, gate_spec, additional_instruction=None):
	s = six_dot_sample(qc.Station.default.pulse)

	s.add(s.pre_pulse)

	s.init(target[0], target[1])
	s.add(s.wait(100))

	if additional_instruction is not None:
		s.add(get_target(s, additional_instruction))

	s.add(s.wait(500))
	s.add(get_target(s, CROT_target_gate), gate_spec = gate_spec)

	s.add(s.wait(100))
	s.read(target[0], target[1])

	sequence, minstr, name = run_qubit_exp(f'crot_{CROT_target_gate}_calibration', s.sequencer)
	return scan_generic(sequence, minstr, name=name).run()

@job_wrapper
def crot_calib_freq(pair):
	'''
	calibrate CROT frequencies
	'''
	# generate variables if they don't exist.
	var_mgr = variable_mgr()
	pair = str(int(pair))

	if not hasattr(var_mgr, f'CROT{pair}_J_target'):
		var_mgr.add_variable(f'CROT{pair}', f'CROT{pair}_J_target','MHz',0.1,5)

	for i in ['12', '12_z', '21', '21_z']:

		target = pair[0] if '12' in i else pair[1]
		not_target = pair[1] if '12' in i else pair[0]

		if not hasattr(var_mgr, f'CROT{pair}_{i}_MW_power'):
			power = getattr(var_mgr, f'q{target}_MW_power')
			var_mgr.add_variable(f'CROT{pair}',f'CROT{pair}_{i}_MW_power','mV',0.1,power)
		if not hasattr(var_mgr, f'CROT{pair}_{i}_freq'):
			freq = getattr(var_mgr, f'frequency_q{target}')
			var_mgr.add_variable(f'CROT{pair}',f'CROT{pair}_{i}_freq','GHz',0.1,freq)
		if not hasattr(var_mgr, f'CROT{pair}_{i}_pi_time'):
			time = getattr(var_mgr, f'pi_q{target}')
			var_mgr.add_variable(f'CROT{pair}', f'CROT{pair}_{i}_pi_time','ns',0.1,time)

		additional_instr = None
		if 'z' in i:
			additional_instr = f'q{not_target}.X180'

		old_freq = getattr(var_mgr, f'CROT{pair}_{i}_freq')
		gate_spec = single_qubit_gate_spec(f'qubit{target}_MW', linspace(old_freq*1e9-10e6, old_freq*1e9+10e6, 75, axis= 0, name='freq', unit='Hz'),
                                        getattr(var_mgr, f'CROT{pair}_{i}_pi_time'), getattr(var_mgr, f'CROT{pair}_{i}_MW_power'), padding=10)

		ds = crot_calib_seq(pair, f'q{pair}.CROT_{i}', gate_spec, additional_instr)

		frequency = ds(readout_convertor(f'read{target}')).x()
		probabilities = ds(readout_convertor(f'read{target}')).y()
		freq = fit_resonance(frequency[5:], probabilities[5:], plot=True)

		setattr(var_mgr, f'CROT{pair}_{i}_freq', round(freq*1e-9,4))

		print(f'Updating frequency for CROT{pair} from \n\t{old_freq} GHz to \n\t{round(freq*1e-9,4)} GHz')

@job_wrapper
def crot_calib_time(pair):
	'''
	calibrate CROT frequencies
	'''
	# generate variables if they don't exist.
	var_mgr = variable_mgr()
	pair = str(int(pair))

	for i in ['12', '12_z', '21', '21_z']:

		target = pair[0] if '12' in i else pair[1]
		not_target = pair[1] if '12' in i else pair[0]

		additional_instr = None
		if 'z' in i:
			additional_instr = f'q{not_target}.X180'

		old_time = getattr(var_mgr, f'CROT{pair}_{i}_pi_time')
		gate_spec = single_qubit_gate_spec(f'qubit{target}_MW', getattr(var_mgr, f'CROT{pair}_{i}_freq')*1e9,
                                linspace(0,old_time*4, 50, axis= 0, name='time', unit='ns'), getattr(var_mgr, f'CROT{pair}_{i}_MW_power'), padding=10)

		ds = crot_calib_seq(pair, f'q{pair}.CROT_{i}', gate_spec, additional_instr)

		time = ds(readout_convertor(f'read{target}')).x()
		probabilities = ds(readout_convertor(f'read{target}')).y()
		pi_time = fit_ramsey(time[5:]*1e-9, probabilities[5:], plot=True)

		setattr(var_mgr, f'CROT{pair}_{i}_pi_time', round(pi_time))

		print(f'Updating time for CROT{pair} from \n\t{old_time} ns to \n\t{round(pi_time)} ns')


import numpy as np
import cv2
from collections import deque
import argparse

ap = argparse.ArgumentParser()
ap.add_argument("-v", "--video",
	help="path to the (optional) video file")
ap.add_argument("-b", "--buffer", type=int, default=64,
	help="max buffer size")
args = vars(ap.parse_args())

cap = cv2.VideoCapture(0)

greenLower = (29, 86, 6)
greenUpper = (64, 255, 255)
pts = deque(maxlen=args["buffer"])

while(True):
    # Capture frame-by-frame
    ret, frame = cap.read()

    # Our operations on the frame come here
    #gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # Display the resulting frame
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

    green_mask = cv2.inRange(hsv, greenLower, greenUpper)
    green_mask = cv2.erode(green_mask, None, iterations=2)
    green_mask = cv2.dilate(green_mask, None, iterations=2)
#    cnts = cv2.findContours(green_mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    cnts = cv2.findContours(green_mask, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)[-2]
    center = None
    if len(cnts) > 0:
        c = max(cnts, key=cv2.contourArea)
        ((x, y), radius) = cv2.minEnclosingCircle(c)
        M = cv2.moments(c)
        center = (int(M["m10"] / M["m00"]), int(M["m01"] / M["m00"]))
        if radius > 10:
			# draw the circle and centroid on the frame,
			# then update the list of tracked points
            cv2.circle(frame, (int(x), int(y)), int(radius),
                       (0, 255, 255), 2)
            cv2.circle(frame, center, 5, (0, 0, 255), -1)

    pts.appendleft(center)
    for i in xrange(1, len(pts)):
        # if either of the tracked points are None, ignore
        if pts[i - 1] is None or pts[i] is None:
            continue

        # otherwise, compute the thickness of the line and
        # draw the connecting lines
        thickness = int(np.sqrt(args["buffer"] / float(i + 1)) * 2.5)
        cv2.line(frame, pts[i - 1], pts[i], (0, 0, 255), thickness)

# show the frame to our screen
    cv2.imshow("Frame", frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

#    res_green = cv2.bitwise_and(frame,frame, mask= green_mask)
#    green_mask = cv2.erode(green_mask, None, iterations=2)
#    green_mask = cv2.dilate(green_mask, None, iterations=2)
#    cv2.imshow('frame',frame)
#    cv2.imshow('gree mark',res_green)
#    if cv2.waitKey(1) & 0xFF == ord('q'):
#        break

# When everything done, release the capture
cap.release()
cv2.destroyAllWindows()


"""
Counting Sundays

Problem 19
You are given the following information, but you may prefer to do some research for yourself.

- 1 Jan 1900 was a Monday.
- Thirty days has September,
  April, June and November.
  All the rest have thirty-one,
  Saving February alone,
  Which has twenty-eight, rain or shine.
  And on leap years, twenty-nine.
- A leap year occurs on any year evenly divisible by 4, but not on a century unless it is divisible by 400.

How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)?
"""
import datetime
import math
import time

monthDays = {'January': 31, 'February': 28, 'March': 31, 'April': 30,
             'May': 31, 'June': 30, 'July': 31, 'August': 31,
             'September': 30, 'October': 31, 'November': 30, 'December': 31}
# daysOfWeek = {0: 'Sunday', 1: 'Monday', 2: 'Tuesday', 3: 'Wednesday', 4: 'Thursday', 5: 'Friday', 6: 'Saturday'}
# daysOfWeek = {0: 'Saturday', 1: 'Sunday', 2: 'Monday', 3: 'Tuesday', 4: 'Wednesday', 5: 'Thursday', 6: 'Friday'}
daysOfWeekISO = {1: 'Monday', 2: 'Tuesday', 3: 'Wednesday', 4: 'Thursday', 5: 'Friday', 6: 'Saturday', 7: 'Sunday'}

def isCentury(year):
    return year % 1000 == 0

def isLeapYear(year):
    if year % 4 == 0:
        if isCentury(year):
            return year % 400 == 0
        else:
            return True
    else:
        return False

def leapYearsGenerator(start=1900):
    year = start
    while True:
        if isLeapYear(year):
            yield year
        year += 1

def sakamotoAlgorithm(year, month, day):
    """
    Compute day of week by using Sakamoto's algorithm

    https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week

    :param year: year of desired day of week
    :param month: month of desired day of week
    :param day: day of desired day of week
    :return: day of week: 0 = Sunday, 1 = Monday, etc.
    """
    t = [0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4]

    if month < 3:
        year -= 1

    return int((year + year / 4 - year / 100 + year / 400 + t[month - 1] + day) % 7)

def gaussAlgorithm(year, month, day):
    """
    Compute day of week by using Gauss's algorithm

    https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week

    :param year: year of desired day of week
    :param month: month of desired day of week
    :param day: day of desired day of week
    :return: day of week: 0=Sunday … 6=Saturday
    """
    monthShift = [11, 12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  # months are numbered from 3 for March to 14 for February

    if month < 3:  # is the year minus 1 for January or February, and the year for any other month
        year -= 1

    # is the first 2 digits of Y
    c = year // 100

    # is the last 2 digits of Y
    y = year % 100

    # is the day of the month (1 to 31)
    d = day

    # is the shifted month (March=1,...January = 11, February=12)
    m = monthShift[month - 1]

    w = (d + math.floor(2.6 * m - 0.2) + y + math.floor(y / 4) + math.floor(c / 4) - 2 * c) % 7

    return w

def zellerAlgorithm(year, month, day):
    """
    Compute day of week by using Zeller's algorithm

    https://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week

    :param year: year of desired day of week
    :param month: month of desired day of week
    :param day: day of desired day of week
    :return: ISO day-of-week: 1 = Monday to 7 = Sunday
    """
    monthShift = [13, 14, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]  # months are numbered from 3 for March to 14 for February

    if month < 3:  # is the year minus 1 for January or February, and the year for any other month
        year -= 1

    # is the first 2 digits of Y
    c = year // 100

    # is the last 2 digits of Y
    y = year % 100

    # is the day of the month (1 to 31)
    d = day

    # is the shifted month (March=3,...January = 13, February=14)
    m = monthShift[month - 1]

    # is the day of week (1=Sunday,..0=Saturday)
    w = (d + math.floor(13 * (m + 1) / 5) + y + math.floor(y / 4) + math.floor(c / 4) - 2 * c) % 7

    # convert ISO 8601 standard day of week (1=Monday,..7=Sunday)
    w = ((w + 5) % 7) + 1

    return w

def getDayOfWeek(year=datetime.date.today().year, month=datetime.date.today().month, day=datetime.date.today().day):
    """
    Get the day of the week for a given date

    :param year: year of desired day of week
    :param month: month of desired day of week
    :param day: day of desired day of week
    :return: ISO day-of-week: 1 = Monday to 7 = Sunday
    """
    dayOfWeek = zellerAlgorithm(year, month, day)

    print(f'{year}-{month}-{day}: {daysOfWeekISO[dayOfWeek]}')

    return dayOfWeek

def isSunday(year, month, day):
    """Check if day of week is Sunday"""
    return getDayOfWeek(year, month, day) == 7

if __name__ == '__main__':
    start = time.time()

    # How many Sundays fell on the first of the month during the twentieth century (1 Jan 1901 to 31 Dec 2000)
    sundayCount = 0
    for year in range(1901, 2001):
        for month in range(1, 13):
            if isSunday(year, month, 1):
                sundayCount += 1
    print(sundayCount)

    end = time.time()
    print(f"{(end - start):.6f} seconds")


from TensorflowToolbox.utility import file_io
import numpy as np
import cv2
import os
import sys

if __name__ == "__main__":
    if not len(sys.argv) == 3:
        print("Usage: label_preprocess.py data_dir size_len")
        exit(1)

    data_dir = sys.argv[1]
    size_len = int(sys.argv[2])
    dsize = (size_len, size_len)

    data_ext = "_%d.jpg"%size_len
    label_ext = "_%d.desmap"%size_len
    seg_ext = "_%d.segmap"%size_len

    cam_dir_list = file_io.get_dir_list(data_dir)

    train_list = list()
    test_list = list()
    full_file_list = list()
    for cam_dir in cam_dir_list:
        video_list = file_io.get_listfile(cam_dir, ".avi")
        for file_name in video_list:
            data_dir_name = file_name.replace(".avi", "")
            curr_data_list = file_io.get_listfile(data_dir_name, label_ext)
            for desmap_filename in curr_data_list:
                desmap = np.fromfile(desmap_filename, np.float32)
                desmap = np.reshape(desmap, dsize)
                segmap = desmap > 0
                segmap = segmap.astype(np.float32)
                segmap_filename = desmap_filename.replace(label_ext, seg_ext)
                segmap.tofile(segmap_filename)
                #cv2.imshow("image", segmap)
                #cv2.waitKey(0)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import matplotlib.pyplot as plt
import numpy as np

class Data(object):
    def __init__(self, filename, labellist):
        self.rawdata = np.loadtxt(filename)
        self.labellist = labellist
        self.make_data()

    def make_data(self):
        for i, label in enumerate(self.labellist):
            setattr(self, label, self.rawdata[:, i])

def load(filename):
    labellist = ['t',
                 'xd', 'yd', 'zd',
                 'x', 'y', 'z',
                 'vx', 'vy', 'vz',
                 'xz', 'yz', 'zz']
    data = Data(filename, labellist)
    return data

def plot_z(data):
    plt.figure()
    plt.plot(data.t, data.zd, label='zd', c='k', ls='--')
    plt.plot(data.t, data.z, label='z')
    plt.legend()
    plt.title('COM regulation along vertical direction')
    plt.xlabel('time [s]')
    plt.ylabel('z [m]')
    plt.grid(True)

def main():
    import sys
    data = load(sys.argv[1])
    plot_z(data)
    plt.show()

if __name__ == '__main__':
    main()


import os
import sys

from pyspark.ml.feature import StringIndexer
from pyspark.ml.tuning import ParamGridBuilder, CrossValidator
from pyspark.rdd import RDD
from pyspark.sql import Row
from pyspark.sql import DataFrame
from pyspark.sql import SparkSession
from pyspark.sql.functions import lit, regexp_replace
from pyspark.sql.functions import desc
from pyspark.ml.evaluation import RegressionEvaluator
from pyspark.ml.recommendation import ALS
import pandas as pd
from pyspark.sql.functions import col, avg
from pyspark.sql.types import IntegerType

def init_spark():
    spark = SparkSession \
        .builder \
        .appName("Python Spark SQL basic example") \
        .config("spark.some.config.option", "some-value") \
        .getOrCreate()
    return spark

spark = init_spark()
sc = spark.sparkContext
restaurant_file_name = "../data/zomato_restaurant.csv"
reviews_file_name = "../data/zomato_reviews.csv"

restaurant_df = spark.read.option("wholeFile", True).option("multiline", True).option("header", True)\
    .option("inferSchema", "True").csv(restaurant_file_name)
restaurant_df = restaurant_df.filter(restaurant_df['business_id'] != "#NAME?")

reviews_df = spark.read.option("wholeFile", True).option("multiline", True).option("header", True)\
    .option("inferSchema","True").csv(reviews_file_name, header=True)
reviews_df = reviews_df.withColumn('user_id', regexp_replace('user_id', '=-', ''))

restaurant_df = restaurant_df.filter(restaurant_df['city'] == "Phoenix")
restaurant_df = restaurant_df.select(["business_id", "categories", "review_count", "name", "city"])

indexer = StringIndexer(inputCol="business_id", outputCol="business_index")
restaurant_df = indexer.fit(restaurant_df).transform(restaurant_df)
restaurant_df = restaurant_df.withColumn("business_index", restaurant_df["business_index"].cast(IntegerType()))

reviews_df = reviews_df.select(["business_id", "user_id", "stars"])

restaurant_reviews_df = reviews_df.join(restaurant_df, ['business_id'])
restaurant_reviews_df = restaurant_reviews_df.filter(restaurant_reviews_df.business_id.isNotNull())
restaurant_reviews_df = restaurant_reviews_df.filter(restaurant_reviews_df.user_id.isNotNull())

indexer = StringIndexer(inputCol="user_id", outputCol="user_index")
restaurant_reviews_df = indexer.fit(restaurant_reviews_df).transform(restaurant_reviews_df)
restaurant_reviews_df = restaurant_reviews_df.withColumn("user_index", restaurant_reviews_df["user_index"].cast(IntegerType()))

restaurant_reviews_city_df = restaurant_reviews_df.select(["business_index", "user_index", "business_id", "user_id", "stars"])
print(restaurant_reviews_city_df.first())

restaurant_reviews_city_df = restaurant_reviews_city_df.withColumn("stars", restaurant_reviews_city_df["stars"].cast(IntegerType()))

restaurant_reviews_city_df.toPandas().to_csv('mycsv.csv')

(training, test) = restaurant_reviews_city_df.randomSplit([0.8, 0.2], 123)

# als = ALS(maxIter=20, regParam=0.01, userCol="user_index", itemCol="business_index", ratingCol="stars",
#           coldStartStrategy="drop", rank=70).setSeed(123)
# model = als.fit(training)
# predictions = model.transform(test)
# # predictions.cache()
#
# evaluator = RegressionEvaluator(metricName="rmse", labelCol="stars", predictionCol="prediction")
# rmse = evaluator.evaluate(predictions)
# print(rmse)

als = ALS(userCol="user_index", itemCol="business_index", ratingCol="stars", coldStartStrategy="drop")
als.setSeed(123)
grid = ParamGridBuilder().addGrid(als.maxIter, [20]).addGrid(als.rank, [20,30,40,50,60,70]).addGrid(als.regParam, [0.45,0.5,0.55]).build()
evaluator = RegressionEvaluator(predictionCol=als.getPredictionCol(),labelCol=als.getRatingCol(), metricName='rmse')
cv = CrossValidator(estimator=als, estimatorParamMaps=grid, evaluator=evaluator, numFolds=5)
cvModel = cv.fit(training)

predictions = cvModel.transform(test)
predictions.cache()

print('########################## Computing RMSE ###########################')

rmse_evaluator = RegressionEvaluator(predictionCol='prediction',labelCol='stars', metricName='rmse')
rmse = rmse_evaluator.evaluate(predictions)
print(rmse)


import json

class Config:
    def __init__(self, properties, environment=None):
        self._properties = properties
        self._environment = environment
        if environment is not None:
            self._set_properties_environment(properties, environment)

    def __getitem__(self, key):
        return self._properties[key]

    def __contains__(self, key):
        return (key in self._properties)

    def __iter__(self):
        return iter(self._properties)

    def get_dict(self):
        return self._properties

    def get_environment(self):
        return self._environment

    def _set_properties_environment(self, properties, environment):
        if isinstance(properties, list):
            for i in range(len(properties)):
                if isinstance(properties[i], basestring):
                    properties[i] = self._set_str_environment(properties[i], environment)
                else:
                    self._set_properties_environment(properties[i], environment)
        elif isinstance(properties, dict):
            for key in properties.keys():
                item = properties[key]
                if isinstance(item, basestring):
                    properties[key] = self._set_str_environment(item, environment)
                else:
                    self._set_properties_environment(item, environment)

    def _set_str_environment(self, s, environment):
        for key in environment:
            s = s.replace("$!{" + key + "}", environment[key])
        return s

    def merge(self, other_config):
        environment = None
        if self._environment is not None or other_config._environment is not None:
            environment = dict()
            if self._environment is not None:
                for key in self._environment:
                    environment[key] = self._environment[key]
            if other_config._environment is not None:
                for key in other_config._environment:
                    environment[key] = other_config._environment[key]

        properties = dict()
        for key in self._properties:
            properties[key] = self._properties[key]
        for key in other_config._properties:
            properties[key] = other_config._properties[key]

        return Config(properties, environment=environment)

    @staticmethod
    def load(properties_file, environment=None):
        properties = None
        with open(properties_file, 'r') as fp:
            properties = json.load(fp)

        return Config(properties, environment=environment)

    @staticmethod
    def load_from_list(args_list, environment=None):
        properties = dict()

        if len(args_list) % 2 != 0:
            raise ValueError("Args list must be even length to parse into config")

        for i in range(len(args_list)/2):
            arg_name = args_list[i*2].replace("--", "").replace("-", "")
            arg_value = args_list[i*2+1]

            properties[arg_name] = arg_value

        return Config(properties, environment=environment)

    @staticmethod
    def load_from_dict(d, environment=None):
        properties = dict(d)
        return Config(properties, environment=environment)