__author__ = 'Tom Van den Eede'
__copyright__ = 'Copyright 2018-2021, Palette2 Splicer Post Processing Project'
__credits__ = ['Tom Van den Eede',
               'Tim Brookman'
               ]
__license__ = 'GPLv3'
__maintainer__ = 'Tom Van den Eede'
__email__ = 'P2PP@pandora.be'

import os
import time
import sys

import p2pp.gcode as gcode
import p2pp.gui as gui
import p2pp.pings as pings
import p2pp.purgetower as purgetower
import p2pp.variables as v
from p2pp.psconfig import parse_prusaslicer_config
from p2pp.omega import header_generate_omega, header_generate_omega_palette3
from p2pp.sidewipe import create_side_wipe
import p2pp.manualswap as swap
import p2pp.bedprojection as bp
import base64
import version
import zipfile

# GCODE BLOCK CLASSES
CLS_UNDEFINED = 0
CLS_NORMAL = 1
CLS_TOOL_START = 2
CLS_TOOL_UNLOAD = 4
CLS_TOOL_PURGE = 8
CLS_EMPTY = 16
CLS_BRIM = 32
CLS_BRIM_END = 64
CLS_ENDGRID = 128
CLS_COMMENT = 256
CLS_ENDPURGE = 512
CLS_TOOLCOMMAND = 1024

# HASH CODES FOR KEY COMMENTS
hash_FIRST_LAYER_BRIM_START = hash("WIPE TOWER FIRST LAYER BRIM START")
hash_FIRST_LAYER_BRIM_END = hash("WIPE TOWER FIRST LAYER BRIM END")
hash_EMPTY_GRID_START = hash("EMPTY GRID START")
hash_EMPTY_GRID_END = hash("EMPTY GRID END")
hash_TOOLCHANGE_START = hash("TOOLCHANGE START")
hash_TOOLCHANGE_UNLOAD = hash("TOOLCHANGE UNLOAD")
hash_TOOLCHANGE_LOAD = hash("TOOLCHANGE LOAD")
hash_TOOLCHANGE_WIPE = hash("TOOLCHANGE WIPE")
hash_TOOLCHANGE_END = hash("TOOLCHANGE END")

#  delta tower strategy: try to delay the delta as long as possible to minimize the extra print time

def optimize_tower_skip(max_layers):
    skippable = v.skippable_layer.count(True)

    idx = 0
    while skippable > max_layers:
        if v.skippable_layer[idx]:
            v.skippable_layer[idx] = False

            skippable -= 1
        idx += 1

    return skippable

def gcode_process_toolchange(new_tool):
    if new_tool == v.current_tool:
        return

    location = v.total_material_extruded + v.splice_offset

    if new_tool == -1:      # LAST SLICE PROCESSING
        location += v.extra_runout_filament
        v.material_extruded_per_color[v.current_tool] += v.extra_runout_filament
        v.total_material_extruded += v.extra_runout_filament
    else:
        v.palette_inputs_used[new_tool] = True

    length = location - v.previous_toolchange_location

    if v.current_tool != -1:  # FIRST SLICE PROCESSING

        v.splice_extruder_position.append(location)
        v.splice_length.append(length)
        v.splice_used_tool.append(v.current_tool)

        if len(v.splice_extruder_position) == 1:
            min_length = v.min_start_splice_length
            gui_format = "SHORT FIRST SPLICE (min {}mm) Length:{:-3.2f} Input {}"
        else:
            min_length = v.min_splice_length
            gui_format = "SHORT SPLICE (min {}mm) Length:{:-3.2f} Layer:{} Input:{}"

        if v.splice_length[-1] < min_length:
            if v.autoaddsplice and (v.full_purge_reduction or v.side_wipe):
                v.autoadded_purge = v.min_start_splice_length - length
                v.side_wipe_length += v.autoadded_purge
                v.splice_extruder_position[-1] += v.autoadded_purge * v.extrusion_multiplier
                v.splice_length[-1] += v.autoadded_purge
            else:
                gui.log_warning(gui_format.format(min_length, length, v.last_parsed_layer, v.current_tool + 1))
                v.filament_short[new_tool] = max(v.filament_short[new_tool],
                                                 v.min_start_splice_length - v.splice_length[-1])

        v.previous_toolchange_location = v.splice_extruder_position[-1]

    v.previous_tool = v.current_tool
    v.current_tool = new_tool

def calculate_temp_wait_position():

    pos_x = v.wipe_tower_info_minx + v.tx_offset * 1 if abs(v.wipe_tower_info_minx - v.purge_keep_x) < abs(v.wipe_tower_info_maxx - v.purge_keep_x) else -1
    pos_y = v.wipe_tower_info_miny + v.ty_offset * 1 if abs(v.wipe_tower_info_miny - v.purge_keep_y) < abs(v.wipe_tower_info_maxy - v.purge_keep_y) else -1
    return [pos_x, pos_y]

def entertower(layer_hght):

    purgeheight = layer_hght - v.cur_tower_z_delta

    if v.current_position_z != purgeheight:
        v.max_tower_delta = max(v.cur_tower_z_delta, v.max_tower_delta)
        gcode.issue_code(";------------------------------", True)
        gcode.issue_code(";  P2PP DELTA ENTER", True)
        gcode.issue_code(";  Current printing Z = {:.2f}".format(v.current_position_z), True)
        gcode.issue_code(";  Tower Z = {:.2f}".format(purgeheight), True)
        gcode.issue_code(";  Delta = {:.2f} ".format(v.current_position_z - purgeheight), True)
        gcode.issue_code(";------------------------------", True)

        if v.retraction >= 0:
            purgetower.retract(v.current_tool)

        if v.manual_filament_swap:
            swap.swap_pause("M25")
            # unpause z-move is not required

        gcode.issue_code("G1 X{} Y{} F8640".format(v.current_position_x, v.current_position_y))
        gcode.issue_code("G1 Z{:.2f} F10810".format(purgeheight))

        if purgeheight <= (v.first_layer_height+0.02):  # FIRST LAYER PURGES SLOWER
            gcode.issue_code("G1 F{}".format(min(1200, v.wipe_feedrate)))
        else:
            gcode.issue_code("G1 F{}".format(v.wipe_feedrate))

        v.disable_z = True

def update_class(line_hash):

    if line_hash == hash_EMPTY_GRID_START:
        v.block_classification = CLS_EMPTY
        v.layer_emptygrid_counter += 1

    elif line_hash == hash_EMPTY_GRID_END:
        v.block_classification = CLS_ENDGRID

    elif line_hash == hash_TOOLCHANGE_START:
        v.block_classification = CLS_TOOL_START
        v.layer_toolchange_counter += 1

    elif line_hash == hash_TOOLCHANGE_UNLOAD:
        v.block_classification = CLS_TOOL_UNLOAD

    elif line_hash == hash_TOOLCHANGE_LOAD:
        v.block_classification = CLS_TOOL_UNLOAD

    elif line_hash == hash_TOOLCHANGE_WIPE:
        v.block_classification = CLS_TOOL_PURGE

    elif line_hash == hash_TOOLCHANGE_END:
        v.block_classification = CLS_ENDPURGE

    elif line_hash == hash_FIRST_LAYER_BRIM_START:
        v.block_classification = CLS_BRIM
        v.tower_measure = True

    elif line_hash == hash_FIRST_LAYER_BRIM_END:
        v.block_classification = CLS_BRIM_END
        v.tower_measure = False
        v.wipe_tower_xsize = v.wipe_tower_info_maxx - v.wipe_tower_info_minx
        v.wipe_tower_ysize = v.wipe_tower_info_maxy - v.wipe_tower_info_miny

def process_layer(layer, index):
    if layer == v.last_parsed_layer:
        return
    v.last_parsed_layer = layer
    v.layer_end.append(index)
    if layer >= 0:
        v.skippable_layer.append((v.layer_emptygrid_counter > 0) and (v.layer_toolchange_counter == 0))

    v.layer_toolchange_counter = 0
    v.layer_emptygrid_counter = 0

def speed_limiter ( g_code ):
    if g_code[gcode.F] is not None and g_code[gcode.EXTRUDE] and g_code[gcode.F] > v.wipe_feedrate:
        g_code[gcode.COMMENT] = ";-- SLOW DOWN {} --> {}--;".format(g_code[gcode.F], v.wipe_feedrate)
        g_code[gcode.F] = v.wipe_feedrate

def parse_gcode():

    v.layer_toolchange_counter = 0
    v.layer_emptygrid_counter = 0

    v.block_classification = CLS_NORMAL
    v.previous_block_classification = CLS_NORMAL
    total_line_count = len(v.input_gcode)

    flh = int(v.first_layer_height * 100)
    olh = int(v.layer_height * 100)

    backpass_line = -1
    jndex = 0
    side_wipe_towerdefined = False

    for index in range(total_line_count):

        v.previous_block_classification = v.block_classification

        # memory management, reduce size of data structures when data is processed
        line = v.input_gcode[jndex]
        jndex += 1

        if jndex == 100000:
            gui.progress_string(4 + 46 * index // total_line_count)
            v.input_gcode = v.input_gcode[jndex:]
            jndex = 0

        # actual line processing
        if line.startswith(';'):

            is_comment = True

            # following lines should only be tested at the beginning og the file before the first layer is executed
            if not v.thumbnail_end:

                if line.startswith("; thumbnail end"):
                    v.thumbnail = False
                    v.thumbnail_end = True
                    v.thumbnail_data = v.thumbnail_data.replace("; ","")

                if v.thumbnail:
                    v.thumbnail_data += line

                if line.startswith("; thumbnail begin"):
                    v.thumbnail = True

            if line.startswith('; CP'):  # code block assignment
                update_class(hash(line[5:]))

            elif line.startswith(';LAYERHEIGHT'):  # Layer instructions
                fields = line.split(' ')
                try:
                    lv = float(fields[1])
                    lv = int((lv + 0.001) * 100) - flh
                    if lv % olh == 0:
                        process_layer(int(lv / olh), index)
                except (ValueError, IndexError):
                    pass

        else:
            is_comment = False
            try:
                if line[0] == 'T':
                    if v.set_tool == -1:
                        v.block_classification = CLS_NORMAL
                    else:
                        v.block_classification = CLS_TOOL_PURGE
                    cur_tool = int(line[1])
                    v.set_tool = cur_tool
            except (TypeError, IndexError, ValueError):
                pass

        code = gcode.create_command(line, is_comment, v.block_classification)
        v.parsed_gcode.append(code)

        if v.block_classification != v.previous_block_classification:

            if v.block_classification in [CLS_TOOL_START, CLS_TOOL_UNLOAD, CLS_EMPTY, CLS_BRIM]:
                for idx in range(backpass_line, len(v.parsed_gcode)):
                    v.parsed_gcode[idx][gcode.CLASS] = v.block_classification

        # determine tower size
        if v.tower_measure:
            if code[gcode.X]:
                v.wipe_tower_info_minx = min(v.wipe_tower_info_minx, code[gcode.X] - 2 * v.extrusion_width)
                v.wipe_tower_info_maxx = max(v.wipe_tower_info_maxx, code[gcode.X] + 2 * v.extrusion_width)
            if code[gcode.Y]:
                v.wipe_tower_info_miny = min(v.wipe_tower_info_miny, code[gcode.Y] - 4 * 2 * v.extrusion_width)
                v.wipe_tower_info_maxy = max(v.wipe_tower_info_maxy, code[gcode.Y] + 4 * 2 * v.extrusion_width)

        if v.bedtrace:
            if (code[gcode.MOVEMENT] & (gcode.X + gcode.Y)) and v.bed is not None:
                if code[gcode.EXTRUDE]:
                    v.bed.line(code[gcode.X], code[gcode.Y])
                else:
                    v.bed.position(code[gcode.X], code[gcode.Y])

        # determine block separators by looking at the last full XY positioning move
        if (code[gcode.MOVEMENT] & 3) == 3:
            if (code[gcode.MOVEMENT] & 12) == 0:
                backpass_line = len(v.parsed_gcode) - 1

            # add
            if side_wipe_towerdefined:
                if ((v.wipe_tower_info_minx <= code[gcode.X] <= v.wipe_tower_info_maxx) and
                   (v.wipe_tower_info_miny <= code[gcode.Y] <= v.wipe_tower_info_maxy)):
                    code[gcode.MOVEMENT] += gcode.INTOWER

            if v.block_classification in [CLS_ENDGRID, CLS_ENDPURGE]:
                if not (code[gcode.MOVEMENT] & gcode.INTOWER):
                    v.parsed_gcode[-1][gcode.CLASS] = CLS_NORMAL
                    v.block_classification = CLS_NORMAL

        if v.block_classification == CLS_BRIM_END:
            v.block_classification = CLS_NORMAL
            side_wipe_towerdefined = True

    v.input_gcode = []

def gcode_parselines():

    idx = 0
    purge = False
    total_line_count = len(v.parsed_gcode)
    v.retraction = 0
    v.last_parsed_layer = 0
    v.previous_block_classification = v.parsed_gcode[0][gcode.CLASS]

    for process_line_count in range(total_line_count):

        try:
            if process_line_count >= v.layer_end[0]:
                v.last_parsed_layer += 1
                v.layer_end.pop(0)
                v.current_layer_is_skippable = v.skippable_layer[v.last_parsed_layer]
                if v.current_layer_is_skippable:
                    if v.last_parsed_layer == 0:
                        v.cur_tower_z_delta += v.first_layer_height
                    else:
                        v.cur_tower_z_delta += v.layer_height
        except IndexError:
            pass

        g = v.parsed_gcode[idx]

        idx = idx + 1

        # ----- MEMORY MANAGEMENT - when 10K lines are processed, remove the top of the list

        if idx > 100000:
            v.parsed_gcode = v.parsed_gcode[idx:]
            idx = 0

        if process_line_count % 10000 == 0:
            gui.progress_string(50 + 50 * process_line_count // total_line_count)

        current_block_class = g[gcode.CLASS]

        # ---- FIRST SECTION HANDLES DELAYED TEMPERATURE COMMANDS ----

        if current_block_class not in [CLS_TOOL_PURGE, CLS_TOOL_START, CLS_TOOL_UNLOAD] and v.current_temp != v.new_temp:
            gcode.issue_code(v.temp1_stored_command)
            v.temp1_stored_command = ""

        # ---- SECOND SECTION HANDLES COMMENTS AND NONE-MOVEMENT COMMANDS ----

        if g[gcode.COMMAND] is None:
            if v.disable_z and g[gcode.COMMENT].endswith("END"):
                v.disable_z = False

            if v.needpurgetower and g[gcode.COMMENT].endswith("BRIM END"):
                v.needpurgetower = False
                purgetower.purge_create_layers(v.wipe_tower_info_minx, v.wipe_tower_info_miny, v.wipe_tower_xsize,
                                               v.wipe_tower_ysize)
                purgetower.purge_generate_brim()
                v.toolchange_processed = False
            gcode.issue_command(g)
            continue

        elif g[gcode.MOVEMENT] == 0:

            if g[gcode.COMMAND].startswith('T'):

                if v.manual_filament_swap and not (v.side_wipe or v.full_purge_reduction or v.tower_delta) and (v.current_tool != -1):
                    swap.swap_pause("M25")
                    swap.swap_unpause()

                gcode_process_toolchange(int(g[gcode.COMMAND][1:]))
                if not v.debug_leaveToolCommands:
                    gcode.move_to_comment(g, "--P2PP-- Color Change")
                    v.toolchange_processed = (current_block_class != CLS_NORMAL)

            elif v.klipper and g[gcode.COMMAND] == "ACTIVATE_EXTRUDER":
                extruder = g[gcode.OTHER]

                if extruder.startswith(" EXTRUDER=extruder"):

                    try:
                        extruder_num = int(extruder[18])
                    except ValueError:
                        extruder_num = None
                    except IndexError:
                        extruder_num = 0

                    if extruder_num is not None:
                        gcode_process_toolchange(extruder_num)

                    if not v.debug_leaveToolCommands:
                        gcode.move_to_comment(g, "--P2PP-- Color Change")
                        v.toolchange_processed = True
            else:
                if current_block_class == CLS_TOOL_UNLOAD:
                    if g[gcode.COMMAND] in ["G4", "M900"]:
                        gcode.move_to_comment(g, "--P2PP-- tool unload")

                if g[gcode.COMMAND] is not None and g[gcode.COMMAND].startswith('M'):
                    try:
                        commandNum = int(g[gcode.COMMAND][1:])
                    except (ValueError, KeyError):
                        commandNum = 0
                        pass

                    if commandNum in [104, 109]:
                        if v.process_temp:
                            if current_block_class not in [CLS_TOOL_PURGE, CLS_TOOL_START,
                                                           CLS_TOOL_UNLOAD]:
                                g[gcode.COMMENT] += " Unprocessed temp "
                                v.new_temp = gcode.get_parameter(g, gcode.S, v.current_temp)
                                v.current_temp = v.new_temp
                            else:
                                v.new_temp = gcode.get_parameter(g, gcode.S, v.current_temp)
                                if v.new_temp >= v.current_temp:
                                    g[gcode.COMMAND] = "M109"
                                    v.temp2_stored_command = gcode.create_commandstring(g)
                                    gcode.move_to_comment(g,
                                                          "--P2PP-- delayed temp rise until after purge {}-->{}".format(v.current_temp,
                                                                                v.new_temp))
                                    v.current_temp = v.new_temp

                                else:
                                    v.temp1_stored_command = gcode.create_commandstring(g)
                                    gcode.move_to_comment(g,
                                                          "--P2PP-- delayed temp drop until after purge {}-->{}".format(v.current_temp,
                                                                                v.new_temp))
                    elif commandNum == 107:
                        v.saved_fanspeed = 0

                    elif commandNum == 106:
                        v.saved_fanspeed = gcode.get_parameter(g, gcode.S, v.saved_fanspeed)

                    elif commandNum == 221:
                        v.extrusion_multiplier = float(gcode.get_parameter(g, gcode.S, v.extrusion_multiplier * 100.0)) / 100.0

                    elif commandNum == 220:
                        gcode.move_to_comment(g, "--P2PP-- Feed Rate Adjustments are removed")

                    elif commandNum == 572:
                        for i in range(1, v.filament_count):
                            g[gcode.OTHER] = g[gcode.OTHER].replace("D{}".format(i), "D0")

                    elif not v.generate_M0 and g[gcode.COMMAND] == "M0":
                        gcode.move_to_comment(g, "--P2PP-- remove M0 command")

            gcode.issue_command(g)
            continue

        # ---- AS OF HERE ONLY MOVEMENT COMMANDS ----

        classupdate = current_block_class != v.previous_block_classification
        v.previous_block_classification = current_block_class

        if g[gcode.MOVEMENT] & 1:
            v.previous_purge_keep_x = v.purge_keep_x
            v.purge_keep_x = g[gcode.X]
            v.current_position_x = g[gcode.X]

        if g[gcode.MOVEMENT] & 2:
            v.previous_purge_keep_y = v.purge_keep_y
            v.purge_keep_y = g[gcode.Y]
            v.current_position_y = g[gcode.Y]

        if g[gcode.MOVEMENT] & 4:
            if v.disable_z:
                gcode.move_to_comment(g, "-- P2PP - invalid move in delta tower")
                gcode.issue_command(g)
                continue
            else:
                v.current_position_z = g[gcode.Z]

        if g[gcode.MOVEMENT] & 16:
            v.keep_speed = g[gcode.F]

        # this goes for all situations: START and UNLOAD are not needed
        if current_block_class in [CLS_TOOL_START, CLS_TOOL_UNLOAD]:
            gcode.move_to_comment(g, "--P2PP-- tool unload")
            gcode.issue_command(g)
            continue

        # --------------------- TOWER DELTA PROCESSING
        if v.tower_delta:

            if classupdate:

                if current_block_class == CLS_TOOL_PURGE:
                    gcode.issue_command(g)
                    entertower(v.last_parsed_layer * v.layer_height + v.first_layer_height)
                    continue

                if current_block_class == CLS_EMPTY and not v.towerskipped:
                    v.towerskipped = (g[gcode.MOVEMENT] & gcode.INTOWER) == gcode.INTOWER and v.current_layer_is_skippable

                    if not v.towerskipped:
                        gcode.issue_command(g)
                        entertower(v.last_parsed_layer * v.layer_height + v.first_layer_height)
                        continue

                if current_block_class == CLS_NORMAL:
                    if v.towerskipped:
                        gcode.issue_code("G1 Z{:.2f} F10810".format(v.current_position_z))
                        v.towerskipped = False

            if current_block_class == CLS_TOOL_PURGE:
                speed_limiter(g)

            if current_block_class == CLS_TOOL_PURGE:
                if g[gcode.F] is not None and g[gcode.F] > v.purgetopspeed and g[gcode.E]:
                    g[gcode.F] = v.purgetopspeed
                    g[gcode.COMMENT] += " prugespeed topped"

            if v.towerskipped:
                gcode.move_to_comment(g, "--P2PP-- tower skipped")
                gcode.issue_command(g)
                continue
        # --------------------- SIDE WIPE PROCESSING
        elif v.side_wipe:

            if classupdate:

                if current_block_class == CLS_BRIM:
                    if v.bigbrain3d_purge_enabled:
                        create_side_wipe(v.bigbrain3d_prime * v.bigbrain3d_blob_size)
                    v.towerskipped = True
                    v.side_wipe_state = 0

            if not v.towerskipped and (g[gcode.MOVEMENT] & 3) == 3:
                if (g[gcode.MOVEMENT] & gcode.INTOWER) == gcode.INTOWER:
                    v.towerskipped = True
                    v.side_wipe_state = 1 if (current_block_class == CLS_TOOL_PURGE) else 0

            if v.towerskipped and current_block_class == CLS_NORMAL and (g[gcode.MOVEMENT] & 3) == 3:
                if (v.bed_origin_x <= g[gcode.X] <= v.bed_max_x) and (v.bed_origin_y <= g[gcode.Y] <= v.bed_max_y):
                    v.towerskipped = False
                    v.side_wipe_state = 0
                    if v.toolchange_processed and v.side_wipe_length:
                        create_side_wipe()
                        v.toolchange_processed = False

            if v.towerskipped:
                inc = "NO_E"
                if current_block_class in [CLS_TOOL_PURGE, CLS_ENDPURGE] or (current_block_class == CLS_EMPTY and v.side_wipe_state == 1):
                    if g[gcode.EXTRUDE]:
                        v.side_wipe_length += g[gcode.E]
                        inc = "INC_E"

                gcode.move_to_comment(g, "--P2PP-- side wipe skipped ({})".format(inc))
                gcode.issue_command(g)
                continue

        # --------------------- FULL PURGE PROCESSING
        elif v.full_purge_reduction:

            if classupdate:

                if current_block_class == CLS_NORMAL:
                    v.towerskipped = False
                    purge = False

                if current_block_class == CLS_TOOL_PURGE:
                    purge = True

            if not v.towerskipped and current_block_class == CLS_EMPTY and v.current_layer_is_skippable:
                v.towerskipped = (g[gcode.MOVEMENT] & gcode.INTOWER) == gcode.INTOWER

            if v.towerskipped or current_block_class in [CLS_BRIM, CLS_ENDGRID, CLS_EMPTY]:
                gcode.move_to_comment(g, "--P2PP-- full purge skipped")
                gcode.issue_command(g)
                continue

            if current_block_class in [CLS_TOOL_PURGE, CLS_ENDPURGE, CLS_EMPTY]:
                if purge:
                    if g[gcode.EXTRUDE]:
                        v.side_wipe_length += g[gcode.E]
                    gcode.move_to_comment(g, "--P2PP-- full purge skipped ")
                gcode.issue_command(g)
                continue

            if v.toolchange_processed and current_block_class == CLS_NORMAL:
                if v.side_wipe_length and (g[gcode.MOVEMENT] & 3) == 3 and not (g[gcode.MOVEMENT] & gcode.INTOWER) == gcode.INTOWER:
                    purgetower.purge_generate_sequence()
                    v.toolchange_processed = False
                    # do not issue code here as the next code might require further processing such as retractioncorrection
                else:
                    gcode.move_to_comment(g, "--P2PP-- full purge skipped")
                    gcode.issue_command(g)
                    continue

            if v.expect_retract and (g[gcode.MOVEMENT] & 3):
                v.expect_retract = False
                if v.retraction >= 0 and g[gcode.RETRACT]:
                    purgetower.retract(v.current_tool)

            if v.retract_move and g[gcode.RETRACT]:
                g[gcode.X] = v.retract_x
                g[gcode.Y] = v.retract_y
                g[gcode.MOVEMENT] |= 3
                v.retract_move = False

                if v.retraction <= - v.retract_length[v.current_tool]:
                    gcode.move_to_comment(g, "--P2PP-- Double Retract")
                else:
                    v.retraction += g[gcode.E]

        # --------------------- NO TOWER PROCESSING
        else:

            if classupdate:

                if current_block_class in [CLS_TOOL_PURGE, CLS_EMPTY]:
                    if v.acc_ping_left <= 0:
                        pings.check_accessorymode_first()
                    v.enterpurge = True

            # TOEE - Added to limit the speed of the extrusions during purge to defined WIPEFEEDRATE
            if current_block_class == CLS_TOOL_PURGE:
                speed_limiter(g)

            if v.toolchange_processed:

                if v.temp2_stored_command != "":
                    wait_location = calculate_temp_wait_position()
                    gcode.issue_code(
                        "G1 X{:.3f} Y{:.3f} F8640; temp wait position\n".format(wait_location[0], wait_location[0]))
                    gcode.issue_code(v.temp2_stored_command)
                    v.temp2_stored_command = ""

                gcode.issue_code("G1 F8640 ; correct speed")
                gcode.issue_command(g)
                gcode.issue_code("G1 Z{} F10800 ;P2PP correct z-moves".format(v.current_position_z))

                v.toolchange_processed = False
                continue

            if current_block_class == CLS_TOOL_PURGE:
                if g[gcode.F] is not None and g[gcode.F] > v.purgetopspeed and g[gcode.E]:
                    g[gcode.F] = v.purgetopspeed
                    g[gcode.COMMENT] += " prugespeed topped"

        # --------------------- GLOBAL PROCEDDING

        if g[gcode.UNRETRACT]:
            g[gcode.E] = min(-v.retraction, g[gcode.E])
            v.retraction += g[gcode.E]
        elif g[gcode.RETRACT]:
            v.retraction += g[gcode.E]
        elif (g[gcode.MOVEMENT] & 3) and g[gcode.EXTRUDE] and v.retraction < -0.01:
            purgetower.unretract(v.current_tool, -1, ";--- P2PP --- fixup retracts")

        # --------------------- PING PROCESSING

        if v.accessory_mode and g[gcode.EXTRUDE]:
            if not pings.check_accessorymode_second(g[gcode.E]):
                gcode.issue_command(g)
        else:
            gcode.issue_command(g)
            if g[gcode.EXTRUDE] and v.side_wipe_length == 0:
                pings.check_connected_ping()

        v.previous_position_x = v.current_position_x
        v.previous_position_y = v.current_position_y

    # LAST STEP IS ADDING AN EXTRA TOOL UNLOAD TO DETERMINE THE LENGTH OF THE LAST SPLICE
    gcode_process_toolchange(-1)

# -- MAIN ROUTINE --- GLUES ALL THE PROCESSING ROUTINED
# -- FILE READING / FIRST PASS / SECOND PASS / FILE WRITING

def generate(input_file, output_file):

    starttime = time.time()
    basename = os.path.basename(input_file)
    _taskName = os.path.splitext(basename)[0].replace(" ", "_")
    _taskName = _taskName.replace(".mcf", "")
    gui.setfilename(input_file)
    gui.app.sync()
    try:
        # python 3.x
        # noinspection PyArgumentList
        opf = open(input_file, encoding='utf-8')
    except TypeError:
        try:
            # python 2.x
            opf = open(input_file)
        except IOError:
            gui.log_warning("Error Reading:'{}'".format(input_file))
            return
    except IOError:
        gui.log_warning("Error Reading: '{}'".format(input_file))
        return

    gui.create_logitem("Reading File " + input_file)
    gui.progress_string(1)
    v.input_gcode = opf.readlines()
    opf.close()

    v.input_gcode = [item.strip() for item in v.input_gcode]

    gui.create_logitem("Analyzing Prusa Slicer Configuration")
    gui.progress_string(2)
    parse_prusaslicer_config()

    if v.save_unprocessed:
        pre, ext = os.path.splitext(input_file)
        of = pre + "_unprocessed" + ext
        gui.create_logitem("Saving unpocessed code to: " + of)
        opf = open(of, "wb")
        for line in v.input_gcode:
            opf.write(line.encode('utf8'))
            opf.write("\n".encode('utf8'))
        opf.close()

    # added for issue Error: float division by zero (#87)
    if v.extrusion_width == 0:
        gui.create_logitem("Extrusionwidth set to 0, defaulted back to 0.45")
        v.extrusion_width = 0.45

    # v.bed = bp.BedProjection(int(v.bed_size_x), int(v.bed_size_y))
    gui.create_logitem("Analyzing Layers / Functional blocks")
    gui.progress_string(4)
    parse_gcode()
    # if v.bedtrace:
    #     v.bed.save_image()
    v.input_gcode = None

    if v.bed_size_x == -9999 or v.bed_size_y == -9999 or v.bed_origin_x == -9999 or v.bed_origin_y == -9999:
        gui.log_warning("Bedsize nor or incorrectly defined.")
    else:
        if v.bed_shape_rect and v.bed_shape_warning:
            gui.create_logitem("Manual bed size override, PrusaSlicer Bedshape configuration ignored.")
        gui.create_logitem("Bed origin ({:3.1f}mm, {:3.1f}mm)".format(v.bed_origin_x, v.bed_origin_y))
        gui.create_logitem("Bed size   ({:3.1f}mm, {:3.1f}mm)".format(v.bed_size_x, v.bed_size_y))

    v.bed_max_x = v.bed_origin_x + v.bed_size_x
    v.bed_max_y = v.bed_origin_y + v.bed_size_y

    if v.process_temp and v.side_wipe:
        gui.log_warning("TEMPERATURECONTROL and SIDEWIPE / BigBrain3D are incompatible")

    if v.palette_plus:
        if v.palette_plus_ppm == -9:
            gui.log_warning("P+ parameter P+PPM incorrectly set up in startup GCODE")
        if v.palette_plus_loading_offset == -9:
            gui.log_warning("P+ parameter P+LOADINGOFFSET incorrectly set up in startup GCODE")

    v.side_wipe = not ((v.bed_origin_x <= v.wipetower_posx <= v.bed_max_x) and (v.bed_origin_y <= v.wipetower_posy <= v.bed_max_y))
    v.tower_delta = v.max_tower_z_delta > 0

    if (v.tower_delta or v.full_purge_reduction) and v.variable_layer:
        gui.log_warning("Variable layers may cause issues with FULLPURGE / TOWER DELTA")
        gui.log_warning("This warning could be caused by support that will print on variable layer offsets")

    if v.side_wipe:

        if v.skirts:
            if v.ps_version >= "2.2":
                gui.log_warning("SIDEWIPE and SKIRTS are NOT compatible in PS2.2 or later")
            if v.full_purge_reduction:
                gui.log_warning("FULLPURGE and SKIRTS are NOT compatible.  Overlaps may occur")

        if v.wipe_remove_sparse_layers:
            gui.log_warning("SIDE WIPE mode not compatible with sparse wipe tower in PS")
            gui.log_warning("Use Tower Delta instead")

        gui.create_logitem("Side wipe activated", "blue")

        if v.full_purge_reduction:
            gui.log_warning("FULLURGEREDUCTION is incompatible with SIDEWIPE, parameter ignored")
            v.full_purge_reduction = False

    if v.full_purge_reduction:

        if v.tower_delta:
            gui.log_warning("FULLPURGEREDUCTION is incompatible with TOWERDELTA")
            v.tower_delta = False
        gui.create_logitem("FULLPURGEREDUCTION activated", "blue")

    if v.autoaddsplice and not v.full_purge_reduction and not v.side_wipe:
        gui.log_warning("AUTOADDPURGE only works with SIDEWIPE and FULLPURGEREDUCTION")

    if len(v.skippable_layer) == 0:
        gui.log_warning("LAYER configuration is missing.")
        gui.close_button_enable()
        sys.exit(-1)
    else:
        skippable = optimize_tower_skip(int(v.max_tower_z_delta / v.layer_height))
        if v.tower_delta:
            v.skippable_layer[0] = False
            if skippable > 0:
                gui.log_warning("TOWERDELTA in effect for {} Layers or {:.2f}mm".format(skippable, skippable * v.layer_height))
            else:
                gui.create_logitem("TOWERDELTA could not be applied to this print")

        gui.create_logitem("Generate processed GCode")
        gcode_parselines()
        v.processtime = time.time() - starttime
        omega_result = header_generate_omega(_taskName)
        header = omega_result['header'] + omega_result['summary'] + omega_result['warnings']

        # write the output file
        ######################

        if output_file is None:
            output_file = input_file

        path, _ = os.path.split(output_file)

        if v.palette3:
            output_file = os.path.join(path, "print.gcode")
            pre, ext = os.path.splitext(input_file)
            mcfx = pre + ".mcfx"

        gui.create_logitem("Generating GCODE file: " + output_file)
        opf = open(output_file, "wb")
        if not v.accessory_mode and not v.palette3:
            for line in header:
                opf.write(line.encode('utf8'))
            opf.write(("\n\n;--------- THIS CODE HAS BEEN PROCESSED BY P2PP v{} --- \n\n".format(version.Version)).encode('utf8'))
            if v.generate_M0:
                header.append("M0\n")
            opf.write("T0\n".encode('utf8'))
        else:
            opf.write(("\n\n;--------- THIS CODE HAS BEEN PROCESSED BY P2PP v{} --- \n\n".format(version.Version)).encode('utf8'))

        if v.splice_offset == 0:
            gui.log_warning("SPLICE_OFFSET not defined")
        for line in v.processed_gcode:
            try:
                opf.write(line.encode('utf8'))
            except IOError:
                gui.log_warning("Line : {} could not be written to output".format(line))
            opf.write("\n".encode('utf8'))
        opf.close()

        if v.palette3:

            # generate meta.json
            # generate palette.json
            # generate thumbnail
            # generate zip
            meta, palette = header_generate_omega_palette3(None)

            meta_file = os.path.join(path , "meta.json")
            palette_file = os.path.join(path , "palette.json")
            im_file = os.path.join(path, "thumbnail.png")

            gui.create_logitem("Generating Palette 3 output files")
            mf = open(meta_file, 'wb')
            mf.write(meta.__str__().encode('ascii'))
            mf.close()

            pa = open(palette_file, 'wb')
            pa.write(palette.__str__().encode('ascii'))
            pa.close()

            im = open(im_file, "wb")
            im.write(base64.b64decode(v.thumbnail_data))
            im.close()

            zipf = zipfile.ZipFile(mcfx, 'w', zipfile.ZIP_DEFLATED)
            zipf.write(meta_file , "meta.json")
            zipf.write(palette_file, "palette.json")
            zipf.write(output_file, "print.gcode")
            zipf.write(im_file, "thumbnail.png")

            os.remove(meta_file)
            os.remove(palette_file)
            os.remove(output_file)
            os.remove(im_file)
            zipf.close()

        if v.accessory_mode:

            pre, ext = os.path.splitext(output_file)
            if v.palette_plus:
                maffile = pre + ".msf"
            else:
                maffile = pre + ".maf"
            gui.create_logitem("Generating PALETTE MAF/MSF file: " + maffile)

            maf = open(maffile, 'wb')

            for h in header:
                h = str(h).strip("\r\n")
                maf.write(h.encode('ascii'))
                maf.write("\r\n".encode('ascii'))

            maf.close()

        gui.print_summary(omega_result['summary'])

    gui.progress_string(101)
    if (len(v.process_warnings) > 0 and not v.ignore_warnings) or v.consolewait:
        gui.close_button_enable()


# -*- coding: utf-8 -*-
from functools import update_wrapper
from datetime import datetime, timedelta
import cgi
from tempfile import TemporaryFile
from .exceptions import HTTPException, BadRequest
from .utils import cached_property
from .datastructures import MultiDict, FileUpload, FormsDict, WSGIHeaders
from ._compat import (PY2, to_bytes, string_types, text_type,
     integer_types, to_unicode, to_native, BytesIO)
if PY2:
    from Cookie import SimpleCookie
else:
    from http.cookies import SimpleCookie
from .utils import (urlencode, urldecode, urlquote, urlunquote, urljoin, json)
from .http import (parse_content_type, parse_date, parse_auth, parse_content_type, parse_range_header)

from .exceptions import BadRequest

MEMFILE_MAX = 4*1024*1024

class Request(object):
    """
    """

    #: the charset for the request, defaults to utf-8
    charset = 'utf-8'

    #: the error handling procedure for errors, defaults to 'replace'
    encoding_errors = 'replace'

    endpoint = ''

    #: a dict of view arguments that matched the request.  If an exception
    #: happened when matching, this will be `None`.
    view_args = None

    def __init__(self, environ, populate_request=True):
        self.environ = environ
        if populate_request:
            self.environ['cocopot.request'] = self

    def __repr__(self):
        # make sure the __repr__ even works if the request was created
        # from an invalid WSGI environment.  If we display the request
        # in a debug session we don't want the repr to blow up.
        args = []
        try:
            args.append("'%s'" % to_native(self.url, self.url_charset))
            args.append('[%s]' % self.method)
        except Exception:
            args.append('(invalid WSGI environ)')

        return '<%s %s>' % (
            self.__class__.__name__,
            ' '.join(args)
        )

    @property
    def url_charset(self):
        """The charset that is assumed for URLs.  Defaults to the value
        of `charset`.

        """
        return self.charset

    def close(self):
        """Closes associated resources of this request object.  This
        closes all file handles explicitly.  You can also use the request
        object in a with statement with will automatically close it.

        """
        if hasattr(self.stream, 'close'):
            self.stream.close()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, tb):
        self.close()

    @cached_property
    def stream(self):
        stream = self.get_input_stream()
        stream.seek(0)
        return stream

    @property
    def input_stream(self):
        return self.environ.get('wsgi.input')

    @cached_property
    def args(self):
        query_string = self.environ.get('QUERY_STRING', '')
        if query_string:
            return MultiDict(urldecode(query_string))
        else:
            return MultiDict()

    @property
    def data(self):
        return self.get_data()

    def get_data(self, cache=True, as_text=False):
        """This reads the buffered incoming data from the client into one
        bytestring.  By default this is cached but that behavior can be
        changed by setting `cache` to `False`.

        Usually it's a bad idea to call this method without checking the
        content length first as a client could send dozens of megabytes or more
        to cause memory problems on the server.

        If `as_text` is set to `True` the return value will be a decoded
        unicode string.

        """
        rv = getattr(self, '_cached_data', None)
        if rv is None:
            rv = self.stream.read()
            if cache:
                self._cached_data = rv
        if as_text:
            rv = rv.decode(self.charset, self.encoding_errors)
        return rv

    @cached_property
    def chunked(self):
        """ True if Chunked transfer encoding was. """
        return 'chunked' in self.environ.get('HTTP_TRANSFER_ENCODING', '').lower()

    def iter_body(self, read, bufsize):
        maxread = max(0, self.content_length)
        while maxread:
            part = read(min(maxread, bufsize))
            if not part: break
            yield part
            maxread -= len(part)

    def iter_chunked(self, read, bufsize):
        err = BadRequest('Error while parsing chunked transfer body.')
        rn, sem, bs = to_bytes('\r\n'), to_bytes(';'), to_bytes('')
        while True:
            header = read(1)
            while header[-2:] != rn:
                c = read(1)
                header += c
                if not c: raise err
                if len(header) > bufsize: raise err
            size, _, _ = header.partition(sem)
            try:
                maxread = int(to_native(size.strip()), 16)
            except ValueError:
                raise err
            if maxread == 0: break
            buff = bs
            while maxread > 0:
                if not buff:
                    buff = read(min(maxread, bufsize))
                part, buff = buff[:maxread], buff[maxread:]
                if not part: raise err
                yield part
                maxread -= len(part)
            if read(2) != rn:
                raise err

    def get_input_stream(self):
        try:
            read_func = self.environ['wsgi.input'].read
        except KeyError:
            self.environ['wsgi.input'] = BytesIO()
            return self.environ['wsgi.input']
        if self.chunked:
            body, body_size, is_temp_file = BytesIO(), 0, False
            for part in self.iter_chunked(read_func, MEMFILE_MAX):
                body.write(part)
                body_size += len(part)
                if not is_temp_file and body_size > MEMFILE_MAX:
                    body, tmp = TemporaryFile(mode='w+b'), body
                    body.write(tmp.getvalue())
                    del tmp
                    is_temp_file = True
        else:
            if self.content_length > MEMFILE_MAX:
                body = TemporaryFile(mode='w+b')
            else:
                body = BytesIO()
                for part in self.iter_body(read_func, MEMFILE_MAX):
                    body.write(part)
        self.environ['wsgi.input'] = body
        body.seek(0)
        return body

    def parse_form_data(self):
        post = FormsDict()
        # We default to application/x-www-form-urlencoded for everything that
        # is not multipart and take the fast path (also: 3.1 workaround)
        if not self.content_type.startswith('multipart/'):
            pairs = urldecode(to_unicode(self.get_data()))
            for key, value in pairs:
                post[key] = value
            return post

        safe_env = {'QUERY_STRING': ''}  # Build a safe environment for cgi
        for key in ('REQUEST_METHOD', 'CONTENT_TYPE', 'CONTENT_LENGTH'):
            if key in self.environ:
                safe_env[key] = self.environ[key]
        args = dict(fp=self.stream, environ=safe_env, keep_blank_values=True)
        if not PY2:
            args['encoding'] = 'utf8'
        data = cgi.FieldStorage(**args)
        self.environ['_cgi.FieldStorage'] = data  #http://bugs.python.org/issue18394
        data = data.list or []
        for item in data:
            if item.filename:
                post[item.name] = FileUpload(item.file, item.name,
                                             item.filename, item.headers)
            else:
                post[item.name] = item.value
        return post

    @cached_property
    def parsed_form_data(self):
        return self.parse_form_data()

    @cached_property
    def form(self):
        form = FormsDict()
        for name, item in self.parsed_form_data.allitems():
            if not isinstance(item, FileUpload):
                form[name] = item
        return form

    @cached_property
    def values(self):
        """Combined multi dict for `args` and `form`."""
        args = []
        for d in self.args, self.form:
            if not isinstance(d, MultiDict):
                for k, v in d.items():
                    args.append((k, v))
            else:
                for k, v in d.iterallitems():
                    args.append((k, v))
        return MultiDict(args)

    @cached_property
    def files(self):
        files = FormsDict()
        for name, item in self.parsed_form_data.allitems():
            if isinstance(item, FileUpload):
                files[name] = item
        return files

    def get_host(self):
        """Return the real host for the given WSGI environment.  This first checks
        the `X-Forwarded-Host` header, then the normal `Host` header, and finally
        the `SERVER_NAME` environment variable (using the first one it finds).
        """
        environ = self.environ
        if 'HTTP_X_FORWARDED_HOST' in environ:
            rv = environ['HTTP_X_FORWARDED_HOST'].split(',', 1)[0].strip()
        elif 'HTTP_HOST' in environ:
            rv = environ['HTTP_HOST']
        else:
            rv = environ['SERVER_NAME']
            if (environ['wsgi.url_scheme'], environ['SERVER_PORT']) not \
               in (('https', '443'), ('http', '80')):
                rv += ':%s'%(environ['SERVER_PORT'])
        return rv

    def get_content_length(self):
        """Returns the content length from the WSGI environment as
        integer.  If it's not available `None` is returned.
        """
        content_length = self.environ.get('CONTENT_LENGTH')
        if content_length is not None:
            try:
                return max(0, int(content_length))
            except (ValueError, TypeError):
                pass
        return 0

    def get_current_url(self, root_only=False, strip_querystring=False, host_only=False):
        """A handy helper function that recreates the full URL as IRI for the
        current request or parts of it.  Here an example:

            >>> get_current_url()
            'http://localhost/script/?param=foo'
            >>> get_current_url(root_only=True)
            'http://localhost/script/'
            >>> get_current_url(host_only=True)
            'http://localhost/'
            >>> get_current_url(strip_querystring=True)
            'http://localhost/script/'

        """
        environ = self.environ
        tmp = [environ['wsgi.url_scheme'], '://', self.get_host()]
        cat = tmp.append
        if host_only:
            return ''.join(tmp) + '/'
        cat(urlquote(environ.get('SCRIPT_NAME', '')).rstrip('/'))
        cat('/')
        if not root_only:
            print(type(environ.get('PATH_INFO')))
            cat(urlquote(environ.get('PATH_INFO', '').lstrip('/')))
            if not strip_querystring:
                qs = self.query_string
                if qs:
                    cat('?' + qs)
        return ''.join(tmp)

    @cached_property
    def cookies(self):
        """Read only access to the retrieved cookie values as dictionary."""
        cookies = SimpleCookie(self.environ.get('HTTP_COOKIE', '')).values()
        return FormsDict((c.key, c.value) for c in cookies)

    def get_cookie(self, key):
        """ Return the content of a cookie. """
        value = self.cookies.get(key)
        return value

    @cached_property
    def headers(self):
        """The headers from the WSGI environ as immutable
        `~cocopot.datastructures.WSGIHeaders`.
        """
        return WSGIHeaders(self.environ)

    @cached_property
    def path(self):
        """Requested path as unicode.  This works a bit like the regular path
        info in the WSGI environment but will always include a leading slash,
        even if the URL root is accessed.
        """
        return '/' + to_unicode(self.environ.get('PATH_INFO', '')).lstrip('/')

    @property
    def script_name(self):
        """ The initial portion of the URL's `path` that was removed by a higher
            level (server or routing middleware) before the application was
            called. This script path is returned with leading and tailing
            slashes. """
        script_name = self.environ.get('SCRIPT_NAME', '').strip('/')
        return '/' + script_name + '/' if script_name else '/'

    @property
    def full_path(self):
        """Requested path as unicode, including the query string."""
        return urljoin(self.script_name, self.path.lstrip('/'))

    @cached_property
    def script_root(self):
        """The root path of the script without the trailing slash."""
        raw_path = to_unicode(self.environ.get('SCRIPT_NAME') or '',
                                       self.charset, self.encoding_errors)
        return raw_path.rstrip('/')

    @cached_property
    def url(self):
        """The reconstructed current URL as IRI.
        """
        return self.get_current_url()

    @cached_property
    def base_url(self):
        """Like `url` but without the querystring
        """
        return self.get_current_url(strip_querystring=True)

    @cached_property
    def root_url(self):
        """The full URL root (with hostname), this is the application
        root as IRI.
        """
        return self.get_current_url(root_only=True)

    @cached_property
    def host_url(self):
        """Just the host with scheme as IRI.
        """
        return self.get_current_url(host_only=True)

    @cached_property
    def host(self):
        """Just the host including the port if available.
        """
        return self.get_host()

    @property
    def query_string(self):
        return self.environ.get('QUERY_STRING', '')

    @property
    def method(self):
        return self.environ.get('REQUEST_METHOD', 'GET').upper()

    @cached_property
    def access_route(self):
        """If a forwarded header exists this is a list of all ip addresses
        from the client ip to the last proxy server.
        """
        if 'HTTP_X_FORWARDED_FOR' in self.environ:
            addr = self.environ['HTTP_X_FORWARDED_FOR'].split(',')
            return list([x.strip() for x in addr])
        elif 'REMOTE_ADDR' in self.environ:
            return list([self.environ['REMOTE_ADDR']])
        return list()

    remote_route = access_route

    @property
    def remote_addr(self):
        """The remote address of the client."""
        route = self.access_route
        return route[0] if route else None

    is_xhr = property(lambda x: x.environ.get('HTTP_X_REQUESTED_WITH', '')
                      .lower() == 'xmlhttprequest', doc='''
        True if the request was triggered via a JavaScript XMLHttpRequest.
        This only works with libraries that support the `X-Requested-With`
        header and set it to "XMLHttpRequest".  Libraries that do that are
        prototype, jQuery and Mochikit and probably some more.''')
    is_secure = property(lambda x: x.environ['wsgi.url_scheme'] == 'https',
                         doc='`True` if the request is secure.')

    @cached_property
    def if_modified_since(self):
        """The parsed `If-Modified-Since` header as datetime object."""
        return parse_date(self.environ.get('HTTP_IF_MODIFIED_SINCE'))

    @cached_property
    def if_unmodified_since(self):
        """The parsed `If-Unmodified-Since` header as datetime object."""
        return parse_date(self.environ.get('HTTP_IF_UNMODIFIED_SINCE'))

    @cached_property
    def range(self):
        """The parsed `Range` header.
        """
        return parse_range_header(self.environ.get('HTTP_RANGE'))

    @cached_property
    def authorization(self):
        header = self.environ.get('HTTP_AUTHORIZATION')
        if not header: return None
        return parse_auth(header)

    @property
    def content_type(self):
        return self.environ.get('CONTENT_TYPE', '')

    @cached_property
    def content_length(self):
        """The Content-Length entity-header field indicates the size of the
        entity-body in bytes or, in the case of the HEAD method, the size of
        the entity-body that would have been sent had the request been a
        GET.
        """
        return self.get_content_length()

    @cached_property
    def parsed_content_type(self):
        return parse_content_type(self.environ.get('CONTENT_TYPE', ''))

    @property
    def mimetype(self):
        """Like `content_type` but without parameters (eg, without
        charset, type etc.).  For example if the content
        type is ``text/html; charset=utf-8`` the mimetype would be
        ``'text/html'``.
        """
        return self.parsed_content_type[0]

    @property
    def mimetype_params(self):
        """The mimetype parameters as dict.  For example if the content
        type is ``text/html; charset=utf-8`` the params would be
        ``{'charset': 'utf-8'}``.
        """
        return self.parsed_content_type[1]

    @property
    def blueprint(self):
        """The name of the current blueprint"""
        if '.' in self.endpoint:
            return self.endpoint.rsplit('.', 1)[0]

    @property
    def json(self):
        """If the mimetype is `application/json` this will contain the
        parsed JSON data.  Otherwise this will be `None`.

        The `get_json` method should be used instead.
        """
        return self.get_json(silent=True)

    def get_json(self, force=False, silent=False, cache=True):
        """Parses the incoming JSON request data and returns it.  If
        parsing fails the `on_json_loading_failed` method on the
        request object will be invoked.  By default this function will
        only load the json data if the mimetype is ``application/json``
        but this can be overriden by the `force` parameter.

        Args:

          * force: if set to `True` the mimetype is ignored.
          * silent: if set to `False` this method will fail silently
                       and return `False`.
          * cache: if set to `True` the parsed JSON data is remembered
                      on the request.
        """
        _missing = object()
        rv = getattr(self, '_cached_json', _missing)
        if rv is not _missing:
            return rv

        if self.mimetype != 'application/json' and not force:
            return None

        # We accept a request charset against the specification as
        # certain clients have been using this in the past.  This
        # fits our general approach of being nice in what we accept
        # and strict in what we send out.
        request_charset = self.mimetype_params.get('charset', 'utf-8')
        try:
            data = to_unicode(self.get_data(cache=False))
            if request_charset is not None:
                rv = json.loads(data, encoding=request_charset)
            else:
                rv = json.loads(data)
        except ValueError as e:
            if silent:
                rv = None
            else:
                raise BadRequest()
        if cache:
            self._cached_json = rv
        return rv


# Import Modules Created for Machine Learning
import data_prep as prep
import classification as classification

'''
# Install and Import third Party Modules
    The most important Python libraries and packages for Data Science
        1. Numpy
        2. Pandas
        3. Matplotlib
        4. Scikit-Learn
        5. Scipy

'''
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import numpy.random as nr
import scipy.stats as ss

from sklearn import preprocessing
import sklearn.model_selection as ms
from sklearn import linear_model
import sklearn.metrics as sklm
import sklearn.decomposition as skde
from sklearn import feature_selection as fs
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.naive_bayes import GaussianNB, BernoulliNB
from sklearn.neural_network import MLPClassifier
from sklearn import svm
from sklearn.metrics import mean_squared_error

# Other Modules
import math
import time
from pathlib import Path

class data_prep():

    def __init__(self):
        self.test=0

    def read_csv(self, path, delimiter=',', header=None):
        return pd.read_csv(filepath_or_buffer=path, sep=delimiter, header=header)

    def drop_column(self, pd_data, drop_column):
        for col in drop_column:
            pd_data.drop([col], axis=1, inplace=True)

    def remove_duplicate_rows(self, pd_data, column):
        for col in column:
            print('\nCheck if there are duplicates for:' + col + '\n')
            print(pd_data.shape)
            pd_data_col = pd_data[col]
            print(pd_data_col.unique().shape)

            if pd_data.shape[0] != pd_data_col.unique().shape[0]:
                # Remove duplicate rows
                print("\nRemove duplicate rows")
                pd_data.drop_duplicates(subset=col, keep='first', inplace=True)
                print(pd_data.shape)
                print(pd_data_col.unique().shape)

    def recode_column_names(self, before_symbol, after_symbol, pd_data):
        pd_data.columns = [str.replace(before_symbol, after_symbol) for str in pd_data.columns]

    def check_missing_values_in_object_type(self, pd_data, miss_value):
        print('\nList of features which have missing value: \n')
        print((pd_data.astype(np.object) == miss_value).any())

        print('\nList of features which have the count of missing value: \n')
        for col in pd_data.columns:
            if pd_data[col].dtype == object:
                count = 0
                count = [count + 1 for x in pd_data[col] if x == miss_value]
                print(col + ' ' + str(sum(count)))

    def remove_rows_with_missing_values(self, pd_data, miss_value, column):
        for col in column:
            pd_data.loc[pd_data[col] == miss_value, col] = np.nan
            pd_data.dropna(axis = 0, inplace = True)
            pd_data.shape

    def transform_column_data_type(self,pd_data, column, to_data_type):
        if to_data_type == 'to_numeric':
            for col in column:
                pd_data[col] = pd.to_numeric(pd_data[col])

    def aggregating_categorical_variables(self, pd_data, categories, column):
        for col in column:
            pd_data[col] = [categories[x] for x in pd_data[col]]

    def column_groupby_value_counts(self, pd_data, column):
        for col in column:
            print('\nColumn name: ' + col + '\n')
            print(pd_data[col].value_counts())

    def math_conversion_column(self, pd_data, column, math_conversion_type):
        # Feature engineering
        # Right skewed data, Positively skewed data: Common transformations of this data include:
        #    square root, cube root, and logarithmic.
        # Left skewed data, Negatively skewed data: Common transformations include:
        #    square , cube root and logarithmic.
        for col in column:
            if math_conversion_type is 'log':
                pd_data[['log_'+ col]] = pd_data[[col]].applymap(math.log)
            elif math_conversion_type is 'square_root':
                pd_data[['square_root_'+ col]] = pd_data[[col]].applymap(math.sqrt)
            elif math_conversion_type is 'square':
                pd_data['square_'+ col] = np.power(pd_data[col],2)
            elif math_conversion_type is 'cube_root':
                pd_data['cube_root_'+ col] = np.power(pd_data[col],1/3)

    def data_describe(self, pd_data):
        print(pd_data.describe())
        print(pd_data.describe(include=[np.object, pd.Categorical]).T)

    #========================================================================================#
    # Preprocessing data - Encoding categorical features
    #========================================================================================#
    def encode_string(self, pd_data, categorical_columns):
        # Encode / Convert strings to numeric categories
        for col in categorical_columns:
            enc = preprocessing.LabelEncoder()
            enc.fit(pd_data[col])
            enc_cat_features = enc.transform(pd_data[col])

            # Now, apply one hot encoding
            ohe = preprocessing.OneHotEncoder(categories='auto')
            encoded = ohe.fit(enc_cat_features.reshape(-1,1))
            encoded_transformed = encoded.transform(enc_cat_features.reshape(-1,1)).toarray()

            # Create columns for each encoded categories
            for shape in range(int(encoded_transformed.shape[1])):
                # print("pd_data['encode_" + col + str(shape) + "']")
                # print(encoded_transformed[:,shape])
                pd_data['encode_' + col + str(shape)] = np.concatenate([encoded_transformed[:,shape]])

    #========================================================================================#
    # Preprocessing data - Scaling numerical features
    #========================================================================================#
    # Numeric features must be rescaled so they have a similar range of values.
    # Rescaling prevents features from having an undue influence on model training simply because then have a larger range of numeric variables.

    def all_scaler_plot(self, pd_data, numerical_columns):
        # Since these are member functions, call it as a member function on the instance, self.
        #standard_scaler = self.standard_scaler()
        #min_max_scaler = self.min_max_scaler()
        #max_abs_scaler = self.max_abs_scaler()
        #robust_scaler = self.robust_scaler()
        #power_transformation_Yeo_Johnson_scaler = self.power_transformation_Yeo_Johnson_scaler()
        #power_transformation_box_cox_scaler = self.power_transformation_box_cox_scaler()
        ##quantile_transformation_gaussian_scaler = self.quantile_transformation_gaussian_scaler()
        #quantile_transformation_uniform_scaler = self.quantile_transformation_uniform_scaler()
        #sample_wise_L2_normalizing_scaler = self.sample_wise_L2_normalizing_scaler()

        # Scale all features and store in different column names
        self.standard_scaler(pd_data=pd_data, numerical_columns=numerical_columns)
        self.min_max_scaler(pd_data=pd_data, numerical_columns=numerical_columns)
        self.max_abs_scaler(pd_data=pd_data, numerical_columns=numerical_columns)
        self.robust_scaler(pd_data=pd_data, numerical_columns=numerical_columns)
        self.power_transformation_Yeo_Johnson_scaler(pd_data=pd_data, numerical_columns=numerical_columns)
        self.power_transformation_box_cox_scaler(pd_data=pd_data, numerical_columns=numerical_columns)
        self.quantile_transformation_gaussian_scaler(pd_data=pd_data, numerical_columns=numerical_columns)
        self.quantile_transformation_uniform_scaler(pd_data=pd_data, numerical_columns=numerical_columns)
        self.sample_wise_L2_normalizing_scaler(pd_data=pd_data, numerical_columns=numerical_columns)

        # Histogram Plot
        column_name = ['standard_scaler_',
                       'min_max_scaler_',
                       'max_abs_scaler_',
                       'robust_scaler_',
                       'Yeo_Johnson_scaler_',
                       'box_cox_scaler_',
                       'quantile_gaussian_scaler_',
                       'quantile_uniform_scaler_',
                       'L2_normalizing_scaler_']
        col_size = len(column_name)
        row_size = len(numerical_columns)

        # Set up seaborn
        sns.set(style="white", palette="muted", color_codes=True)
        f, axes = plt.subplots(row_size, # max row = row +1(plus one for the x-axis scale)
                               col_size,  # max column
                               figsize=(15, 10), # figsize of each graph(x,y)
                               sharex=False, # True:x- or y-axis will be shared among all subplots. False:each subplot x- or y-axis will be independent.
                               sharey=False) # 'row': each subplot row will share an x- or y-axis. 'col': each subplot column will share an x- or y-axis.
        sns.despine(left=True)

        for index_row, row in enumerate(numerical_columns):
            for index_col, col in enumerate(column_name):
                # Plot a historgram and kernel density estimate
                plot = sns.distplot(pd_data[col + row], color="m", ax=axes[index_row,index_col])
                plot.title.set_text(row[:15]) # truncate title, it should not be too long
                plot.set(xlabel=col[:-8], ylabel='Density') # col[:-8] get rid of the word '_scaler_'

        plt.setp(axes, yticks=[])
        plt.tight_layout()
        plt.show()

    def all_scaler_stat_sql(self, pd_data, numerical_columns):

        column_name = ['standard_scaler_',
                   'min_max_scaler_',
                   'max_abs_scaler_',
                   'robust_scaler_',
                   'Yeo_Johnson_scaler_',
                   'box_cox_scaler_',
                   'quantile_gaussian_scaler_',
                   'quantile_uniform_scaler_',
                   'L2_normalizing_scaler_']

        #column_name = ['standard_scaler_', 'min_max_scaler_']

        # input Stats into DataFrame and into database
        table_name = ['feature', 'scaler_type', 'counts', 'min', 'max', 'mean', 'variance', 'skewness', 'kurtosis']

        # Create Table DataFrame
        df_stats_describe = pd.DataFrame(columns=table_name)

        # Initalse Numpy Array
        np_stat_feature = np.array(numerical_columns[0])
        np_stat_scaler_type = np.array(column_name[0])

        for index_row, row in enumerate(numerical_columns):     #['loan_duration_mo', 'loan_amount', 'payment_pcnt_income', 'age_yrs']
            for index_col, col in enumerate(column_name):# 'standard_scaler_', 'min_max_scaler_', 'max_abs_scaler_',
                stats_describe = ss.stats.describe(pd_data[col + row])
                #print(stats_describe)

                if index_col == 0 and index_row == 0:
                    np_stat_feature = np.array(row)
                    np_stat_scaler_type = np.array(col)
                    np_stat_counts = np.array(stats_describe[0])
                    np_stat_minmax = np.array(stats_describe[1])
                    np_stat_min = np.array(stats_describe[1][0])
                    np_stat_max = np.array(stats_describe[1][1])
                    np_stat_mean = np.array(stats_describe[2])
                    np_stat_variance = np.array(stats_describe[3])
                    np_stat_skewness = np.array(stats_describe[4])
                    np_stat_kurtosis = np.array(stats_describe[5])
                else:
                    '''
                    # np.insert shows the first insert at the last array
                    np_stat_feature = np.insert(np_stat_feature, len(column_name)-index_col, row)
                    np_stat_scaler_type = np.insert(np_stat_scaler_type, len(column_name)-index_col, col)
                    np_stat_counts = np.insert(np_stat_counts, len(column_name)-index_col, stats_describe[0])
                    np_stat_minmax = np.insert(np_stat_minmax, len(column_name)-index_col, stats_describe[1]) #minxmax is in 2-D array
                    np_stat_min = np.insert(np_stat_min, len(column_name)-index_col, stats_describe[1][0])
                    np_stat_max = np.insert(np_stat_max, len(column_name)-index_col, stats_describe[1][1])
                    np_stat_mean = np.insert(np_stat_mean, len(column_name)-index_col, stats_describe[2])
                    np_stat_variance = np.insert(np_stat_variance, len(column_name)-index_col, stats_describe[3])
                    np_stat_skewness = np.insert(np_stat_skewness, len(column_name)-index_col, stats_describe[4])
                    np_stat_kurtosis = np.insert(np_stat_kurtosis, len(column_name)-index_col, stats_describe[5])
                    '''
                    np_stat_feature = np.append(np_stat_feature, row)
                    np_stat_scaler_type = np.append(np_stat_scaler_type, col)
                    np_stat_counts = np.append(np_stat_counts, stats_describe[0])
                    np_stat_minmax = np.append(np_stat_minmax, stats_describe[1]) #minxmax is in 2-D array
                    np_stat_min = np.append(np_stat_min, stats_describe[1][0])
                    np_stat_max = np.append(np_stat_max, stats_describe[1][1])
                    np_stat_mean = np.append(np_stat_mean, stats_describe[2])
                    np_stat_variance = np.append(np_stat_variance, stats_describe[3])
                    np_stat_skewness = np.append(np_stat_skewness, stats_describe[4])
                    np_stat_kurtosis = np.append(np_stat_kurtosis, stats_describe[5])

        df_stats_describe['feature'] = np_stat_feature
        df_stats_describe['scaler_type'] = np_stat_scaler_type
        df_stats_describe['counts'] = np_stat_counts
        df_stats_describe['min'] = np_stat_min
        df_stats_describe['max'] = np_stat_max
        df_stats_describe['mean'] = np_stat_mean
        df_stats_describe['variance'] = np_stat_variance
        df_stats_describe['skewness'] = np_stat_skewness
        df_stats_describe['kurtosis'] = np_stat_kurtosis
        #print(df_stats_describe)
        return df_stats_describe

    def standard_scaler(self, pd_data, numerical_columns):
    # Standardize features by removing the mean and scaling to unit variance (z-score)
        scaler = preprocessing.StandardScaler().fit(pd_data[numerical_columns])
        scaler_transform = scaler.transform(pd_data[numerical_columns])
        #print(scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['standard_scaler_'+ col] = scaler_transform[:,select_column]
            select_column = select_column + 1

    def min_max_scaler(self, pd_data, numerical_columns):
    # Transforms features by scaling each feature to a given range. In this function its 0 to 1
        min_max_scaler = preprocessing.MinMaxScaler().fit(pd_data[numerical_columns])
        min_max_scaler_transform = min_max_scaler.transform(pd_data[numerical_columns])
        #print(min_max_scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(min_max_scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['min_max_scaler_'+ col] = min_max_scaler_transform[:,select_column]
            select_column = select_column + 1

    def max_abs_scaler(self, pd_data, numerical_columns):
    # Maximal absolute value of each feature in the training set will be 1.0.
    # It does not shift/center the data, and thus does not destroy any sparsity.
        max_abs_scaler = preprocessing.MaxAbsScaler().fit(pd_data[numerical_columns])
        max_abs_scaler_transform = max_abs_scaler.transform(pd_data[numerical_columns])
        #print(max_abs_scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(max_abs_scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['max_abs_scaler_'+ col] = max_abs_scaler_transform[:,select_column]
            select_column = select_column + 1

    def robust_scaler(self, pd_data, numerical_columns):
    # Used to deal with outliers.
    # The range between the 1st quartile (25th quantile) and the 3rd quartile (75th quantile) by default.
        robust_scaler = preprocessing.RobustScaler(quantile_range=(25, 75)).fit(pd_data[numerical_columns])
        robust_scaler_transform = robust_scaler.transform(pd_data[numerical_columns])
        #print(robust_scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(robust_scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['robust_scaler_'+ col] = robust_scaler_transform[:,select_column]
            select_column = select_column + 1

    def power_transformation_Yeo_Johnson_scaler(self, pd_data, numerical_columns):
    # Yeo-Johnson supports both positive or negative data.
    # By default, zero-mean, unit-variance normalization is applied to the transformed data.
        power_transformation_Yeo_Johnson_scaler = preprocessing.PowerTransformer(method='yeo-johnson').fit(pd_data[numerical_columns])
        power_transformation_Yeo_Johnson_scaler_transform = power_transformation_Yeo_Johnson_scaler.transform(pd_data[numerical_columns])
        #print(power_transformation_Yeo_Johnson_scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(power_transformation_Yeo_Johnson_scaling_scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['Yeo_Johnson_scaler_'+ col] = power_transformation_Yeo_Johnson_scaler_transform[:,select_column]
            select_column = select_column + 1

    def power_transformation_box_cox_scaler(self, pd_data, numerical_columns):
    # Box-Cox requires input data to be strictly positive.
    # By default, zero-mean, unit-variance normalization is applied to the transformed data.
        power_transformation_box_cox_scaler = preprocessing.PowerTransformer(method='box-cox').fit(pd_data[numerical_columns])
        power_transformation_box_cox_scaler_transform = power_transformation_box_cox_scaler.transform(pd_data[numerical_columns])
        #print(power_transformation_box_cox_scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(power_transformation_box_cox_scaling_scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['box_cox_scaler_'+ col] = power_transformation_box_cox_scaler_transform[:,select_column]
            select_column = select_column + 1

    def quantile_transformation_gaussian_scaler(self, pd_data, numerical_columns):
    # to map the data to a uniform distribution with values between 0 and 1:
    # spread out the most frequent values. It also reduces the impact of (marginal) outliers.
    # Note that this transform is non-linear. It may distort linear correlations
        quantile_transformation_gaussian_scaler = preprocessing.QuantileTransformer(output_distribution='normal').fit(pd_data[numerical_columns])
        quantile_transformation_gaussian_scaler_transform = quantile_transformation_gaussian_scaler.transform(pd_data[numerical_columns])
        #print(quantile_transformation_gaussian_scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(quantile_transformation_gaussian_scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['quantile_gaussian_scaler_'+ col] = quantile_transformation_gaussian_scaler_transform[:,select_column]
            select_column = select_column + 1

    def quantile_transformation_uniform_scaler(self, pd_data, numerical_columns):
    # to map the data to a uniform distribution with values between 0 and 1:
    # spread out the most frequent values. It also reduces the impact of (marginal) outliers.
    # Note that this transform is non-linear. It may distort linear correlations
        quantile_transformation_uniform_scaler = preprocessing.QuantileTransformer(output_distribution='uniform').fit(pd_data[numerical_columns])
        quantile_transformation_uniform_scaler_transform = quantile_transformation_uniform_scaler.transform(pd_data[numerical_columns])
        #print(quantile_transformation_uniform_scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(quantile_transformation_uniform_scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['quantile_uniform_scaler_'+ col] = quantile_transformation_uniform_scaler_transform[:,select_column]
            select_column = select_column + 1

    def sample_wise_L2_normalizing_scaler(self, pd_data, numerical_columns):
    # Normalization is the process of scaling individual samples to have unit norm
        sample_wise_L2_normalizing_scaler = preprocessing.Normalizer().fit(pd_data[numerical_columns])
        sample_wise_L2_normalizing_scaler_transform = sample_wise_L2_normalizing_scaler.transform(pd_data[numerical_columns])
        #print(sample_wise_L2_normalizing_scaler.transform(pd_data[numerical_columns]))

        select_column = 0
        #print(sample_wise_L2_normalizing_scaler_transform[:,select_column])
        for col in numerical_columns:
            pd_data['L2_normalizing_scaler_'+ col] = sample_wise_L2_normalizing_scaler_transform[:,select_column]
            select_column = select_column + 1

    #========================================================================================#
    # Preprocessing data - Ploting
    #========================================================================================#
    def plot_box(self, pd_data, num_graph, col_x=0, col_y=0):
        for col in num_graph:
            sns.set_style("whitegrid")

            if col_x != 0:
                sns.boxplot(col_x, col, data=pd_data)
                plt.xlabel(col_x) # Set text for the x axis
                plt.ylabel(col)# Set text for y axis
            else:
                #sns.boxplot('num_of_cylinders', 'price', data=pd_data)
                sns.boxplot(col, col_y, data=pd_data)
                plt.xlabel(col) # Set text for the x axis
                plt.ylabel(col_y)# Set text for y axis

            plt.show()

    def plot_scatter_shape(self, pd_data, num_graph, col_x=0, col_y=0, shape_col=0, alpha = 0.2):
        shapes = ['+', 'o', 's', 'x', '^'] # pick distinctive shapes
        unique_categories = pd_data[shape_col].unique()
        for col in num_graph: # loop over the columns to plot
            sns.set_style("whitegrid")

            if col_x != 0:
                for i, cat in enumerate(unique_categories): # loop over the unique categories
                    temp = pd_data[pd_data[shape_col] == cat]
                    sns.regplot(col_x, col, data=temp, marker = shapes[i], label = cat,
                                scatter_kws={"alpha":alpha}, fit_reg = False, color = 'blue')
                plt.title('Scatter plot of ' + col + ' vs. ' + col_x) # Give the plot a main title
                plt.xlabel(col_x) # Set text for the x axis
                plt.ylabel(col)# Set text for y axis
            else:
                for i, cat in enumerate(unique_categories): # loop over the unique categories
                    temp = pd_data[pd_data[shape_col] == cat]
                    sns.regplot(col, col_y, data=temp, marker = shapes[i], label = cat,
                                scatter_kws={"alpha":alpha}, fit_reg = False, color = 'blue')
                plt.title('Scatter plot of ' + col_y + ' vs. ' + col) # Give the plot a main title
                plt.xlabel(col) # Set text for the x axis
                plt.ylabel(col_y)# Set text for y axis

            plt.legend()
            plt.show()

    def plot_violin(self, pd_data, num_graph, col_x):
        for col in num_graph:
            sns.set_style("whitegrid")
            sns.violinplot(col_x, col, data=pd_data)
            plt.xlabel(col_x) # Set text for the x axis
            plt.ylabel(col)# Set text for y axis
            plt.show()

    def hist_plot(self, pd_data, num_graph):
        for col in num_graph:
            sns.distplot(pd_data[col])
            plt.title('Histogram of ' + col)
            plt.xlabel(col)
            plt.ylabel('Density')
            plt.show()

    def heat_map_correlation(self, pd_data):
        # How to use this function:
        # data_prep.heat_map_correlation(pd_data=credit)
        corrmat= pd_data.corr()
        f, ax =plt.subplots(figsize=(5,4))
        sns.heatmap(corrmat, square=True)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'kenny'

from docx import Document
from docx.shared import Inches
from docx.enum.text import WD_ALIGN_PARAGRAPH
from random import shuffle
import tkMessageBox

def list_to_paper(group_count, title,  questionlist):
    question_order = range(len(questionlist))
    if group_count in range(1, 7):
        cross_ref_file = open("Data\\Cevap.txt", "wb")
        for n in range(group_count):
            document = Document('Data\\Template.docx')
            p = document.add_heading(title, 2)
            p.alignment = WD_ALIGN_PARAGRAPH.CENTER
            document.add_heading(unicode('Ad:                                                     Sınıf:', 'utf-8'), 4)
            document.add_heading('Soyad:                                                 No:', 4)
            for i in question_order:
                cross_ref_file.write('\t ' + str(i + 1));
                document.add_paragraph(str(i + 1) + '.')     # , style='ListNumber')
                document.add_picture(questionlist[i], width=Inches(3.3))
                for x in range(5):
                    document.add_paragraph("\n")
                document.save('Data\Sinav (' + ['A', 'B', 'C', 'D', 'E', 'F'][n] + ').docx')
            shuffle(question_order)
            cross_ref_file.write('\n');
        cross_ref_file.close()
    else:
        tkMessageBox.showerror("Hata", "Grup Sayısı 1-6 arasinda olabilir.!")


from urllib.request import urlopen
import json
from argparse import ArgumentParser
from datetime import datetime

p = ArgumentParser()
p.add_argument("pkg", nargs="*")
args = p.parse_args()

def print_version(pkg):
    with urlopen(f"https://pypi.org/pypi/{pkg}/json") as f:
        data = json.load(f)
    ver = data["info"]["version"]
    reldate = min(datetime.fromisoformat(f['upload_time']) for f in data['releases'][ver])
    print(f"{pkg}: {ver} ({reldate:%Y/%m/%d})")

for pkg in args.pkg:
    print_version(pkg)


# from __future__ import division
import matplotlib.pyplot as plt
from scipy.io import wavfile
# from scipy.fftpack import *
import scipy
import numpy as np
import math

# filename = "C:\\Users\\ethan\\Desktop\\downsampled music\\"

def plotwave(fs, signal, maxf=None):
        """Visualize (a segment of) a wave file."""
        # maxf = maximum number of frames
        frames = scipy.arange(signal.size)   # x-axis
        if maxf:
            plt.plot(frames[:maxf], signal[:maxf])
            plt.xticks(scipy.arange(0, maxf, 0.5*fs), scipy.arange(0, maxf/fs, 0.5))
            plt.show()
        else:
            plt.plot(frames, signal)
            plt.xticks(scipy.arange(0, signal.size, 0.5*fs), scipy.arange(0, signal.size/fs, 0.5))
            plt.show()

def downsample(filename, i, factor):
    """Lower the sampling rate by factor."""
    # newfilename = filename[:-4]+'-down'+str(factor)+'.wav'
    fs, wave = wavfile.read(filename)
    newfs = int(fs/factor)
    # fill in the rest
    indices = range(0, len(wave), factor)
    wave = wave[indices]
    wavfile.write("C:\\Users\\ethan\\Desktop\\ultradownsampled music\\" + str(i) + ".wav", newfs, wave)

def invert(filename, num):
    '''invert the track'''
    fs, wave = wavfile.read(filename)

    newWav = []
    numRev = int((fs * 60 / 80)*2)
    for point in range(0, len(wave)):

        newWav.append([-wave[point][0], -wave[point][1]])

        # if point&(numRev*2) >= numRev:

        #     toSlow = []

        #     for i in range(numRev):
        #         toSlow.append([wave[point + i][0], wave[point + i][1]])

        #     toSlow = np.array(toSlow)

        #     toSlow = (ifft(fft(np.array(toSlow)) - np.int16(50))).tolist()
        #     newWav.append(toSlow)

        # else:
        #     for i in range(numRev):
        #         newWav.append([wave[point + i][0], wave[point + i][1]])

        # newWav.insert(0, [-wave[point][0], -wave[point][1]])

    newWav = np.array(newWav)

    wavfile.write("C:\\Users\\ethan\\Desktop\\" + str(num) +".wav", fs, newWav)

def noiseCancel(filename, num):
    '''invert the track'''
    fs, wave = wavfile.read(filename)

    newWav = []
    for point in range(0, len(wave)):

        newWav.append([wave[point][0], -wave[point][0]])

    newWav = np.array(newWav)

    wavfile.write("C:\\Users\\ethan\\Desktop\\" + str(num) +"noise cancelled.wav", fs, newWav)

def numConvert(num):
    return np.float32(num / 32767)

def minimize(filename):
    fs, wave = wavfile.read(filename)

    newWav = [[numConvert(wave[0][0]), numConvert(wave[0][1]), 0]]

    bp0 = wave[0][0]
    bp1 = wave[0][1]
    cp0 = wave[1][0]
    cp1 = wave[1][1]
    ap0 = wave[2][0]
    ap1 = wave[2][1]

    for point in range(1, len(wave) - 1):
        # peak
        if (cp0 >= bp0 and cp0 >= ap0) or (cp1>= bp1 and cp1 >= ap1) or (cp0 <= bp0 and cp0 <= ap0) or (cp1 <= bp1 and cp1 <= ap1):

            midpoint = newWav[-1][2] + int((point - newWav[-1][2])/2)

            newWav.append([numConvert(wave[midpoint][0]), numConvert(wave[midpoint][1]), midpoint])

            newWav.append([numConvert(wave[point][0]), numConvert(wave[point][1]), point])

        bp0 = wave[point][0]
        bp1 = wave[point][1]

        cp0 = wave[point+1][0]
        cp1 = wave[point+1][1]

        if point < len(wave)-2:
            ap0 = wave[point+2][0]
            ap1 = wave[point+2][1]

    return fs, newWav

def rebuild(fs, minimized):
    newWav = []

    for i in range(len(minimized) - 1):
        newWav.append([minimized[i][0], minimized[i][1]])

        lenToGo = minimized[i+1][2] - minimized[i][2]
        for j in range(1, lenToGo):
            newWav.append([minimized[i][0] + numConvert((minimized[i+1][0] - minimized[i][0]) * j/lenToGo),
                           minimized[i][1] + numConvert((minimized[i+1][1] - minimized[i][1]) * j/lenToGo)])

            # newWav.append([minimized[i][0] + numConvert(-0.5*(minimized[i+1][0] - minimized[i][0])*math.cos(math.pi * j/lenToGo) + 0.5*(minimized[i+1][0] - minimized[i][0])),
            #                minimized[i][1] + numConvert(-0.5*(minimized[i+1][1] - minimized[i][1])*math.cos(math.pi * j/lenToGo) + 0.5*(minimized[i+1][1] - minimized[i][1]))])

    return fs, np.array(newWav)

# import sys

# fs, minn = minimize("C:\\USers\\ethan\\Desktop\\music\\"+ str(5) + ".wav")
# print(sys.getsizeof(np.array(minn)))

# fs, wave = rebuild(fs, minn)
# print(sys.getsizeof(wave))

# wavfile.write("C:\\Users\\ethan\\Desktop\\ultrainverse\\" + str(5) +".wav", fs, wave)

# filename = "C:\\USers\\ethan\\Desktop\\music\\"+ str(1) + ".wav"
# fs, wave = wavfile.read(filename)

# print(type(wave))

noiseCancel("C:\\USers\\ethan\\Desktop\\music\\"+ str(5) + ".wav", 5)

'''
for i in range(3, 4):

    filename = "C:\\USers\\ethan\\Desktop\\music\\"+ str(i) + ".wav"

    #fs, wave = wavfile.read(filename)
    #Note that this particular file has a single channel. Most audio files will have two (stereo) channels.

    #data0 = wave[:,0]
    #data1 = wave[:,1]

    #print ('Data:', data0, "Length:", len(data0))
    #print ('Sampling rate:', fs)
    #print ('Audio length:', len(data0)/fs, 'seconds')

    # plotwave(fs, data1)

    minimize(filename, i)
    print(i)

'''


import numpy as np
import properscoring as ps
from scipy.misc import logsumexp
from scipy import optimize

def pll(samples, y, T, tau):
    """Return mean Predictive Log Likelihood"""
    # samples shape ex: (950,50), t by col, obs by row
    # y shape ex: (950,1), obs by row

    # Squared l2 norm sums over output dimensions (axis 2)
    sq_l2_norm = np.sum((samples - y) ** 2, axis=2).T

    # We have examples by row, t=1..T by col (axis 1)
    # logsumexp is over t, i.e. axis 1.
    lse = logsumexp(-0.5 * tau * sq_l2_norm, 1)

    # We have examples by row.
    # Reshape lse in case we have a nx1 vector tau (although taking the mean it does not matter)
    pll_per_ex = (lse.reshape(-1,1)
                  - np.log(T)
                  - 0.5 * np.log(2 * np.pi)
                  + 0.5 * np.log(tau))

    return np.mean(pll_per_ex)

def rmse(yHat_array, y_array):
    return np.sqrt(np.sum((yHat_array - y_array) ** 2) / float(len(y_array)))

def crps(y, yHat_means, yHat_variances):
    """yHat_variances is predictive variance, i.e. already includes tau"""
    yHat_std = np.sqrt(yHat_variances)
    return np.mean(ps.crps_gaussian(y, mu=yHat_means, sig=yHat_std))

def crps_minimization(std_dev_array, y, yHat_means):
    """yHat_variances is predictive variance, i.e. already includes tau"""
    return np.mean(ps.crps_gaussian(y, mu=yHat_means, sig=std_dev_array[0]))

def pll_maximum(yHat_2d, y_2d):
    optimal_tau = (yHat_2d - y_2d)**(-2.0)
    return pll(np.array([yHat_2d]), np.array([y_2d]), 1, optimal_tau)

def crps_minimum(yHat_2d, y_2d):
    avg = []
    for i, (yHat_val, y_2d_val) in enumerate(zip(yHat_2d.flatten(), y_2d.flatten())):
        optimal_tau_pll = (yHat_val - y_2d_val) ** (-2.0)
        result = optimize.minimize(crps_minimization,
                    np.sqrt(1.0 / optimal_tau_pll),
                    method='L-BFGS-B',
                    args=(y_2d_val, yHat_val),
                    bounds=[(None, 1e10)],
                    options={'maxiter':100})

        crps_min = crps(y_2d_val, yHat_val, result.x[0]**2.0)
        avg.append(crps_min)

    return np.mean(avg)


import math, random
# deque is converts a list into a queue
from collections import deque

# this function removes 7 cards from the deck and gives it to the player
def serve(deck):
  hand = []
  # remove card from deck using popleft function and append it to hand 7 times - range(7) for loop
  for _ in range(7):
    hand.append(deck.popleft())

  return hand

# this function represents each round of the game
def play_round(player, player2):
  # if the player is you, print your hand and collection
  if player.turn == 'p':
    player.print()

  # if player doesn't have any cards in hand, check to see if there are cards in the deck to draw from
  if(len(player.hand) == 0):
    # if the deck isn't empty, draw from the deck... else print "You are out of cards"
    if(len(deck) > 0):
      print("Player has no cards in hand and needs to draw")
      player.draw(deck)
    else:
      print("You are out of cards")
      print()
  # if player has cards in hand, ask other opponent for a desired card within the player's hand
  else:
    demand = player.ask()
    # if the card asked for is in the opponent's hand, the oponent loses the card to the player
    if(demand in player2.hand):
      player2.hand.remove(demand)
      if player.turn == 'p':
        print("The computer has a", demand, "and gives it to you")
      else:
        print("You have a", demand, "and give it to the computer")
      # player gains the card and it is added to his hand
      player.add_card(demand)
      print()
      # the player gets to ask again in a repeated round
      play_round(player, player2)
    # if the card asked for is not in the opponent's hand, the player has to draw from the deck
    else:
      print("Go fish!")
      player.draw(deck)
      print()

class Player:
  def __init__(self, hand, turn):
    self.hand = []
    self.collection = []
    self.turn = turn
    for i in hand:
      self.add_card(i)

  def add_card(self, card):
    if card in self.hand:
      self.collection.append(card)
      self.hand.remove(card)
      if self.turn == 'p':
        print("You have a pair of", card)
      else:
        print("The computer has a pair of", card)
    else:
      self.hand.append(card)

  def draw(self, deck):
    drawn = deck.popleft()
    if self.turn == 'p':
      print("You draw a", drawn)
    else:
      print("The computer draws a card")

    self.add_card(drawn)

  def ask(self):
    demand = ''
    if self.turn == 'p':
      demand = input("Pick a card to ask for: ")
      if(demand not in self.hand):
        print("Sorry, you don't have any", demand, "in your hand")
        self.ask()
      else:
        print("You ask the computer for a", demand)
    else:
      random.shuffle(self.hand)
      demand = self.hand[0]
      print("The computer asks you for a", demand)

    return demand

  def calculate(self):
    result = len(self.collection)
    if player.turn == 'p':
      print("Your score is", result)
    else:
      print("The computer's score is", result)

    return result

  def print(self):
    if self.turn == 'p':
      if(len(self.hand) > 0):
        print("Your hand is: ", *self.hand)
      if(len(self.collection) > 0):
        print("Your collection is: ", *self.collection)
    else:
      if(len(self.hand) > 0):
        print("The computer's hand is", *self.hand)
      if(len(self.collection) > 0):
        print("The computer's collection is: ", *self.collection)

# Dictionary of Cards and their values
cards = {'goldfish': 1, 'catfish': 1, 'trout': 1, 'grouper': 1, 'tuna': 2, 'salmon': 2, 'sturgeon': 2, 'piranha': 3, 'swordfish': 4, 'clownfish': -1 }

# The deck contains 4 of each cards so multiple list of cards by 4
deck = deque([key for key,value in cards.items()] * 4)

# Shuffle deck
random.shuffle(deck)

player_hand = serve(deck)
computer_hand = serve(deck)
p = Player(player_hand, 'p')
c = Player(computer_hand, 'c')
print()
player = p
player2 = c

while(len(deck) > 0):
  play_round(player, player2)
  tmp = player
  player = player2
  player2 = tmp

p.print()
p1 = p.calculate()
c.print()
p2 = c.calculate()
if(p1 > p2):
  winner ="you"
else:
  winner = "the computer"
print("The winner is ", winner)


import sqlite3

#dbName = 'example.db'

def setUpDb(dbName):
	conn = sqlite3.connect(dbName)
	c = conn.cursor()

	# Drop if existing
	c.execute("DROP TABLE IF EXISTS Checks;")

	# Create table
	c.execute('''CREATE TABLE Checks ( Id INTEGER PRIMARY KEY, Datestamp TEXT, Siteurl TEXT, Gps INTEGER );''')

	conn.close()

def writeRowToDb(dbName):
	conn = sqlite3.connect(dbName)
	c = conn.cursor()

	# Insert a row of data
	c.execute("INSERT INTO Checks ( Datestamp, Siteurl, Gps) VALUES ('2015-01-05','goteborg.se', -7 );")

	conn.commit()

	conn.close()

setUpDb("tmpTest.db")
writeRowToDb("tmpTest.db")


import cv2
import time
from calculate import *
from face import *
from faceset import *
from skeleton_detect import *
from csvfile import *

outer_id = 'yfacesy'
csvfilepath = "./faces/face_token.csv"
create_faceset(outer_id, csvfilepath)
print("初始化人脸数据集\n")
print(get_detail(outer_id))

cap = cv2.VideoCapture("./")
i = 1
testtime = 0
sign = False
while(True):
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
    ret, frame = cap.read()
    if ret == False:
        break

    if i >= 40:# 40帧=1s 基础用时10s 每个功能平均5s
        time1 = time.time()
        savepath = "./data/cap.jpg"
        cv2.imwrite(savepath, frame)
        testtime += 1
        print('第%d'%testtime + '次检测')
        if sign == False:
            print("\n签到\n")
            print(is_signing(savepath, outer_id))
            sign = True
        print("\n抬头低头\n")
        print(is_pitch(savepath ,outer_id))
        print("\n玩手机\n")
        print(is_playing_phone(savepath ,outer_id))
        print("\n睡觉\n")
        print(is_sleeping(savepath ,outer_id))
        print("\n发言\n")
        print(is_standing(savepath ,outer_id))
        time2 = time.time()
        usetime = time2 - time1
        print('此次测试总共用时为%d' % usetime)
        print('\n####################################################\n')
        i = 0
        os.remove(savepath)
    else :
        i += 1

cap.release()
cv2.destroyAllWindows()

os.remove("./faces/face_token.csv")
os.remove('./faces/body_rect.csv')
result = delete_set(outer_id)
print("\n\n\nfaceset "+result['outer_id']+" 已删除")


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import models, migrations
import django.utils.timezone
from django.conf import settings
import generic.search
import rights.utils

class Migration(migrations.Migration):

    dependencies = [
        ('auth', '0001_initial'),
    ]

    operations = [
        migrations.CreateModel(
            name='TruffeUser',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('password', models.CharField(max_length=128, verbose_name='password')),
                ('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
                ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
                ('username', models.CharField(unique=True, max_length=255, verbose_name='Sciper ou username')),
                ('first_name', models.CharField(max_length=100, verbose_name='Pr\xe9nom', blank=True)),
                ('last_name', models.CharField(max_length=100, verbose_name='Nom de famille', blank=True)),
                ('email', models.EmailField(max_length=255, verbose_name='Adresse email')),
                ('email_perso', models.EmailField(max_length=255, null=True, verbose_name='Adresse email priv\xe9e', blank=True)),
                ('is_active', models.BooleanField(default=True, help_text='D\xe9fini si cet utilisateur doit \xeatre consid\xe9r\xe9 comme actif. D\xe9sactiver ceci au lieu de supprimer le compte.', verbose_name='Actif')),
                ('is_betatester', models.BooleanField(default=False, help_text='Rend visible les \xe9l\xe9ments en cours de d\xe9veloppement', verbose_name='Betatesteur')),
                ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name="Date d'inscription")),
                ('mobile', models.CharField(max_length=25, blank=True)),
                ('adresse', models.TextField(blank=True)),
                ('nom_banque', models.CharField(help_text='Pour la poste, mets Postfinance. Sinon, mets le nom de ta banque.', max_length=128, blank=True)),
                ('iban_ou_ccp', models.CharField(help_text='Pour la poste, mets ton CCP. Sinon, mets ton IBAN', max_length=128, blank=True)),
                ('body', models.CharField(default=b'.', max_length=1)),
                ('homepage', models.TextField(null=True, blank=True)),
                ('avatar', models.ImageField(help_text='Si pas renseign\xe9, utilise la photo EPFL. Si pas de photo EPFL publique, utilise un poney.', null=True, upload_to=b'uploads/avatars/', blank=True)),
                ('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')),
                ('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
            ],
            options={
                'verbose_name': 'User',
                'verbose_name_plural': 'Users',
            },
            bases=(models.Model, rights.utils.ModelWithRight, generic.search.SearchableModel),
        ),
        migrations.CreateModel(
            name='UserPrivacy',
            fields=[
                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
                ('field', models.CharField(max_length=64, choices=[(b'mobile', 'Mobile'), (b'adresse', 'Adresse'), (b'nom_banque', 'Nom banque'), (b'iban_ou_ccp', 'IBAN ou CCP'), (b'email_perso', 'Adresse email priv\xe9e')])),
                ('level', models.CharField(max_length=64, choices=[(b'prive', 'Priv\xe9'), (b'groupe', 'Membres de mes groupes'), (b'member', 'Accr\xe9dit\xe9s AGEPoly'), (b'public', 'Public')])),
                ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL, on_delete=models.deletion.PROTECT)),
            ],
            options={
            },
            bases=(models.Model,),
        ),
    ]