from __future__ import division
import copy,re,os,time,commands,signal,exceptions,math
import traceback
from labelit.exception import MOSFLM_Warning
from labelit.mathsupport import length
from labelit.preferences import labelit_commands

class MosflmVersionError(exceptions.Exception):pass

vertest = """#!/bin/csh -f
%(executable)s <<eof > %(scheme_id)s.out
EXIT
eof
"""

class KnownMosflmVersion:
  def __init__(self):
    self.known_mosflm_version = ''

  def __call__(self):
    if self.known_mosflm_version == '' and \
       labelit_commands.index_only==0:
      try:
        from labelit.command_line.default_param import mo
        inputd ={"executable":mo,"scheme_id":'vertest'}
        constructAndExecute(vertest,'vertest',inputd)
        retrieveLast(ver1,'vertest'+".out",inputd)
        self.known_mosflm_version = inputd['version'] #only works for version numbers of like format
      except Exception:
        self.known_mosflm_version = '0'
    return self.known_mosflm_version

known_mosflm_version = KnownMosflmVersion() #singleton

mosflm_623 = {
'look1' : """#!/bin/csh -f
%(executable)s \
SPOTOD /dev/null summary %(scheme_id)s.sum <<eof
TITLE %(scheme_id)s initial autoindexing trial for one frame

DIRECTORY %(directory)s
TEMPLATE %(template)s
HKLOUT %(scheme_id)s.mtz
IMAGE 1

%(mosflm_detector)s

GENFILE %(scheme_id)s.gen
NEWMAT %(scheme_id)s.mat

BEAM %(xbeammosflm)s %(ybeammosflm)s

WAVE %(wavelength)s
%(mosflm_beamline)s
MOSAICITY %(mosaicity)s

DISTANCE %(distancemosflm)s
RESOLUTION %(resolution)s
TWOTHETA 0.0

RUN

EXIT
eof
""",

'scheme4' : """#!/bin/csh -f
%(executable)s \
COORDS %(scheme_id)s.coords SPOTOD %(scheme_id)s.spotod summary %(scheme_id)s.sum <<eof > %(scheme_id)s.out
TITLE %(scheme_id)s two-wedge integration

DIRECTORY %(directory)s
TEMPLATE %(template)s
HKLOUT %(mtzsubfile)s.mtz
%(mosflm_detector)s

GENFILE %(scheme_id)s.gen

BEAM %(xbeammosflm)s %(ybeammosflm)s

WAVE %(wavelength)s
%(mosflm_beamline)s
MOSAICITY %(mosaicity)s

DISTANCE %(distancemosflm)s
RESOLUTION %(resolution)s
TWOTHETA 0.0
SYMMETRY %(spacegroup)s

PROFILE OVERLOAD PARTIALS %(opt_fixbox)s
%(opt_raster)s
%(opt_separation)s
REFINEMENT INCLUDE PARTIALS %(special_resid)s
POSTREF FIX ALL

PROCESS %(procstart)s TO %(procend)s
MATRIX %(prior_scheme_id)s.mat
RUN

PROCESS %(procstart2)s TO %(procend2)s
MATRIX %(prior_scheme_id)s_S.mat
RUN

EXIT

eof

""",
'scheme4M' : """#!/bin/csh -f
%(executable)s \
COORDS %(scheme_id)s.coords SPOTOD %(scheme_id)s.spotod summary %(scheme_id)s.sum <<eof > %(scheme_id)s.out
TITLE %(scheme_id)s one-wedge integration

DIRECTORY %(directory)s
TEMPLATE %(template)s
HKLOUT %(mtzsubfile)s.mtz
%(mosflm_detector)s

GENFILE %(scheme_id)s.gen
NEWMAT %(scheme_id)s.mat

BEAM %(xbeammosflm)s %(ybeammosflm)s

WAVE %(wavelength)s
%(mosflm_beamline)s
MOSAICITY %(mosaicity)s

DISTANCE %(distancemosflm)s
RESOLUTION %(resolution)s
TWOTHETA 0.0
SYMMETRY %(spacegroup)s
MATRIX %(prior_scheme_id)s.mat

PROFILE OVERLOAD PARTIALS %(opt_fixbox)s
%(opt_raster)s
%(opt_separation)s
REFINEMENT INCLUDE PARTIALS %(special_resid)s
POSTREF FIX ALL

PROCESS %(procstart)s TO %(procend)s
NEWMATRIX %(scheme_id)s.mat
RUN
EXIT

eof

""",
'scala_integration' : """#!/bin/csh -f
%(executable)s COORDS scala_integration.coords summary scala_integration.sum <<eof > scala_integration.out
TITLE full integration

DIRECTORY %(directory)s
TEMPLATE %(template)s
HKLOUT scala_integration.mtz
GENFILE scala_integration.gen
%(mosflm_detector)s

NUSPOT OFF
BEAM %(xbeammosflm)s %(ybeammosflm)s
DISTANCE %(distancemosflm)s
TWOTHETA 0.0

WAVE %(wavelength)s
%(mosflm_beamline)s

%(opt_separation)s
MOSAICITY %(mosaicity)s
SYMMETRY %(symm)s
RESOLUTION %(rsymop_resol)s
MATRIX scala_integration.mat

PROFILE OVERLOAD PARTIALS
%(opt_raster)s
REFINEMENT INCLUDE PARTIALS %(special_resid)s
POSTREF FIX ALL

PROCESS %(procstart)s TO %(procend)s
RUN

EXIT
eof

""",
'scala_postrefine':"""#!/bin/csh -f
cat > scala_postrefine.mat <<eof-cat
%(newmat)seof-cat

%(executable)s coords scala_postrefine.coords summary scala_postrefine.sum <<eof > scala_postrefine.out
TITLE postrefinement using two segments

DIRECTORY %(directory)s
TEMPLATE %(template)s
HKLOUT scala_postrefine.mtz
GENFILE scala_postrefine.gen
%(mosflm_detector)s

NUSPOT OFF
BEAM %(xbeammosflm)s %(ybeammosflm)s
DISTANCE %(distancemosflm)s
TWOTHETA 0.0

WAVE %(wavelength)s
%(mosflm_beamline)s

%(opt_separation)s
MOSAICITY %(mosaicity)s
SYMMETRY %(symm)s
RESOLUTION %(rsymop_resol)s
MATRIX scala_postrefine.mat

NEWMATRIX scala_integration.mat

PROFILE OVERLOAD
%(opt_raster)s

POSTREF MULTI SEGMENT 2
PROCESS %(procstart)s TO %(procend)s
RUN

PROCESS %(procstart2)s TO %(procend2)s
RUN
EXIT

eof
""",
'scala_nopostrefine':"""#!/bin/csh -f
cat > scala_integration.mat <<eof-cat
%(newmat)seof-cat
""",
'scala_completeness':"""#!/bin/csh -f
%(executable)s summary scala_completeness.sum <<eof > scala_completeness_%(solution)s_%(group)s.out
TITLE predict the completeness of the partial dataset

DIRECTORY %(directory)s
TEMPLATE %(template)s
%(mosflm_detector)s

BEAM %(xbeammosflm)s %(ybeammosflm)s
DISTANCE %(distancemosflm)s
TWOTHETA 0.0

WAVE %(wavelength)s
%(mosflm_beamline)s

SYMMETRY %(symm)s
RESOLUTION %(rsymop_resol)s
MATRIX index%(solution)s.mat

IMAGE %(procstart)s
go

STRATEGY START %(procstart)s END %(procend)s
go
RUN 1
go
EXIT

eof
""",
'web_integration' : """#!/bin/csh -f
cat > integration%(cnt)s.mat <<eof-cat
%(newmat)seof-cat

%(executable)s summary integrate%(cnt)s.sum <<eof
# This unix script assumes MOSFLM 6.2.3 or higher
# It is assumed that the user has the program %(executable)s in the path
TITLE integration script starting with labelit model
# Upon running this script, the user is advised to refine the cell first
#  (assuming several images are available), and then proceed with integration.

DIRECTORY %(directory)s
TEMPLATE %(template)s
IMAGE %(procstart)s
HKLOUT integration%(cnt)s.mtz
GENFILE integration%(cnt)s.gen
%(mosflm_detector)s

NUSPOT OFF
BEAM %(xbeammosflm)s %(ybeammosflm)s
DISTANCE %(distancemosflm)s
TWOTHETA 0.0

WAVE %(wavelength)s
%(mosflm_beamline)s

MOSAICITY %(mosaicity)s
SYMMETRY %(symm)s
RESOLUTION %(resol)s
MATRIX integration%(cnt)s.mat

PROFILE OVERLOAD PARTIALS
%(opt_raster)s
%(opt_separation)s
REFINEMENT INCLUDE PARTIALS %(special_resid)s

RUN

EXIT
eof

"""
}

mosflm_624 = copy.deepcopy(mosflm_623)
for key in mosflm_624.keys():
  mosflm_624[key] = mosflm_624[key].replace("NUSPOT OFF\n","")

#result dictionary regular expressions

rd1 = {}
rd1["r_goodspots"] = r'A total of\s+([0-9]+) spots were written to file'
rd1["r_cell"] = r'Final cell \(after refinement\) is\s+([0-9A-Z. ]+)'
rd1["r_spacegroup"] = r'with\s+([0-9A-Z]+)\s+\(number'
rd1["r_solution"] = r'solution #\s*([0-9]+)\swith'

rd5 = {}
rd5["r_xbeam"] = r'have been refined to\s+([0-9.]+)'
rd5["r_ybeam"] = r'have been refined to\s+(?:[0-9.]+)\s+([0-9.]+)'

rd2 = {}
rd2["r_mosaicity"] = r'MOSAIC\)\s+([0-9.]+)'
rd2["r_xbeam"] = r'XCEN[^0-9\-]+([0-9.\-]+)'
rd2["r_ybeam"] = r'YCEN[^0-9\-]+[0-9.\-]+\s+([0-9.\-]+)'
rd2["r_distance"] = r'XTOFD[^0-9]+[0-9.]+\s+[0-9.]+\s+[0-9.]+\s+([0-9.]+)'
rd2["r_residual"] = r'Final rms residual:\s+([0-9.]+)'
rd2["r_highres"] = r'Resolution Range[^\-]+-\s+([0-9.]+)'

ver1 = {}
ver1["version"] = r'[mM]osflm [vV]ersion ([0-9.]+)'

rd2m = {}
rd2m["r_mosaicity"] = r'Mosaic spread\s+New\s+([0-9.]+)'

rd4 = copy.deepcopy(rd2)
rd4["r_box"] = r'parameters have been set to\s+([0-9.]+\s+[0-9.]+\s+[0-9.]+\s+[0-9.]+\s+[0-9.]+)'
rd4["r_separation"] = r'parameters \(in X and Y\) have been set to\s+([0-9.]+\s+[0-9.]+)'

class floatrange:
  def __init__(self,lower,upper,stride):
    self.current = lower-stride
    self.upper = upper
    self.stride = stride
    self.epsilon = 0.00000000001
  def __getitem__(self,i):
    self.current += self.stride
    if self.current-self.epsilon>self.upper: raise IndexError
    return self.current

def force_debug(message):
  DEBUG=1
  if DEBUG:
    f = open(os.path.join("spawn"),"a")
    f.write(time.strftime("%a %b %d %H:%M:%S %Y"))
    f.write(" pid:%d ppid:%d"%(os.getpid(),os.getppid()))
    f.write(" %s\n"%message)
    f.close()

def safe_exec(command, logfile):
  from libtbx.subprocess_with_fixes import Popen # FIXME use libtbx.easy_run
  #DEFAULT implementation:
  #os.system(command)
  #return
  command = os.path.join(os.getcwd(),command)
  force_debug(command)
  #x = os.spawnv(os.P_NOWAIT, command, [command,])
  #processid = x # fixes case where processreport < 2
  processid = Popen([command,command]).pid
  force_debug("Interface just spawned %d"%processid)
  timer = 0
  while (1):
    time.sleep(0.5)
    timer+=1
    if os.environ.has_key("OSTYPE") and "darwin" in os.environ["OSTYPE"]:
      ps_command = "ps -A |grep %d |grep -v grep|grep -v \(csh\)"
    else:
      ps_command = "ps -ef|grep %d |grep -v grep|grep -v \(csh\)|grep -v defunct"
    y = commands.getoutput( ps_command%(processid,) )
    processreport = y.split("\n")
    alive_status = True in [command in p for p in processreport]
    if not alive_status:
      force_debug("The mosflm job has ended")
      test_condition = 0
      break
    else:
      #Mosflm job still going.  Monitor progress every few seconds.
      if (timer%20==0):
        #Find out if there is a known pathology
        #Case 4.  Detect log file overflow
        test_condition = "MOSFLM logfile overflow"
        size = os.stat(logfile)[6]
        if size > 10000000: # 10 million byte log file must be in error!
          force_debug( "detected MOSFLM log file too long")
          try: # a valiant attempt to kill a runaway mosflm job
            # The process id running mosflm
            mosflm_job = int(
            commands.getoutput("ps -ef|grep %d|awk '($3==%d){print $2}' "%(
                                         processid,processid))
            )
            os.kill(mosflm_job,signal.SIGINT)
          except Exception: pass
          break
        #Case 5.  Something really bad is happening.  MOSFLM is hung, but there is
        #         no clean way to detect this by parsing the output.  Kill the
        #         job if it's been idle (no output) for more than TIMELIMIT seconds
        test_condition =  "MOSFLM time limit"
        TIMELIMIT = 240
        modtime = os.stat(logfile)[8]
        nowtime = time.time()
        elapsed = nowtime-modtime
        if elapsed>TIMELIMIT:
          force_debug("apparently %f seconds elapsed without any new MOSFLM output"%elapsed)
          try:
            # for debugging:
            #print commands.getoutput('''ps -Ao user,pid,ppid,pgid,stime,args''')
            mosflm_job = int(
            commands.getoutput("ps -ef|grep %d|awk '($3==%d){print $2}' "%(
                                         processid,processid))
            )
            os.kill(mosflm_job,signal.SIGINT)
          except Exception: pass
          break

  try:
    force_debug("WaitAttempt process %d"%processid)
    waitpid,status = os.waitpid(processid,os.WNOHANG)
    force_debug("WaitSuccess, status is %d %d"%(processid,status))
  except Exception:
    force_debug("No such child process %d"%processid)
  if test_condition!=0: raise MOSFLM_Warning(test_condition)

def constructAndExecute(scheme,filename,newpd):
  output = mosflmtrap(scheme,newpd)
  f = open(newpd["scheme_id"],"w")
  f.write(output)
  f.close()
  os.chmod(newpd["scheme_id"],0744)
  #os.system(newpd["scheme_id"])
  safe_exec(newpd["scheme_id"],newpd["scheme_id"]+".out")

def safeopen(filename):
  if not os.path.exists(filename):
    raise MOSFLM_Warning("MOSFLM logfile file not found")
  if not os.path.isfile(filename):
    raise MOSFLM_Warning("MOSFLM logfile not regular file")
  size = os.stat(filename)[6]
  cyclethru=0
  while 1:
    cyclethru+=1
    if cyclethru > 20: raise MOSFLM_Warning("MOSFLM logfile not closed")
    #print size
    #apparently (in Redhat 8 / Python 2.3) the file may not be closed; keep cycling until size stabilizes
    oldsize = copy.deepcopy(size)
    time.sleep(0.50) #tried 0.25 sec; wasn't sufficient on RH8 Web server
    size = os.stat(filename)[6]
    if size==oldsize: break
  if size <= 0:
    raise MOSFLM_Warning("MOSFLM logfile file size 0")
  if size > 10000000: # 10 million byte log file must be in error!
    raise MOSFLM_Warning("MOSFLM logfile file too big")
  r = commands.getoutput(
          'cat %s | grep -i "FATAL ERROR"'%(filename,))
  if len(r) >= 10:
    raise MOSFLM_Warning("MOSFLM logfile declares FATAL ERROR")
  return open(filename,"r")

def retrieveLast(rd,filename,pd,force=0):
  g = safeopen(filename)
  rfile = g.read()
  g.close()
  for item in rd.keys():
    pattern = re.compile(rd[item])
    match = pattern.findall(rfile)
    if match==[]:
      if force==0:
        pd[item]=None
      else:
        #traceback.print_stack()
        raise MOSFLM_Warning("MOSFLM does not give expected results on %s for file %s"%(item,filename),pd)
    else:
      pd[item]=match[len(match)-1]

class fastread:
  #Gives readline method that throws exception at end of file
  def __init__(self,filename):
    self.g = safeopen(filename)
    self.lines = self.g.xreadlines()
    self.count = -1
  def readline(self):
    self.count+=1
    return self.lines[self.count]
  def __del__(self):
    self.g.close()

warn709="""Warning: Use caution when running Labelit with Mosflm 7.0.9, as Labelit's interface to Mosflm
fails in some cases with v.7.0.9.  Temporary workaround is to use an earlier Mosflm."""
printwarn709=True

def get_template(scriptname):
  global known_mosflm_version,printwarn709
  if known_mosflm_version() == '0': raise MosflmVersionError("Can't run MOSFLM")
  elif known_mosflm_version() < '6.2.4':
    raise MosflmVersionError('mosflm versions prior to 6.2.4 not supported')
  elif known_mosflm_version() == '6.2.4':
    return mosflm_624[scriptname]
  elif known_mosflm_version() in ['6.2.5','6.2.6','7.0.0','7.0.1','7.0.2',
    '7.0.3','7.0.4','7.0.5','7.0.6','7.0.7','7.0.8','7.0.9','7.1.0']:
    if known_mosflm_version() == "7.0.9" and printwarn709: print warn709; printwarn709=False
    return mosflm_624[scriptname]
  elif known_mosflm_version() > '7.1.0':
    return mosflm_624[scriptname]
    raise MosflmVersionError('mosflm versions after 7.1.0 not supported')

def get_template_quick(scriptname):
  global known_mosflm_version
  if known_mosflm_version() < '6.2.4':
    raise MosflmVersionError('mosflm versions prior to 6.2.4 not supported')
  elif known_mosflm_version() == '6.2.4':
    return mosflm_624[scriptname]
  elif known_mosflm_version() in ['6.2.5','6.2.6','7.0.0','7.0.1','7.0.2',
    '7.0.3','7.0.4','7.0.5','7.0.6','7.0.7','7.0.8','7.0.9','7.1.0']:
    return mosflm_624[scriptname]
  elif known_mosflm_version() > '7.1.0':
    return mosflm_624[scriptname]
    raise MosflmVersionError('mosflm versions after 7.1.0 not supported')

def write_lookat(frame1,parameterdict):  #construct the look script
  pd1a = copy.deepcopy(parameterdict)
  pd1a["scheme_id"] = "look1"
  output = mosflmtrap(get_template('look1'),pd1a)
  f = open("lookat","w")
  f.write(output)
  f.close()
  os.chmod("lookat",0744)

def mosflmtrap(scriptstring,rundict):
  '''for MarIP, realize that certain transformations are needed when
  porting the dictionary information to mosflm; which should be localized
  in this file'''
  if scriptstring==vertest:
    return scriptstring%rundict
  try:
   # this forces the recalculation of the beam center for non-zero two theta
   if 'xbeammosflm' in rundict.keys():
     del rundict['xbeammosflm']
     del rundict['ybeammosflm']
   if 'xbeammosflm' not in rundict.keys():# it's there for rsymop, not index
    from labelit.detectors.convention import LABELITtoMosflm
    transform_function = LABELITtoMosflm(rundict).transformer()
    labelitbeam = (float(rundict['xbeam']),float(rundict['ybeam']))
    mosflmbeam = transform_function(labelitbeam)
    #fix beam for non-zero two-theta
    if float(rundict['twotheta'])!=0.0:
      # "NON ZERO TWO THETA" adjustment of beam
      # code is currently specific for ADSC/Mar CCD
      twotheta = float(rundict['twotheta'])
      mosflmbeam=list(mosflmbeam)
      mosflmbeam[0]+=float(rundict['distance'])*math.tan(
        twotheta*math.pi/180.)

    rundict['xbeammosflm']='%f'%mosflmbeam[0]
    rundict['ybeammosflm']='%f'%mosflmbeam[1]
   rundict['distancemosflm']='%f'%(float(rundict['distance']))
  except Exception:
    import traceback
    traceback.print_exc()
  limits_fix(rundict)
  constructed_script = scriptstring%rundict
  constructed_script = start_and_angle_fix(constructed_script,rundict)
  return modify_for_BEST(constructed_script,rundict)

def mosflm_info_setup(info_dict,index_engine):
  '''take an INFO dict and an AutoIndexEngine as input,
  and return particular keys required for running MOSFLM.'''
  first_pass = {
           'executable':'ipmosflm',
           'size1':info_dict['size1'],
           'pixel_size':info_dict['pixel_size'],
           'vendortype':info_dict['vendortype'],
           'template':info_dict['template'],
           'mosflm_detector':info_dict['mosflm_detector'],
           'mosflm_beamline':info_dict['mosflm_beamline'],
           'opt_raster':info_dict['opt_raster'],
           'opt_separation':info_dict['opt_separation'],
           'special_resid':info_dict['special_resid'],
           'wavelength':'%.6f'%index_engine.wavelength,
  }
  from labelit.detectors.convention import LABELITtoMosflm
  transform_function = LABELITtoMosflm(info_dict).transformer()
  labelitbeam = (index_engine.xbeam(),index_engine.ybeam())
  mosflmbeam = transform_function(labelitbeam)
  first_pass['xbeammosflm'] = '%.6f'%mosflmbeam[0]
  first_pass['ybeammosflm'] = '%.6f'%mosflmbeam[1]
  limits_fix(first_pass)
  return first_pass

class limits_fix_engine:
  '''Calculate the MOSFLM LIMITS XMAX and YMAX based on the square geometry of
  the detector, for the Mar CCD, and the known MOSFLM beam center'''
  def __init__(self):
    self.px_exp = re.compile(r'UIS_PIXEL ([.0-9]+)')
    self.sz_exp = re.compile(r'UIS_SIZE ([.0-9]+)')
  def process_tags(self,first_pass):
    if first_pass['mosflm_detector'].find('#MARCCD detector')==0:
      if first_pass['mosflm_detector'].find('xmax_tag')>0:#i.e., test to see if we've done this already
        first_pass['mosflm_detector'] = self.rawprocess(first_pass)
  def rawprocess(self,first_pass):
    try:
        self.xbeam = float(first_pass['xbeammosflm'])
        self.ybeam = float(first_pass['ybeammosflm'])
        match = self.px_exp.findall(first_pass['mosflm_detector'])
        px = float(match[0])
        match = self.sz_exp.findall(first_pass['mosflm_detector'])
        sz = float(match[0])
        self.detector_width = px*sz
        xmax = max(self.xbeam,self.detector_width-self.xbeam)
        ymax = max(self.ybeam,self.detector_width-self.ybeam)
        return first_pass['mosflm_detector'].replace(
          'xmax_tag','%.2f'%xmax).replace('ymax_tag','%.2f'%ymax)
    except Exception: #for backward compatibility
        return first_pass['mosflm_detector']
  def corners(self):
    #coordinates of the four detector corners relative to the beam (mm)
    return ((-self.xbeam,-self.ybeam),
            (-self.xbeam,self.detector_width-self.ybeam),
            (self.detector_width-self.xbeam,-self.ybeam),
            (self.detector_width-self.xbeam,self.detector_width-self.ybeam))
  def edges(self):
    #coordinates of the four detector edges relative to the beam (mm)
    return ((-self.xbeam+(self.detector_width/2.),-self.ybeam),
            (-self.xbeam,(self.detector_width/2.) - self.ybeam),
            (self.detector_width-self.xbeam,(self.detector_width/2.)-self.ybeam),
            ((self.detector_width/2.) - self.xbeam,self.detector_width-self.ybeam))

XE = limits_fix_engine()

def limits_fix(first_pass):
  XE.process_tags(first_pass)

def start_and_angle_fix(script,rundict):
  if script.find('detector raxis')>=0 or script.find("BIOCARS 14-BM-C S/N=910")>=0 \
  or script.find('detector Pilatus-6M')>=0 \
  or labelit_commands.image_specific_osc_start != None:
    if script.find('detector raxis')>=0:
      print "For Raxis-II, LABELIT and MOSFLM use differing coord. systems for direct beam"
    for line in script.split('\n'):
      if line.find('PROCESS')==0:
        framekey = int(line.split(' ')[1])
        # for postrefine, framekey may not have been an indexing frame
        if not rundict.has_key("osc_start"):continue # for use with Pilatus & compatibility_file=
        if framekey in rundict['osc_start'].keys():
          startangle = float(rundict['osc_start'][framekey])
        else:
          frameone = rundict['osc_start'].keys()[0]
          startone = float(rundict['osc_start'][frameone])
          startangle = startone + (framekey - frameone) * float(rundict['deltaphi'])

        script = script.replace(line,'%s START %f ANGLE %f\n'%(
          line.strip(),startangle,
          float(rundict['deltaphi'])))
      if line.find('integration script')>0:
        script = script.replace(line,line+" with PHI range fix")
        for i_line in script.split('\n'):
          if i_line.find('IMAGE')==0:
            framekey = int(i_line.split(' ')[1])
            script = script.replace(i_line,'%s PHI %f TO %f\n'%(
              i_line.strip(),float(rundict['osc_start'][framekey]),
              float(rundict['osc_start'][framekey])+float(rundict['deltaphi'])))
  if float(rundict['twotheta'])!=0.0:
    twotheta = float(rundict['twotheta'])
    yscale_fudge = math.sin(2.*twotheta*math.pi/180.)/(2*twotheta*math.pi/180.)
    for line in script.split('\n'):
      if line.find('TWOTHETA')==0:
        script = script.replace(line,'TWOTHETA %.4f'%(-twotheta))
      if line.find('BEAM')==0:
        tokens = line.split('BEAM')
        tokens = ['BEAM','SWUNG_OUT']+tokens[1:]
        script = script.replace(line,' '.join(tokens))
  return script

def ad_hoc_inverse_beam_fix(mosx,mosy,mosd,mostt):
  # go from refined mosflm parameters back to the labelit camera coordinates.
  # two theta is not refined.
  yscale_fudge = math.sin(2.*mostt*math.pi/180.)/(2*mostt*math.pi/180.)
  cameraybeam = mosy * yscale_fudge
  cameradistance = mosd * yscale_fudge
  cameraxbeam = mosx - (cameradistance*math.tan(mostt*math.pi/180.))
  return cameraxbeam,cameraybeam

class SafetyLimits: #Just determine the resolution limit for MOSFLM runs
  def __init__(self,INFO,AI):
    self.INFO = INFO
    self.AI = AI
    self.first_pass = mosflm_info_setup(self.INFO,self.AI)

  def determined_limit_from_screen(self):
    try:
      screen = float(self.INFO['best_integration']['integration']['r_resolution'])
    except Exception:
      # no previous MOSFLM success.  Rely on DISTL results.
      screen = float(self.INFO['resolution'])
    return screen

  def safety_limit(self,algorithm="corner"):
   epsilon = 0.0001
    # Originally, the limit at edge of detector on a MarCCD, otherwise lambda/2.
    # But due to comments from Ana Gonzalez on problems with MOSFLM integration
    # out to the corner (Gordon Conference, 2006), a) a limit will be placed
    # on all detectors based on detector geometry, and b) it will be a
    # configurable choice whether the limit is based on the farthest corner
    # or the farthest edge.
   try:
    #for backward compatibility default to measured if no detector geom is stored
    if False and not self.first_pass['mosflm_detector'].find('#MARCCD detector')==0:
      return epsilon + self.AI.wavelength/2.
    else:
      XE.rawprocess(self.first_pass)
      if algorithm=="edge":
        mpoint = max([length(point) for point in XE.edges()])
      elif algorithm=="corner":
        mpoint = max([length(point) for point in XE.corners()])
      theta = math.atan2(mpoint,self.AI.distance())/2.0
      return epsilon + self.AI.wavelength/(2.0*math.sin(theta))
   except Exception:
     return self.determined_limit_from_screen()

  def determined_limit_with_safety(self):
    return max(self.determined_limit_from_screen(), self.safety_limit() )

def modify_for_BEST(constructed,rundict):
  if labelit_commands.best_support==False:
    return constructed
  else:
    if constructed.find("wedge integration")<0:
      return constructed
    else:
      N_digits = rundict['template'].count('#')
      decorator_template = 'BEST ON FILE %%s_%%0%dd.hkl\n%%s\nBEST OFF'%N_digits
      # Expression PROCESS + a number + anything, as short as possible + RUN
      pattern = re.compile(r'(PROCESS ([0-9]+) (.|\n)*?RUN)+')
      match = pattern.findall(constructed)
      for ij, item in enumerate(match):
        if ij==0:
          bestfile_dat = "FINDSPOTS FIND %d\n"%int(item[1])
        else:
          bestfile_dat = ""
        constructed = constructed.replace(
          item[0],
          bestfile_dat +
          decorator_template%(rundict['scheme_id'],int(item[1]),item[0]) )
      return constructed
