def loadAndCompareProfiles(prof1, prof2):
    prof1_data = js.load_json(prof1)
    if prof1_data is None:
        print("Failed to load profile: {}".format(prof1))

    prof2_data = js.load_json(prof2)
    if prof2_data is None:
        print("Failed to load profile: {}".format(prof2))
def printPartitionSetInfo(partitionDir):
    """
    Given a directory containing all the json for a partition this function will print it out.
    """
    partitions = []
    partition_to_users = {}
    partition_type = ""
    
    for fname in os.listdir(partitionDir):
        (filebase, ext) = fname.rsplit('.',1)
        
        if ext == 'ditto':
            continue
        
        if fname == "user_map.json":
            data = js.load_json(os.path.join(partitionDir, fname))
            
            for user,partition in data.items():
                if partition not in partition_to_users:
                    partition_to_users[partition] = []
                    
                partition_to_users[partition].append(user)
            
            partition_type = data['partitioner']
            continue
        
        # Load the json
        fname = os.path.join(partitionDir,fname)
        try:
            data = js.load_json(fname)
        except Exception as e:
            print("Failed to load json: {}".format(fname))
            sys.exit(2)
        
        pnum = int(filebase)
        if len(partitions) <= pnum:
            partitions += [None] * (pnum - len(partitions) + 1)
        
        partitions[pnum] = data
        
    # Now print all of them
    print("\n---- Partitioner Type: {} ----\n".format(partition_type))
    for index,partition in enumerate(partitions):
        if partition is None:
            continue

        print("Partition Number {}".format(index))
        print("\tSource profile: {}".format(partition['source']))
        print("\tFly time #: {}".format(len(partition['fly_times'])))
        print("\tPress time #: {}". format(len(partition['press_times'])))
        print("\tWPM: {}".format(partition['wpm']))
        print("\tUsers in partition: {}".format(partition_to_users[index]))
def loadProfiles(*filepaths):
    profiles = {}

    user_name_re = re.compile("(.+?)_reduced.json")

    for path in filepaths:
        fname = os.path.basename(path)

        # Load the profile's json data
        try:
            prof_data = js.load_json(path)
        except Exception as e:
            print("Failed to load json: {}".format(e))
            sys.exit(-1)

        # Verify it has already been reduced
        print("Doing json format pre-check on {}".format(path))
        if not expectedJsonFormat(prof_data):
            print("Bad profile format: '{}'".format(path))
            print("The profile data likely hasn't been reduced yet")
            sys.exit(1)

        if 'prof_name' in prof_data:
            fname = prof_data['prof_name']
        else:
            m = user_name_re.search(fname)
            if m is None:
                print("Cannot create internal name for profile. Either use the format of the reducer or provide a 'prof_name' key in the profile")
                sys.exit(1)
            
            fname = m.group(1)
            
        profiles[fname] = prof_data

    return profiles
def printReducedProfileInfo(*profiles):
    for profile in profiles:
        if "user_map.json" in profile:
            continue

        all_data = js.load_json(profile)

        pdata = all_data
        wpm = 0
        if 'text' in all_data:
            if 'wpm' in pdata:
                wpm = pdata['wpm']

            pdata = all_data['text']
        
        fly_total_float = 0.0
        fly_total = num_fly = 0
        max_fly = -1
        (max_fly_from, max_fly_to) = (-1,-1)
        for from_key, to_keys in pdata['fly_times'].items():
            for to_key, time in to_keys.items():
                #if type(to_key) in (str,unicode) and to_key.endswith("_stdv"):
                #    continue
                if time > max_fly:
                    max_fly = time
                    max_fly_from = from_key
                    max_fly_to = to_key

                num_fly += 1
                fly_total_float += time
                fly_total += int(time)

                
        
        print("\n=======================\n")
        print("Profile Name: {}".format(profile))
        if 'username' in all_data and all_data['username'] is not None:
            print("User name: {}".format(all_data['username']))

        print("# collected fly: {} (unique pairs)".format(pdata['num_fly_times']))
        print("# collected press: {} (unique keys)".format(pdata['num_press_times']))
        print("# filtered fly: {} (range: {})".format(pdata['filtered_fly'], pdata['irq_range_fly']))
        print("# filtered press: {} (range: {})".format(pdata['filtered_press'], pdata['irq_range_press']))
        print("Mean fly time: {} ms".format(pdata['fly_mean']))
        print("Standard Dev. fly time: {} ms".format(pdata['fly_stdv']))
        print("Mean press time: {} ms".format(pdata['press_mean']))
        print("Standard Dev. press time: {} ms".format(pdata['press_stdv']))
        if wpm > 0:
            print("Words Per Minute: {}".format(wpm))
        print("Max fly time from {} -> {} = {} ms".format(max_fly_from, max_fly_to, max_fly))
        print("Max fly time as char {} -> {}".format(getCharFromJSCode(max_fly_from), getCharFromJSCode(max_fly_to)))
        print("Recalculated mean fly: {} ms".format(fly_total / num_fly))
        print("Recalculated mean fly (float): {} ms".format(fly_total_float / num_fly))
def compareDittoAndJson(ditto, profile):
    # First read in the ditto dprofile and create a python
    # dictionary
    ditto_as_map = {'fly_times':{}, 'press_times':{}}
    
    unitSize = sizeofStruct(FSProfileStruct)

    with open(ditto, "rb") as dprofile:
        sBytes = dprofile.read(unitSize)
        total_fly_times = []
        total_press_times = []
        while sBytes:
            struct = buildStructFromBytes(FSProfileStruct(), sBytes)
            if struct.time_type == FSProfileTimeType.FLY_TIME:
                from_key = JSCodeFromDittoSC(struct.from_key)
                to_key = JSCodeFromDittoSC(struct.to_key)
                if from_key not in ditto_as_map['fly_times']:
                    ditto_as_map['fly_times'][from_key] = {}

                ditto_as_map['fly_times'][from_key][to_key] = struct.time_in_ms
            elif struct.time_type == FSProfileTimeType.PRESS_TIME:
                key = JSCodeFromDittoSC(struct.from_key)
                ditto_as_map['press_times'][key] = struct.time_in_ms
            else:
                print("!! ---- Unrecognized FSProfileTimeType ( {} ) ---- !!".format(struct.time_type))

            sBytes = dprofile.read(unitSize)
                
    # Load the regular dprofile
    profile_data = js.load_json(profile)
    if profile_data is None:
        print("Failed to load dprofile " + profile)
        sys.exit(2)
        
    # Do the comparison
    compareProfiles(ditto_as_map, profile_data, timeToInt=True)
def json_to_ditto(in_profile, out_profile):
    if not out_profile.endswith(".ditto"):
        print("Warning: ditto profiles usually end with .ditto")

    json_profile = load_json(in_profile)
    ditto_profile = open(out_profile, "wb")

    if "text" in json_profile:
        json_profile = json_profile["text"]

    if (json_profile is None) or (ditto_profile is None):
        print("Failed to load required data(json: {}, out: {})".format(json_profile, ditto_profile))
        return False

    stdv_re = re.compile("^[0-9]+_stdv$")

    # print out info on the size of the input profile
    sz = 0
    for k, v in json_profile["fly_times"].items():
        for k2, v2 in v.items():
            if stdv_re.match(k2):
                continue

            sz += 1

    print("# fly times in input profile = " + str(sz))

    total_fly = total_press = 0
    fly_time_total = press_time_total = 0

    # When we can't map from the js to the ditto code for a key we throw it out. This can
    # have an impact on the overall fly and press times for a profile
    thrown_fly = []
    thrown_press = []

    num_stdv = 0

    unit = FSProfileStruct()
    unit.setTimeType(FSProfileTimeType.FLY_TIME)

    for from_key, to_key_info in json_profile["fly_times"].items():
        for to_key, time in to_key_info.items():
            if stdv_re.match(to_key):
                # We don't include any stdv info in ditto profiles right now
                num_stdv += 1
                continue

            mapped_key = DittoSCFromJSCode(int(from_key))
            if mapped_key < 0:
                unmapped_key_warn(from_key)
                thrown_fly.append(int(time))
                continue

            unit.setFromKey(mapped_key)

            mapped_key = DittoSCFromJSCode(int(to_key))
            if mapped_key < 0:
                unmapped_key_warn(to_key)
                thrown_fly.append(int(time))
                continue

            unit.setToKey(mapped_key)

            unit.setKeyTimeMs(int(time))

            writeStruct(ditto_profile, unit)
            total_fly += 1
            fly_time_total += int(time)

    ## Setup for adding press times
    unit.setTimeType(FSProfileTimeType.PRESS_TIME)

    for key, time in json_profile["press_times"].items():
        if stdv_re.match(key):
            # Once again, we don't include stdv right now
            num_stdv += 1
            continue

        # For press times Ditto just takes the 'from' key but to be safe
        # incase of future changes I just set both to be the same
        mapped_key = DittoSCFromJSCode(int(key))
        if mapped_key < 0:
            # They will already get the warning in fly times
            # unmapped_key_warn(key)
            thrown_press.append(int(time))
            continue

        unit.setFromKey(mapped_key)
        unit.setToKey(mapped_key)

        unit.setKeyTimeMs(int(time))

        writeStruct(ditto_profile, unit)
        total_press += 1
        press_time_total += int(time)

    ditto_profile.close()

    print("\nFinished converting source profile '{}' to ditto profile '{}'".format(in_profile, out_profile))
    print("\t# Fly Times: {}".format(total_fly))
    print("\t# Press Times: {}".format(total_press))
    print("\tFly time average: {}".format(fly_time_total / total_fly))
    print("\tPress time average: {}".format(press_time_total / total_press))
    if len(thrown_fly) > 0:
        print("\tAverage of unmapped fly times: {}".format(sum(thrown_fly) / len(thrown_fly)))
    if len(thrown_press) > 0:
        print("\tAverage of unmapped press times: {}".format(sum(thrown_press) / len(thrown_press)))
    print("\tIgnored {} standard deviation entries".format(num_stdv))