#!/usr/bin/python
# mix.py
# This has several subroutines that take bake files, do string replacements on
# them, mix and match them, and so on.

import re

class Grid:
    """
    This represents the space over which bake iterates and operates
    """
    def __init__(self, lines):
        """
        A grid is generated from a bp file

        It has:
        keys: the list of keys
        list_values: the list of values for a given key (these are matched by
        their indices)
        n_values is the number of values for a given index
        N_values is the repeated product of n_values; it is the total number of
        runs to do.
        keydict gives the index of a given key
        """
        self.keys = []
        self.list_values = []
        self.n_values = []
        self.keydict = {}

        # Load bp file
        # m is the number of keys (not the number of values for the
        # keys)
        m = 0
        for line in lines:
            elements = line.split(';')
            if elements[0] not in self.keys:
                self.keys.append(elements[0])
                self.list_values.append(elements[1:])
                self.n_values.append(len(elements) - 1)
                self.keydict[self.keys[-1]] = m
                m = m + 1
            else:
                # If this key is repeated
                i = self.keydict[elements[0]]
                self.list_values[i] = elements[1:]
                self.n_values[i] = len(elements) - 1

        # Count how many runs I'm going to start
        self.N_values = 1
        for i in self.n_values:
            self.N_values = self.N_values * i

    def infer_label(self, string, pattern, label_key):
        """
        Makes up a sensible label if none is specified.

        string should be something like the bp filename, a good start to the
        label. If it's absent, that doesn't matter.

        The label format is:
        string-key1@key1@-key2@key2...
        or
        key1@key1@-key2@key2...
        if string is absent.
        """
        label_bits = []
        if string:
            label_bits.append(string)
        for i in xrange(len(self.keys)):
            if len(self.list_values[i]) > 1:
                label_bits.append(
                    re.search(pattern, self.keys[i]).group(1) + 
                    self.keys[i])
        label = '-'.join(label_bits)
        # Add the label as a key-values pair to the weird data structure
        self.keys.append(label_key)
        self.list_values.append([label])
        self.n_values.append(1)
        self.keydict[label_key] = len(self.keys) - 1

    def replace(self, string):
        """
        Like string.replace() but replaces all tags and values for a given job

        """
        # self.values is assigned in default_loop
        for j in range(0, len(self.keys)):
            string = string.replace(self.keys[j], self.values[j])
        return string

def KeyValueSubValue(values, keydict, pattern):
    """
    This takes each key's value and expands the values by substituting in
    the values of other keys.
    """
    for j in range(len(values)):
        foundkey = re.search(pattern, values[j])
        # This is done iteratively so that it doesn't matter what order lines
        # appear in a bake parameter file
        while foundkey:
            values[j] = values[j].replace(
                foundkey.group(0), values[keydict[foundkey.group(0)]])
            foundkey = re.search(pattern, values[j])
    return None


def ItRunValues(grid, pattern, slice_start=0, slice_end=0):
    """
    This iterator returns one set of values for each run; one run is given
    by each iteration of ItRunValues. These values can then be subbed in for
    the corresponding keys.
    """
    if slice_end == 0:
        slice_end = grid.N_values
        #listi and values need to be initialized
    values = [0 for i in xrange(len(grid.keys))]
    for list_i in ItList_i(grid.n_values, slice_start, slice_end):
        # Pick the values to be used in this run
        for j in range(len(grid.keys)):
            values[j] = grid.list_values[j][list_i[j]]
        # Do the string replace operations on the values themselves
        KeyValueSubValue(values, grid.keydict, pattern)
        yield values


def ItList_i(n_values, slice_start, slice_end):
    """
    This is basically a thing that encloses ItList_iNoSlice and does the
    slicing math for it.
    """
    myItList_iNoSlice = ItList_iNoSlice(n_values)
    for i in xrange(0, slice_start):
        myItList_iNoSlice.next()
    for i in xrange(slice_start, slice_end):
        yield myItList_iNoSlice.next()


def ItList_iNoSlice(n_values):
    """
    Imagine an m-dimensional grid, where m is the number of keys; the width
    of the grid in one of these directions is the number of values for that
    key. This iterator walks through each location in that grid; list_i
    corresponds to a single position in that grid.
    """
    list_i = [0 for i in xrange(len(n_values))]
    yield list_i
    while True:
        j = 0
        while True:
            list_i[j] = list_i[j] + 1
            if list_i[j] == n_values[j]:
                list_i[j] = 0
                j = j + 1
            else:
                break
        yield list_i

@@ -2959,23 +2959,20 @@
 values, 
-keydict
+grid
 , patter
@@ -3406,16 +3406,21 @@
  values%5B
+grid.
 keydict%5B
@@ -3953,25 +3953,16 @@
 t_i(grid
-.n_values
 , slice_
@@ -4229,24 +4229,16 @@
 es, grid
-.keydict
 , patter
@@ -4276,24 +4276,20 @@
 tList_i(
-n_values
+grid
 , slice_
@@ -4461,24 +4461,20 @@
 NoSlice(
-n_values
+grid
 )%0A    fo
@@ -4643,24 +4643,20 @@
 NoSlice(
-n_values
+grid
 ):%0A    %22
@@ -4977,16 +4977,21 @@
 nge(len(
+grid.
 n_values
@@ -5127,16 +5127,21 @@
 i%5Bj%5D == 
+grid.
 n_values

Make it so that grid's members are only used as items in grid, not alone