#printtab1: This file prints the Table 1 based on Reactions.xml
#Tuomo Maki-Marttunen, 2019

from pylab import *
import scipy.io
import sys
import time

class bcolors:
  OKBLUE = '\033[94m'
  OKGREEN = '\033[92m'
  FAIL = '\033[91m'
  ENDC = '\033[0m'
  BOLD = '\033[1m'
  UNDERLINE = '\033[4m'

Reacs = []
Reactants = []
Reactant_powers = []
Products = []
Product_powers = []
Forwardrates = []
Backwardrates = []
reac_file = open('Reactions.xml')
firstline = reac_file.readline()
line = reac_file.readline()
nsAll = []
nsProdAll = []

print_reac_N = True
if len(sys.argv) > 1 and int(sys.argv[1]) == 0:
  print_reac_N = False

### Load reactions ###
toPrint = []
reacRatesToPrint = []
while len(line) > 0:
  if line.find('Reaction name') > -1 and line[0:4] != '<!--':
    newline = reac_file.readline()
    #print "newline = "+newline

    Reactants_this = []
    Products_this = []
    Forwardrate = 0
    Backwardrate = 0
    Powers = []
    PowersProd = []
    ns = []
    nsProd = []
    while newline.find('</Reaction>') == -1:
      if newline.find('Reactant specieID') > -1:
        Reactants_this.append(newline[newline.find('"')+1:newline.find('"')+newline[newline.find('"')+1:].find('"')+1])
        if newline.find('power') > -1:
          newline2 = newline[newline.find('power'):]
          Powers.append(int(newline2[newline2.find('"')+1:newline2.find('"')+newline2[newline2.find('"')+1:].find('"')+1]))
        else:
          Powers.append(1)
        if newline.find('n=') > -1:
          newline2 = newline[newline.find('n='):]
          ns.append(int(newline2[newline2.find('"')+1:newline2.find('"')+newline2[newline2.find('"')+1:].find('"')+1]))
        else:
          ns.append(Powers[-1])
      if newline.find('Product specieID') > -1 or newline.find('Product  specieID') > -1:
        Products_this.append(newline[newline.find('"')+1:newline.find('"')+newline[newline.find('"')+1:].find('"')+1])
        if newline.find('power') > -1:
          newline2 = newline[newline.find('power'):]
          PowersProd.append(int(newline2[newline2.find('"')+1:newline2.find('"')+newline2[newline2.find('"')+1:].find('"')+1]))
        else:
          PowersProd.append(1)
        if newline.find('n=') > -1:
          newline2 = newline[newline.find('n='):]
          nsProd.append(int(newline2[newline2.find('"')+1:newline2.find('"')+newline2[newline2.find('"')+1:].find('"')+1]))
        else:
          nsProd.append(PowersProd[-1])
      if newline.find('forwardRate') > -1:
        newline2 = newline[newline.find('>')+1:]
        Forwardrate = float(newline2[:newline2.find('<')])
      if newline.find('reverseRate') > -1:
        newline2 = newline[newline.find('>')+1:]
        Backwardrate = float(newline2[:newline2.find('<')])
      newline = reac_file.readline()
    Forwardrates.append(Forwardrate)
    Backwardrates.append(Backwardrate)
    Reactants.append(Reactants_this[:])
    Products.append(Products_this[:])
    Reactant_powers.append(Powers[:])
    Product_powers.append(PowersProd[:])
    nsAll.append(ns[:])
    nsProdAll.append(nsProd[:])
    Reac_txt = ''
    for i in range(0,len(Reactants_this)):
      if ns[i] != 1:
        Reac_txt = Reac_txt + str(ns[i])+"*"+Reactants_this[i]
      else:
        Reac_txt = Reac_txt + Reactants_this[i]
      if i < len(Reactants_this) - 1:
        Reac_txt = Reac_txt + " + "
    Reac_txt = Reac_txt + " $\\rightleftharpoons$ "
    for i in range(0,len(Products_this)):
      if nsProd[i] != 1:
        Reac_txt = Reac_txt + str(nsProd[i])+"*"+Products_this[i]
      else:
        Reac_txt = Reac_txt + Products_this[i]
      if i < len(Products_this) - 1:
        Reac_txt = Reac_txt + " + "
    addition = ""
    reacRatesToPrint.append([Forwardrate,Backwardrate])
    if ns == Powers and nsProd == PowersProd:
      toPrint.append(addition+Reac_txt+" & "+str(Forwardrate)+" & "+str(Backwardrate) + " & \\\\")
    elif ns != Powers:
      toPrint.append(addition+Reac_txt+" & "+str(Forwardrate)+" & "+str(Backwardrate) + " & $*$ \\\\")
    elif nsProd != PowersProd:
      toPrint.append(addition+Reac_txt+" & "+str(Forwardrate)+" & "+str(Backwardrate) + " & $\dagger$ \\\\")
    Reacs.append(Reac_txt)
  line = reac_file.readline()    
reac_file.close()

### Divide reactions to groups ###
toPrintHere = [[i] for i in range(0,len(toPrint))]
extraConditions = 2
for ireac in range(0,len(toPrint)):
  for ireacgone in range(0,ireac):
    if reacRatesToPrint[ireac][0] == reacRatesToPrint[ireacgone][0] and reacRatesToPrint[ireac][1] == reacRatesToPrint[ireacgone][1]:
      if extraConditions > 0:
        #Extra conditions:
        #1) There has to be same number of reactants and products
        if len(Reactants[ireac]) != len(Reactants[ireacgone]) or len(Products[ireac]) != len(Products[ireacgone]): 
          continue
        
      if extraConditions > 1:
        #2) There has to be a sequence of at least three letters that appears in the two corresponding reactants/products
        foundOne = False
        for wordpair in zip(Reactants[ireac]+Products[ireac],Reactants[ireacgone]+Products[ireacgone])+zip(Reactants[ireacgone]+Products[ireacgone],Reactants[ireac]+Products[ireac]):
          if (wordpair[0] == 'R' and 'R' in wordpair[1]) or (wordpair[0] == 'pR' and 'pR' in wordpair[1]) or (wordpair[0] == 'ppR' and 'ppR' in wordpair[1]):
            foundOne = True
          for itriple in range(0,len(wordpair[0])-3):
            if wordpair[0][itriple:itriple+3] in wordpair[1]:
              foundOne = True
              #print wordpair[0]+" has a triplet included in "+wordpair[1]
              break
        #Exceptions: don't let PKAc, if being the second product, be replaced by Gibg
        if len(Products[ireac]) == 2 and len(Products[ireacgone]) == 2 and (Products[ireac][1] == 'PKAc' and Products[ireacgone][1] == 'Gibg' or Products[ireac][1] == 'Gibg' and Products[ireacgone][1] == 'PKAc'):
          foundOne = False
        if not foundOne:
          continue
      if extraConditions > 2:
        #3) One of the reactants or products has to be included in the name of the corresponding reactant or product in the other reaction
        reacNamesOK = 0 
        for ireactant in range(0,len(Reactants[ireac])):
          if Reactants[ireac][ireactant] in Reactants[ireacgone][ireactant] or Reactants[ireacgone][ireactant] in Reactants[ireac][ireactant]:
            reacNamesOK = 1
        for iproduct in range(0,len(Products[ireac])):
          if Products[ireac][iproduct] in Products[ireacgone][iproduct] or Products[ireacgone][iproduct] in Products[ireac][iproduct]:
            reacNamesOK = 1
        if not reacNamesOK:
          continue

      if extraConditions > 3:
        #4) The length of the first (or nth) reactant (or product) has to be a fixed number of letters shorter or longer than the name of the first product (or reactant)
        lengthsOK = 0
        for iproduct in range(0,len(Products[ireac])):
          if len(Reactants[ireac][0]) - len(Reactants[ireacgone][0]) == len(Products[ireac][iproduct]) - len(Products[ireacgone][iproduct]):
            lengthsOK = 1
        for ireactant in range(0,len(Reactants[ireac])):
          if len(Products[ireac][0]) - len(Products[ireacgone][0]) == len(Reactants[ireac][ireactant]) - len(Reactants[ireacgone][ireactant]):
            lengthsOK = 1
        if not lengthsOK:
          continue

      #Only add the reaction if the reaction is printed where it stands
      if ireacgone in toPrintHere[ireacgone]:
        toPrintHere[ireacgone].append(ireac)
        toPrintHere[ireac] = []
      else: #otherwise, find where it is printed and print it there
        foundOne = False
        for ireacgone2 in range(0,ireacgone):
          if ireacgone in toPrintHere[ireacgone2]:
            foundOne = True
            break
        if foundOne:
          toPrintHere[ireacgone2].append(ireac)
          toPrintHere[ireac] = []          
      break


### Order the reactions by the groups they belong to ###
toPrintFinal = []
originalLocation = []
for ireac in range(0,len(toPrint)):
  for ireachere in range(0,len(toPrintHere[ireac])):
    toPrintFinal.append(toPrint[toPrintHere[ireac][ireachere]].replace('_','\_'))
    originalLocation.append(toPrintHere[ireac][ireachere])
originalLocationInv = []
for i in range(0,len(originalLocation)):
  originalLocationInv.append(find(array(originalLocation)==i)[0])

# Optional: print all reactions by the groups
#for ireac in range(0,len(toPrint)):
#  print str(ireac) + " (" + str(originalLocation[ireac])+") "+toPrintFinal[ireac]

print "\n"
print "\n"
print "\n"
diffCounter = 1
variables = []
variableNames = []
ReacsGrouped = []
for ireac in range(0,len(toPrintHere)):
  if len(toPrintHere[ireac]) == 0:
    continue
  if len(toPrintHere[ireac]) == 1:
    ReacsGrouped.append(toPrintFinal[originalLocationInv[ireac]])
    continue
  print "    ireac="+str(ireac)+", toPrintHere[ireac] = "+str(toPrintHere[ireac])
  thisReac = ''
  NsameLetters_all = []
  for iword in range(0,len(Reactants[toPrintHere[ireac][0]]) + len(Products[toPrintHere[ireac][0]])):
    NsameLetters = 0
    if iword < len(Reactants[toPrintHere[ireac][0]]):
      target = Reactants
      iiword = iword
    else:
      target = Products
      iiword = iword - len(Reactants[toPrintHere[ireac][0]])
    for iletter in range(0,100):
      allSame = True
      for ireac2 in range(1,len(toPrintHere[ireac])):
        if iletter >= min(len(target[toPrintHere[ireac][0]][iiword]),len(target[toPrintHere[ireac][ireac2]][iiword])) or target[toPrintHere[ireac][0]][iiword][iletter] != target[toPrintHere[ireac][ireac2]][iiword][iletter]:
          allSame = False
          break
      if allSame:
        NsameLetters = NsameLetters + 1
    #Exceptions: If the common string is short and cuts the name of the species at an awkward place, then use 0 as NsameLetters. Other exceptions: make shorter NsameLetters
    if target[toPrintHere[ireac][0]][iiword][0:NsameLetters] in ['AC','C','G','P']:
      NsameLetters = 0
    elif target[toPrintHere[ireac][0]][iiword][NsameLetters-1] == '_':
      NsameLetters = NsameLetters-1
    if target[toPrintHere[ireac][0]][iiword][0:NsameLetters] == 'PP2BCaMCa':
      NsameLetters = 4
    if target[toPrintHere[ireac][0]][iiword][0:NsameLetters] == 'AC1GsaGT':
      NsameLetters = 6
    if 'GluR' in target[toPrintHere[ireac][0]][iiword][0:NsameLetters] and target[toPrintHere[ireac][0]][iiword][NsameLetters-2:NsameLetters] == '_S':
      NsameLetters = NsameLetters - 2
    NsameLetters_all.append(NsameLetters)

    #Check if the word is same in all reactions, i.e., if NsameLetters is the same as maximum length along the targets:
    if NsameLetters == max([len(target[toPrintHere[ireac][j]][iiword]) for j in range(0,len(toPrintHere[ireac]))]):
      thisReac = thisReac + target[toPrintHere[ireac][j]][iiword] + ' + '
      if iword == len(Reactants[toPrintHere[ireac][0]]) - 1:
        thisReac = thisReac[0:-2] + '$\\rightleftharpoons$ '
      continue

    #Check if the word is the same as one of the previous words:
    iwordsame = -1
    for iword2 in range(0,iword):
      allSame = True
      if iword2 < len(Reactants[toPrintHere[ireac][0]]):
        target2 = Reactants
        iiword2 = iword2
      else:
        target2 = Products
        iiword2 = iword2 - len(Reactants[toPrintHere[ireac][0]])
      NsameLetters2 = NsameLetters_all[iword2]
      for j in range(0,len(toPrintHere[ireac])):
        if target[toPrintHere[ireac][j]][iiword][NsameLetters:] != target2[toPrintHere[ireac][j]][iiword2][NsameLetters2:]:
          allSame = False
      if allSame:
        iwordsame = iword2
        break
      
    if iwordsame > -1:
      thisReac = thisReac + target[toPrintHere[ireac][0]][iiword][0:NsameLetters] + '$\mathbf{'+chr(ord('X')+iwordsame)+'}_{'+str(diffCounter)+'}$ + '      
      if iword == len(Reactants[toPrintHere[ireac][0]]) - 1:
        thisReac = thisReac[0:-2] + '$\\rightleftharpoons$ '
      continue

    thisReac = thisReac + target[toPrintHere[ireac][0]][iiword][0:NsameLetters] + '$\mathbf{'+chr(ord('X')+iword)+'}_{'+str(diffCounter)+'}$ + '      
    if iword == len(Reactants[toPrintHere[ireac][0]]) - 1:
      thisReac = thisReac[0:-2] + '$\\rightleftharpoons$ '
    variablesThis = []
    for ireac2 in range(0,len(toPrintHere[ireac])):
      if len(target[toPrintHere[ireac][ireac2]][iiword][NsameLetters:]) == 0:
        variablesThis.append('$\\{\\}$')
      else:
        variablesThis.append(target[toPrintHere[ireac][ireac2]][iiword][NsameLetters:])
    variables.append(variablesThis[:])
    variableNames.append('$\mathbf{'+chr(ord('X')+iword)+'}_{'+str(diffCounter)+'}$')
    targetNames = ''
    for i in range(0,len(toPrintHere[ireac])):
      targetNames = targetNames + target[toPrintHere[ireac][i]][iiword] + ', '
    #print "thisReac = "+str(thisReac)+", NsameLetters = "+str(NsameLetters)+" ("+targetNames[0:-2]+")"
    #time.sleep(0.5)

  ns = nsAll[toPrintHere[ireac][0]]
  nsProd = nsProdAll[toPrintHere[ireac][0]]
  Powers = Reactant_powers[toPrintHere[ireac][0]]
  PowersProd = Product_powers[toPrintHere[ireac][0]]
             
  reacStart = thisReac[0:-2].replace('_{',':::{').replace('_','\_').replace(':::{','_{')   #Change all '_'s to '\_'s except when followed by '{'
  if ns == Powers and nsProd == PowersProd:
    thisReac = reacStart + ' & '+str(Forwardrates[toPrintHere[ireac][0]])+' & '+str(Backwardrates[toPrintHere[ireac][0]]) + ' & \\\\'
  elif ns != Powers:
    thisReac = reacStart + ' & '+str(Forwardrates[toPrintHere[ireac][0]])+' & '+str(Backwardrates[toPrintHere[ireac][0]]) + ' & $*$ \\\\'
  elif nsProd != PowersProd:
    thisReac = reacStart + ' & '+str(Forwardrates[toPrintHere[ireac][0]])+' & '+str(Backwardrates[toPrintHere[ireac][0]]) + ' & $\dagger$ \\\\'

  ReacsGrouped.append(thisReac)

  diffCounter = diffCounter + 1

print ""
print "VARIABLES PRINTED WHERE THEY BELONG (not used in the article):"
ReacLens = [len(x) for x in ReacsGrouped]
for ireac in range(0,len(ReacsGrouped)):
  print ReacsGrouped[ireac]
  icheck = 0                                              # Check the numbers of 'mathbf's in the ReacsGrouped.
  while ReacsGrouped[ireac][icheck:].find('mathbf') > -1: # This is important as we want to get an idea how long the text is in PDF so we can divide to two rows if needed.
    ReacLens[ireac] = ReacLens[ireac] - 13                # Each mathbf means an extra 13 characters that don't affect the length.
    icheck = icheck + ReacsGrouped[ireac][icheck:].find('mathbf') + 1
  for ivariable in range(0,len(variables)):
    if variableNames[ivariable] in ReacsGrouped[ireac]:
      myText = '  '+variableNames[ivariable]+' $\in$ $\\{$'
      for ivar in range(0,len(variables[ivariable])):
        myText = myText + bcolors.OKBLUE + variables[ivariable][ivar] + bcolors.ENDC + ', '
      myText = myText[0:-2]+'$\\}$'
      print myText
      
ReacsLined = []
print ""
print ""
print "VARIABLES PRINTED IN THE END (Table 1)"
for ireac in range(0,len(ReacsGrouped)):
  if ReacLens[ireac] > 80:
    ind = ReacsGrouped[ireac].find('$\\right')
    ReacsLined.append(str(ireac+1)+' & '+ReacsGrouped[ireac][0:ind] + ' & & & \\\\')
    ReacsLined.append(' &   '+ReacsGrouped[ireac][ind:])
  else:
    ReacsLined.append(str(ireac+1)+' & '+ReacsGrouped[ireac])

for ireacper2 in range(0,(len(ReacsLined)+1)/2):
  ind = ReacsLined[ireacper2].find('\\\\')
  if ireacper2 + (len(ReacsLined)+1)/2 < len(ReacsLined):
    secondLine = ReacsLined[ireacper2 + (len(ReacsLined)+1)/2]
  else:
    secondLine = ' & \\\\'
  if ind > -1:
    print ReacsLined[ireacper2][0:ind] + ' '*(115-len(ReacsLined[ireacper2][0:ind]))+'& ' + secondLine
  else:
    print ReacsLined[ireacper2] + ' '*(115-len(ReacsLined[ireacper2]))+'& ' + secondLine
  

print ""
ivariable = 0
varLines = []
while ivariable < len(variables):
  if not 'X' in variableNames[ivariable]:
    print 'error, variableNames[ivariable]='+str(variableNames[ivariable])
  NvarsSameReac = 1
  while ivariable + NvarsSameReac < len(variables) and 'X' not in variableNames[ivariable+NvarsSameReac]:
    NvarsSameReac = NvarsSameReac + 1
  if NvarsSameReac == 1:
    myText = variableNames[ivariable]+' $\in$ $\\{$'
    for ivar in range(0,len(variables[ivariable])):
      myText = myText + variables[ivariable][ivar].replace('_','\_') + ', '
    myText = myText[0:-2]+'$\\}$'
    varLines.append(myText)
  else:
    myText = '('
    for ivar in range(0,NvarsSameReac):
      myText = myText + variableNames[ivariable+ivar] + ', '
    myText = myText[0:-2] + ') $\in$ $\\{$ ('
    for ivar in range(0,len(variables[ivariable])):
      myText = myText[0:-2]+' ('
      for ivarX in range(0,NvarsSameReac):
        myText = myText + variables[ivariable+ivarX][ivar].replace('_','\_') + ', '
      myText = myText[0:-2]+'), ('
    myText = myText[0:-3]+' $\\}$'
    varLines.append(myText)
  ivariable = ivariable + NvarsSameReac

varsLined = []
VarLens = [len(x) for x in varLines]
for ivar in range(0,len(varLines)):
  varLines[ivar] = varLines[ivar]+'\\\\'

for ivar in range(0,len(varLines)):
  myLine = varLines[ivar]
  while len(myLine) > 0:
    nMathbf = 0
    toDoLater = ''
    icheck = 0                                             # Check the numbers of 'mathbf's in the varLines.
    while myLine[icheck:].find('mathbf') > -1:             # This is important as we want to get an idea how long the text is in PDF so we can divide to two rows if needed.
      nMathbf = nMathbf + 1                                # Each mathbf means an extra 13 (here 18) characters that don't affect the length.
      icheck = icheck + myLine[icheck:].find('mathbf') + 1
    while len(myLine) > 72 + 13*nMathbf:
      s = myLine[:]
      ind = len(s)-1-(s[::-1].find(','))
      toDoLater = myLine[ind:]+toDoLater
      myLine = myLine[0:ind]
      nMathbf = 0
      icheck = 0                                             # Check the numbers of 'mathbf's in the varLines.
      while myLine[icheck:].find('mathbf') > -1:             # This is important as we want to get an idea how long the text is in PDF so we can divide to two rows if needed.
        nMathbf = nMathbf + 1                                # Each mathbf means an extra 13 (here 18) characters that don't affect the length.
        icheck = icheck + myLine[icheck:].find('mathbf') + 1
    if len(toDoLater) > 0 and toDoLater[0] == ',':
      varsLined.append(myLine+',\\\\')
      myLine = toDoLater[1:]
    else:
      varsLined.append(myLine)
      myLine = toDoLater


for ivarper2 in range(0,(len(varsLined)+1)/2):
  ind = varsLined[ivarper2].find('\\\\')
  if ivarper2 + (len(varsLined)+1)/2 < len(varsLined):
    secondLine = varsLined[ivarper2 + (len(varsLined)+1)/2]
  else:
    secondLine = '\\\\'
  if ind > -1:
    print varsLined[ivarper2][0:ind] + ' '*(96-len(varsLined[ivarper2][0:ind]))+' & ' + secondLine
  else:
    print varsLined[ivarper2] + ' '*(96-len(varsLined[ivarper2]))+' & ' + secondLine