class Loop(ArduinoErrorProofedRoutine):
    activeLoop = None  #reference active loop so that if a protocol is cancelled while a loop is running, we can remove "iterations:" label.

    # Loop.__init__
    # Input:
    #   master - a Tkinter frame that the Loop will be nested in
    # Output:
    #   None
    def __init__(self, master):
        # Loop frames hold the routine frame with steps and sub-loops, as well as a bar to specify # iterations
        self.box = LabelFrame(master, text="Loop")
        self.iterations = LabelEntry(self.box, 0, 0, "Number of iterations: ")
        self.currIter = Label(
            self.box, text="")  #Displays current iteration while running
        self.currIter.grid(row=0, column=2)
        self.steptype = "Loop"  #also alows Loops to be saved and loaded as if steps.
        super(Loop, self).__init__(self.box)

    # Loop.draw:
    #   Inputs:
    #       _row - the row of the Tkinter parent frame in which the loop will be drawn with the grid manager
    #       _cos - the column of the Tkinter parent frame in which the loop will be drawn with the grid manager
    #   Output: None
    def draw(self, _row, _col):
        self.box.grid(row=_row, column=_col, sticky=W)
        super(Loop, self).draw(1, 0)

    # Loop.saveEntries: Called before running or saving a protocol. Checks to make sure all entries are valid (recursively
    #   for nested loops) and saves the values so the protocol will not crash even if the user changes values during a run.
    # Input:
    #   iters - a tuple containing the number of iterations each outer loop will be iterated over. The immediate outer
    #           loop is first, the second outer loop is second, and so on. This is used for recursive error checking.
    # Output: None
    def saveEntries(self, iters=None):
        saveIter = self.iterations.get()
        if saveIter == "":
            raise ValueError("Error: Unfilled number of iterations in loop.")
        try:
            self.saveIter = int(saveIter)
        except:
            raise ValueError(saveIter + " is not a valid n")
        if self.saveIter < 1:
            raise ValueError(
                "You must loop over a postitive integer number of iterations.")
        if self.steps == []:
            raise Exception("You cannot run a loop with no steps!")
        for item in self.steps:
            if iters:
                _iters = tuple([self.saveIter] + list(iters))
            else:
                _iters = (self.saveIter, )

            try:
                item.saveEntries(iters=_iters)
                if item.box.cget('bg') == "yellow":
                    try:
                        item.box.config(bg='SystemButtonFace')
                    except:
                        item.box.config(bg='gray')
            except Exception as E:
                if hasattr(
                        item, 'activeLoop'
                ):  # then item is a loop. Only turn yellow if iteration error.
                    if E.message in (
                            "Error: Unfilled number of iterations in loop.",
                            "You must loop over a postitive integer number of iterations.",
                            "You cannot run a loop with no steps!"
                    ) or " is not a valid n" in E.message:
                        item.box.config(bg='yellow')
                # turn yellow
                else:  # turn yellow
                    item.box.config(bg='yellow')
                # in anycase, raise the error again.
                raise E

    # Loop.save: Called recursively when Protocol.save is called. Returns information necessary to reconstruct loop to calling object.
    #   Inputs:
    #       None
    #   Outputs:
    #       savedLoop - A list of information necessary to reconstruct the loop to be saved in a JSON file.
    def save(self):
        savedLoop = super(Loop, self).save()
        print(type(self.stepImplementation))
        if hasattr(
                self.stepImplementation, '__iter__'
        ):  # if more than one step can be used in the protocol (self.stepImplementation is an iterable)
            stepImp = [s.__name__ for s in self.stepImplementation]
            savedLoop = ["Loop", stepImp, self.iterations.get()] + savedLoop
        else:  #Otherwise only one steptype is used in a protocol
            savedLoop = [
                "Loop", self.stepImplementation.__name__,
                self.iterations.get()
            ] + savedLoop
        return savedLoop

    # Called recursively when Protocol.loadProtocol is called. Reconstructs loop saved by Loop.save
    #   Inputs:
    #       savedLoop - list of information to reconstruct saved loop object, generated by Loop.saved and retrieved from
    #           a JSON file.
    #   Outputs:
    #       None
    def load(self, savedLoop):
        stepImp = savedLoop[0]
        self.stepImplementation = []
        if type(stepImp) == list:
            for s in stepImp:
                self.checkIfHasIllegalCharacters(s)
                self.stepImplementation.append(eval(s))
        else:
            self.checkIfHasIllegalCharacters(stepImp)
            self.stepImplementation = eval(stepImp)
        self.iterations.insert(0, savedLoop[1])
        self.iterations.saved = savedLoop[1]
        super(Loop, self).load(savedLoop[2:])

    # Loop.run - executes the loop
    # Inputs:
    #   iter - a tuple containing the current iteration values of outer loops. The current loop is at bin 0, the first
    #       outer Loop is at bin 1, ect.
    # Outputs:
    #       Returns "Error" if there is an error while running. This propogates up through the recursive structure to
    #       cancel the run.
    def run(self, iter=None):
        Loop.activeLoop = self  #this marker allows steps to clean up iteration counter if the protocol is canceled
        for i in range(1, self.saveIter + 1):
            self.currIter.config(text="Iteration: " + str(i))
            if not iter:
                iter0 = (i, )

            else:
                iter0 = (i, ) + iter
            if super(Loop, self).run(
                    iter=iter0
            ) == "Error":  #run through one iteration of the loop
                return "Error"
        self.currIter.config(text="")
        Loop.activeLoop = None