"""
     CCP4XtalData.py: CCP4 GUI Project
     Copyright (C) 2010 University of York

     This library is free software: you can redistribute it and/or
     modify it under the terms of the GNU Lesser General Public License
     version 3, modified in accordance with the provisions of the 
     license to address the requirements of UK law.
 
     You should have received a copy of the modified GNU Lesser General 
     Public License along with this library.  If not, copies may be 
     downloaded from http://www.ccp4.ac.uk/ccp4license.php
 
     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU Lesser General Public License for more details.
"""

"""
   Liz Potterton Jan 2010 - Created. Classes for CCP4 xtal data
                 Sep 2010 - Converted to 'generic' data style
"""

## @package CCP4XtalData (QtCore) Data objects for CCP4 crystallographic data
import types

import CCP4Data,CCP4ModelData
import CCP4File

from CCP4Config import XMLPARSER,QT
import CCP4Modules
from CCP4ErrorHandling import *

if XMLPARSER() == 'lxml':
  from lxml import etree
else:
  from elementtree import ElementTree as etree


def SYMMETRYMANAGER():
  # Horrible mess if loadSymLib crashes!
  if CSymmetryManager.insts is None:
    CSymmetryManager.insts = CSymmetryManager()
    try:
      CSymmetryManager.insts.loadSymLib()
    except CException as e:
      pass
  return CSymmetryManager.insts


class CSymmetryManager:

  insts = None

  ERROR_CODES= { 101 : 'CCP4 directory undefined - can not read symmetry library',
                 102 : 'Failed to find/open symmetry library',
                 103 : 'Error reading symmetry library' }

  def __init__(self):

    self.hmSpaceGroupList = []

    self.crystalSystems = ['triclinic','monoclinic','orthorhombic','tetragonal','trigonal','hexagonal','cubic']

    self.chiralSpaceGroups =  { 'triclinic' : ['P 1'],
                                'monoclinic' : ['P 1 2 1','P 1 21 1','C 1 2 1','I 1 2 1'],
                                'orthorhombic' : ['P 2 2 2','P 2 2 21','P 2 21 2', 'P 21 2 2',
                                                  'P 21 21 2','P 21 2 21','P 2 21 21',
                                                  'P 21 21 21','C 2 2 21',
                                                   'C 2 2 2','F 2 2 2','I 2 2 2','I 21 21 21'],
                                'tetragonal' : ['P 4','P 41','P 42','P 43','I 4',   
                                                'I 41','P 4 2 2',
                                                'P 4 21 2','P 41 2 2','P 41 21 2','P 42 2 2','P 42 21 2',
                                                'P 43 2 2','P 43 21 2','I 4 2 2','I 41 2 2'],
                                'trigonal' : [ 'P 3', 'P 31','P 32', 'R 3 :H', 'R 3 :R',
                                               'P 3 1 2','P 3 2 1','P 31 1 2','P 31 2 1',
                                               'P 32 1 2', 'P 32 2 1','R 3 2 :H', 'R 3 2 :R'],
                                'hexagonal' : [ 'P 6', 'P 61', 'P 65','P 62','P 64',
                                                'P 63','P 6 2 2',
                                                'P 61 2 2','P 65 2 2','P 62 2 2','P 64 2 2','P 63 2 2'],
                                'cubic'  : [ 'P 2 3','F 2 3','I 2 3','P 21 3','I 21 3',
                                             'P 4 3 2', 'P 42 3 2','F 4 3 2',
                                             'F 41 3 2','I 4 3 2','P 43 3 2','P 41 3 2','I 41 3 2']
                                }

    '''
    self.laueGroups = [ [ 198, 195 ],
                        [213, 212, 208, 207],
                        [199, 197],
                        [211, 214],
                        [210, 209],
                        [144, 143, 145],
                        [151, 149, 153],
                        [152, 150, 154],
                        [169, 168, 170, 171, 172, 173],
                        [178, 17,7, 17,9, 180, 181, 182],
                        [76, 75, 77, 78],
                        [92, 89, 90, 91, 95, 93, 94, 96, 1094],
                        [80, 79],
                        [98, 97],
                        [19, 16, 17, 18,  1017, 2017, 2018,  3018],
                        [20, 21, 1020, 1021 ],
                        [24, 23, 1023],
                        [4, 3, 1003, 1004] ]
    '''

    self.laueGroups = [['P 21 3', 'P 2 3'],
                       ['P 41 3 2', 'P 43 3 2', 'P 42 3 2', 'P 4 3 2'],
                       ['I 21 3', 'I 2 3'], ['I 4 3 2', 'I 41 3 2'],
                       ['F 41 3 2', 'F 4 3 2'],
                       ['P 31', 'P 3', 'P 32'],
                       ['P 31 1 2', 'P 3 1 2', 'P 32 1 2'],
                       ['P 31 2 1', 'P 3 2 1', 'P 32 2 1'],
                       ['P 61', 'P 6', 'P 65', 'P 62', 'P 64', 'P 63'],
                       ['P 61 2 2', 'P 2 2 21', 'P 1 c 1', 'P 2 2 21', 'C 1 c 1', 'P 62 2 2', 'P 64 2 2', 'P 63 2 2'],
                       ['P 41', 'P 4', 'P 42', 'P 43'],
                       ['P 41 21 2', 'P 4 2 2', 'P 4 21 2', 'P 41 2 2', 'P 43 2 2', 'P 42 2 2', 'P 42 21 2', 'P 43 21 2', 1094],
                       ['I 41', 'I 4'], ['I 41 2 2', 'I 4 2 2'],
                       ['P 21 21 21', 'P 2 2 2', 'P 2 2 21', 'P 21 21 2', 'P 21 2 2', 'P 2 21 2', 'P 21 2 21', 'P 2 21 21'],
                       ['C 2 2 21', 'C 2 2 2', 1020, 1021],
                       ['I 21 21 21', 'I 2 2 2', 1023],
                       ['P 1 21 1', 'P 1 2 1', 'P 1 1 2', 'P 1 1 21']]

  def convertLaue(self):
    out = []
    for lG in self.laueGroups:
      out.append([])
      for sG in lG:
        if self.ccp4NumberList.count(sG):
          out[-1].append(self.hmSpaceGroupList[self.ccp4NumberList.index(sG)])
          if out[-1][-1] == '' : out[-1][-1] = sG
        else:
          out[-1].append( sG)
    print out


  def loadSymLib(self,fileName=None):
    
    if fileName is None:
      import CCP4Utils,os
      path = CCP4Utils.getCCP4Dir()
      if path is None or len(path)==0:
        raise CException(self.__class__,101,name=self.objectPath())
      fileName =  os.path.normpath(os.path.join(path,'lib','data','syminfo.lib'))
      
    try:
      text = CCP4Utils.readFile(fileName)
    except CException:
      raise CException(self.__class__,102,fileName,name=self.objectPath())

    import re
    lineList = text.split('\n')
    #print 'loadSymLib',len(lineList)
    il = -1
    self.hmSpaceGroupList = []
    self.oldSpaceGroupList = []
    self.pointGroupList = []
    self.ccp4NumberList = []
    xHM = None
    old = []
    pgrp = None
    while il < len(lineList):
      il = il + 1
      if lineList[il][0:16] == 'begin_spacegroup':
        il = il + 1
        while il < len(lineList):
          if lineList[il][0:6] == 'symbol':
            if lineList[il].count('xHM'):
              s = re.search(r'(.*?)\'(.*?)\'(.*)',lineList[il])
              if s is not None: xHM = s.groups()[1]
            elif lineList[il].count('pgrp'):
              s = re.search(r'(.*?)\'(.*?)\'(.*)',lineList[il])
              if s is not None: pgrp = s.groups()[1]
            elif lineList[il].count('old'):
              s = re.search(r'(.*?)\'(.*?)\'(.*)',lineList[il])
              while s is not None:
                o = s.groups()[1]
                if len(o)>0: old.append(o)
                s = re.search(r'(.*?)\'(.*?)\'(.*)',s.groups()[2])
            elif lineList[il].count('ccp4'):
              ccp4 = int(lineList[il].split()[-1])        
          elif lineList[il][0:14] == 'end_spacegroup':
            if xHM is not None:
              self.hmSpaceGroupList.append(xHM)
              self.oldSpaceGroupList.append(old)
              self.pointGroupList.append(pgrp)
              self.ccp4NumberList.append(ccp4)
            xHM = None
            old = []
          il = il + 1

  def isChiral(self,spaceGroup):
    for key,value in self.chiralSpaceGroups.items():
      if value.count(spaceGroup)>0:
        return True
    return False

  def crystalSystem(self,spaceGroup):
    for key,value in self.chiralSpaceGroups.items():
      if value.count(spaceGroup)>0:
        return key
    return None

  def nonEnantiogenicList(self):
    l = []
    for system,nameList in self.chiralSpaceGroups.items(): l.extend(nameList)
    return l

  def spaceGroupValidity(self,spaceGroup=None):
    #print 'spaceGroupValidity',spaceGroup,type(spaceGroup)
    if spaceGroup is None or len(spaceGroup)==0:
      #print 'spaceGroupValidity unset'
      return (2,None)

    up = spaceGroup.upper()
    if up != spaceGroup:
      code = 100
      spaceGroup = up
    else:
      code = 0

    if len(self.hmSpaceGroupList)==0:
      # No data from syminfo.lib so can only chek if it is chiral
      if self.isChiral(spaceGroup):
        return (0+code,spaceGroup)
      else:
        return (6+code,spaceGroup)
      
    if self.hmSpaceGroupList.count(spaceGroup):
      ii = self.hmSpaceGroupList.index(spaceGroup)
      if self.isChiral(spaceGroup):
        return (0+code,spaceGroup)
      else:
        return (1+code,spaceGroup)
        
    # Is it an old space group?
    for ii in range(len(self.oldSpaceGroupList)):
      if self.oldSpaceGroupList[ii].count(spaceGroup):
        return (3+code,self.hmSpaceGroupList[ii])

    # Is it a cut-down version of hm
    hits = []
    import re
    for item in self.hmSpaceGroupList:
      if re.match(spaceGroup+' ',item):
        hits.append(item)
    if len(hits)==1:
      return (4+code,hits[0])
    elif len(hits)>1:
      return (5+code,hits)

    # Is it wrong spaces
    import re
    noSpace = re.sub(' ','',spaceGroup)
    print 'spaceGroupValidity',spaceGroup,noSpace,code
    for item in self.hmSpaceGroupList:
      if noSpace == re.sub(' ','',item):
        hits.append(item)
    if len(hits)>0:
      return (7+code,hits)

    return (2+code,spaceGroup)

  def spaceGroupCompleter(self,spaceGroup=None):
    up = spaceGroup.upper().strip()
    hits = []
    import re
    '''
    for item in self.hmSpaceGroupList:
      if re.match(spaceGroup,item):
        hits.append(item)
    return hits
    '''
    for system,groups in self.chiralSpaceGroups.items():
      for gp in groups:
        if re.match(spaceGroup,gp):
          hits.append(gp)
    return hits
    
      
  def ccp4toHm(self,num):
    if self.ccp4NumberList.count(num):
      return self.hmSpaceGroupList[self.ccp4NumberList.index(num)]
    else:
      return None

  def hmtoCcp4(self,name):
    if name is None or name is NotImplemented: return None
    if self.hmSpaceGroupList.count(name):
      return self.ccp4NumberList[self.hmSpaceGroupList.index(name)]
    else:
      return None
  
class CSpaceGroup(CCP4Data.CString):
  '''A string holding the space group'''
  
  QUALIFIERS = { 'allowUndefined' : True,
                 'toolTip' : 'Hermann-Mauguin space group name',
                 'helpFile' : 'crystal_data#space_group' }
  ERROR_CODES = { 101 : { 'description' : 'Invalid space group' },
                  102 : { 'description' : 'Space group is not chiral',
                             'severity' : SEVERITY_WARNING },
                  103 : { 'description' : 'Space group is not Hermann-Mauguin standard' },
                  104 : { 'description' : 'Space group is not a chiral Hermann-Mauguin standard. Full syminfo.lib information not loaded.' },
                  105 : { 'description' : 'Space group is not Hermann-Mauguin standard - has wrong number of spaces?' },
                  106 : { 'description' : 'Space group is undefined',
                          'severity' :  SEVERITY_UNDEFINED },
                  107 : { 'description' : 'Space group is undefined' },
                  108 : { 'description' : 'Space group is incomplete','severity' : SEVERITY_WARNING }
                  }

  def validity(self,arg):
    '''
    Need to check is valid space group name
    '''
    #print 'CSpaceGroup.validity',arg,type(arg)
    err= CErrorReport()
    if arg is None or len(str(arg))==0:
      if self.qualifiers('allowUndefined'):
        err.append(self.__class__,106,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      else:
        err.append(self.__class__,107,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      return err
   
    arg = str(arg)
    status,correctedSG = SYMMETRYMANAGER().spaceGroupValidity(str(arg))
    #print 'CSpaceGroup.validity',arg,'*',status,'*',correctedSG,'*'
    '''
    if correctedSG != arg:
      details = 'Could be: '
      import types
      if isinstance(correctedSG,types.ListType):
        for item in correctedSG:
          details = details + ' ' + str(item) + ','
        details = details[0:-1]
      else:
        details = details + ' ' + str(correctedSG)
    else:
    '''
    details = arg
    
    if status==1:
      err.append(self.__class__,102,details,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif status == 2:
      err.append(self.__class__,101,details,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif 3<=status<=4:
      err.append(self.__class__,103,details,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif status==5:
      #pass
      err.append(self.__class__,108,details,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif status==6:
      err.append(self.__class__,104,details,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif status==7:
      err.append(self.__class__,105,details,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)

    #print 'CSpaceGroup.validity report',arg,err.report()
    return err

  def set(self,value=None,checkValidity=True):
    #print 'CSpaceGroup.set',value,type(value)
    if isinstance(value,int):
      CCP4Data.CString.set(self,SYMMETRYMANAGER().ccp4toHm(value),checkValidity=checkValidity)
    else:
      CCP4Data.CString.set(self,value,checkValidity=checkValidity)


  def fix(self,arg):

    if arg is None or len(arg)==0:
      return arg

    import types
    status,correctedSG = SYMMETRYMANAGER().spaceGroupValidity(arg)
    #print 'CSpaceGroup.fix',arg,'*',status,'*',correctedSG,'*'
    if isinstance(correctedSG,types.ListType):
      if len(correctedSG) == 1:
        return correctedSG[0]
      else:
        return arg
    else:
      return correctedSG
    
  def number(self):
    return SYMMETRYMANAGER().hmtoCcp4(self.__dict__['_value'])


  # What extra functionality is needed here?

class CReindexOperator(CCP4Data.CData):

  ERROR_CODES = { 201 : { 'description' : 'Operator has bad syntax (needs three comma-separated fields)'},
                  202 : { 'description' : 'Operator contains invalid characters' },
                  203 : { 'description' : 'Operator is not set' }
                  }
  CONTENTS = { 'h' : { 'class' : CCP4Data.CString , 'qualifiers' : { 'default' : 'h' }},
               'k' : { 'class' : CCP4Data.CString , 'qualifiers' : { 'default' : 'k' }},
               'l' : { 'class' : CCP4Data.CString , 'qualifiers' : { 'default' : 'l' }}
               }
  CONTENTS_ORDER = ['h','k','l']
  
  def validity(self,arg):   
    err= CErrorReport()
    if arg is None:
      err.append(self.__class__,106,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      return err


    import re
    for key in  ['h','k','l']:
      op = self.__dict__['_value'][key].__str__().strip()
      if len(op)<1:err.append(self.__class__,203,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)       
      s = re.search('[^0-9,\,,\-,\+,\/,h,k,l]',op)
      if s is not None:
        #print 'CReindexOperator.validity s',s.groups()
        err.append(self.__class__,202,name=self.objectPath())

    #print 'CReindexOperator.validity',err.report()
    return err

  def isSet(self,allowUndefined=False,allowDefault=True,allSet=True):
    for key in  ['h','k','l']:
      if self.__dict__['_value'][key].__str__() != key:
        return True
    return False

class CCellLength(CCP4Data.CFloat):
    '''A cell length'''

    QUALIFIERS = { 'min' : 0.0,
                   'default' : None,
                   'allowUndefined' : False,
                   'toolTip' : 'Cell length in A' }

    def setNm(self,value):
      if value is None:
        self.set(None)
      else:
        self.set(value*10.0)
    def getNm(self):
      if self._value is None:
        return None
      else:
        return self._value / 10.0

    PROPERTIES = { 'nm' : { 'fget' : getNm ,
                            'fset' : setNm } }


class CCellAngle(CCP4Data.CFloat):
    '''A cell angle'''

    QUALIFIERS = { 'min' : 0.0,
                   'max' : 180.0,
                   'default' : None,
                   'allowUndefined' : True,
                   'toolTip' : 'Cell angle in degrees'}

    def getRadians(self):
      import math
      if self._value is None:
        return 0.0
      else:
        return self._value * (math.pi / 180.0)

    def setRadians(self,value):
      import math
      if value is None:
        self.set(value)
      else:
        self.set(value*(180.0/math.pi))

    PROPERTIES = { 'rad' : { 'fget' : getRadians ,
                             'fset' : setRadians } }
  

class CCell(CCP4Data.CData):
    '''A unit cell'''
    CONTENTS = { 'a' : { 'class' : CCellLength, 'qualifiers' : { 'toolTip' : 'Cell length a in A', 'guiLabel' : 'a' } },
                 'b' : { 'class' : CCellLength, 'qualifiers' :  { 'toolTip' : 'Cell length b in A', 'guiLabel' : 'b' }},
                 'c' : { 'class' : CCellLength, 'qualifiers':  { 'toolTip' : 'Cell length c in A', 'guiLabel' : 'c' }},
                 'alpha' :  { 'class' : CCellAngle, 'qualifiers' : { 'toolTip' : 'Cell angle alpha in degrees', 'guiLabel' : 'alpha' }},
                 'beta' :  { 'class' : CCellAngle, 'qualifiers' : { 'toolTip' : 'Cell angle beta in degrees', 'guiLabel' : 'beta' } },
                 'gamma' :  { 'class' : CCellAngle,  'qualifiers' : { 'toolTip' : 'Cell angle gamma in degrees', 'guiLabel' : 'gamma' }} }
    CONTENTS_ORDER = ['a' , 'b', 'c', 'alpha', 'beta', 'gamma']

    QUALIFIERS = { 'toolTip' : 'Cell lengths and angles',
                   'helpFile' : 'crystal_data#cell'  }


    def fix(self,arg):
      if arg['a'] is not None and arg['b'] is not None and arg['c'] is not None:
        for item in ['alpha','beta','gamma']:
          if arg[item] is None:
            arg[item] = 90.0
          elif arg[item] <0.0: arg[item] = 0.0
          elif arg[item] >180.0: arg[item] = 180.0
          elif arg[item] < 3.2:
           import math
           arg[item] = round(arg[item] * (180.0/math.pi),4)
          else: arg[item] = round(arg[item],4)
        for item in ['a','b','c']: arg[item] = round(arg[item],4)
        
        return arg
      else:
        return { 'a' :None , 'b' : None , 'c' :None , 'alpha' :None , 'beta' : None, 'gamma' : None }    
            

    def __cmp__(self,other):
      for item in self.CONTENTS_ORDER:
        c = self.__dict__['_value'][item].__cmp__(other.__dict__['_value'][item])
        if c != 0: return c
      return 0

    def isSet(self,allowUndefined=False,allowDefault=True,allSet=True):
      # Allow the cell angles to be unset
      return  CCP4Data.CData.isSet(self,allSet=False)

    def set(self,value=None):
      if isinstance(value,list) and len(value) in [3,6]:
        d = { 'a' : (value[0]), 'b' : (value[1]) , 'c' : (value[2]) }
        if len(value)==6:
          d.update ( { 'alpha' : (value[3]), 'beta' : (value[4]), 'gamma' :  (value[5]) } )
        else:
          d.update ( { 'alpha' : None, 'beta' :None, 'gamma' :  None } )
        value = d
      #print 'CCell.set',arg,type(arg)
      CCP4Data.CData.set(self,value)

    def guiLabel(self):
      import re
      if self.__dict__['_value']['alpha'].isSet():
        s = '%7.1f,%7.1f,%7.1f,%7.1f,%7.1f,%7.1f'%(
            self.__dict__['_value']['a'].__float__(),self.__dict__['_value']['b'].__float__(),self.__dict__['_value']['c'].__float__(),
            self.__dict__['_value']['alpha'].__float__(),self.__dict__['_value']['beta'].__float__(),self.__dict__['_value']['gamma'].__float__())
      elif self.__dict__['_value']['a'].isSet():
        s = '%7.1f,%7.1f,%7.1f,90,90,90'%(self.__dict__['_value']['a'],self.__dict__['_value']['b'],self.__dict__['_value']['c'])
      else:
        s = 'Unknown'
      s = re.sub(' ','',s)
      return s
      

class CSpaceGroupCell(CCP4Data.CData):
  '''Cell space group and parameters'''

  CONTENTS = { 'spaceGroup' : { 'class' : CSpaceGroup, 'qualifiers' : { 'guilabel' : 'space group' } },
               'cell' :  { 'class' : CCell, 'qualifiers' : { 'guilabel' : 'cell' } } }
  
  CONTENTS_ORDER = [ 'spaceGroup','cell']

  QUALIFIERS = { 'toolTip' : 'Space group and cell length and angles',
                 'helpFile' : 'crystal_data#cell_space_group' }

  ERROR_CODES = { 101 : { 'description' : 'Cell lengths should NOT be identical' },
                  102 : { 'description' : 'Cell angles should NOT be identical' },
                  103 : { 'description' : 'Cell angle should be 90' },
                  104 : { 'description' : 'Cell angle should NOT be 90' },
                  105 : { 'description' : 'Cell lengths should be identical' },
                  106 : { 'description' : 'Cell angle should be 120' },
                  107 : { 'description' : 'Cell angle should be identical' } }

  def isNinety(self,arg):
      if arg is None or abs(arg - 90.0) < 0.0001:
          return True
      else:
          return False


  def validity(self,arg):
    '''
    Needs checking of cell paramenters to be consistent with space group
    '''
    import math
    # triclinic     a != b != c; alpha != beta != gamma
    # monoclinic    a != b != c; alpha= gamma = 90; beta != 90
    # orthorhombic  a != b != c; alpha = beta = gamma = 90
    # tetragonal    a = b != c; alpha = beta = gamma = 90
    # rhombohedral  a = b = c;  alpha = beta = gamma != 90
    # hexagonal     a = b != c; alpha = beta = 90; gamma = 120
    # cubic         a = b = c;  alpha = beta = gamma = 90
    
    v = self.itemValidity()
    if len(v)>0: print 'CSpaceGroupCellitem.Validity',v.report()
        
    if len(v)>0: return v

    cell = arg.get('cell')
    if cell is None: return v
    #print 'validity cell',cell,type(cell['alpha'])
    for ang in ['alpha','beta','gamma']:
        if cell[ang] is None or cell[ang] == 0.0:
          cell[ang] = 90.0
        elif cell[ang]<3.0:
          cell[ang] = cell[ang] *180.0/math.pi

    xtlSys = SYMMETRYMANAGER().crystalSystem(arg.get('spaceGroup',None))
    #print 'CSpaceGroupCell.valid90ty xtlSys',xtlSys
    if xtlSys is None:
      pass
    elif xtlSys == 'triclinic':
      if cell['a'] == cell['b'] or cell['a'] == cell['c'] or cell['b'] == cell['c']:
        v.append(self.__class__,101,'all cell lengths in a triclinic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)      
      if cell['alpha'] == cell['beta'] or cell['alpha'] == cell['gamma'] or cell['beta'] == cell['gamma']:
        v.append(self.__class__,102,'all cell angles in a triclinic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif xtlSys == 'monoclinic':
      if cell['a']== cell['b'] or cell['a'] == cell['c'] or cell['b'] == cell['c']:
        v.append(self.__class__,101,'all cell lengths in a monoclinic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if not self.isNinety(cell['alpha']) or not self.isNinety(cell['gamma']):
        v.append(self.__class__,103,'alpha and gamma in monoclinic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if self.isNinety( cell['beta']):
        v.append(self.__class__,104,'beta in monoclinic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif xtlSys == 'orthorhombic':
      if cell['a'] == cell['b'] or cell['a'] == cell['c'] or cell['b'] == cell['c']:
        v.append(self.__class__,101,'all cell lengths in an orthorhombic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)      
      if not self.isNinety(cell['alpha']) or not self.isNinety(cell['beta']) or not self.isNinety(cell['gamma']):
        v.append(self.__class__,103,'all cell angles in an orthorhombic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif xtlSys == 'tetragonal':
      if cell['a'] != cell['b']:
        v.append(self.__class__,105,'a and b in a tetragonal space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if cell['a'] == cell['c']:
        v.append(self.__class__,101,'a/b and c in a tetragonal space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if not self.isNinety(cell['alpha']) or not self.isNinety(cell['beta']) or not self.isNinety( cell['gamma']):
        v.append(self.__class__,103,'all cell angles in a tetragonal space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif xtlSys == 'rhombohedral':
      if cell['a'] != cell['b'] or cell['a'] != cell['c'] or cell['b'] != cell['c']:
        v.append(self.__class__,105,'All cell lengths in a rhombohedral space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if self.isNinety(cell['alpha']) or self.isNinety(cell['beta']) or self.isNinety(cell['gamma']):
        v.append(self.__class__,104,'All cell angles in a rhombohedral space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if cell['alpha'] != cell['beta'] or cell['alpha'] != cell['gamma']:
        v.append(self.__class__,104,107,'All cell angles in a rhombohedral space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif xtlSys == 'hexagonal':
      if cell['a'] != cell['b']:
        v.append(self.__class__,105,'a and b in a hexagonal space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if cell['a'] == cell['c'] or cell['b'] == cell['c']:
        v.append(self.__class__,101,'a/b and c in a hexagonal space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if not self.isNinety(cell['alpha']) or not self.isNinety(cell['beta']):
        v.append(self.__class__,103,'alpha and beta in a hexagonal space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      if cell['gamma'] != 120.:
        v.append(self.__class__,106,'gamma in a hexagonal space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    elif xtlSys == 'cubic':
      if cell['a'] != cell['b'] or cell['a'] != cell['c'] or cell['b'] != cell['c']:
        v.append(self.__class__,105,'All cell lengths in a cubic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)      
      if not self.isNinety(cell['alpha']) or not self.isNinety(cell['beta']) or not self.isNinety(cell['gamma']):
        v.append(self.__class__,104,'all cell angles in a cubic space group',name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)

    #print 'CSpaceGroupCell.validity()',arg.get('spaceGroup',None),v.report()
    return v

  
class CResolutionRange(CCP4Data.CData):
  CONTENTS = { 'low' :  { 'class' : CCP4Data.CFloat, 'qualifers' :  { 'min' : 0.0, 'allowUndefined' : True } }, 
               'high' :  { 'class' : CCP4Data.CFloat , 'qualifers' :  { 'min' : 0.0, 'allowUndefined' : True } } }
  ERROR_CODES = { 201 : { 'description' : 'High/low resolution wrong way round?' } }

  def validity(self,arg):
    v = self.itemValidity(arg)
    if v.maxSeverity()>0: return v
    
    if arg.get('high').__gt__(arg.get('low')):
        v.append(self.__class__,201,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    return v

  
class CWavelength(CCP4Data.CFloat):
    '''Wavelength in Angstrom'''
    QUALIFIERS = { 'min' : 0.0,
                   'toolTip' : 'Data collection wavelength in Angstrom' }

    def fix(self,arg=None):
      if arg is None or arg is NotImplemented:
        return arg
      arg = round(arg,5)
      return arg
        
  
    def setNm(self,value):
      if value is None:
        self.set(None)
      else:
        self.set(value*10.0)
    def getNm(self):
      if self._value is None:
        return None
      else:
        return self._value / 10.0

    PROPERTIES = { 'nm' : { 'fget' : getNm ,
                            'fset' : setNm } }

class CAltSpaceGroup(CSpaceGroup):
  def guiLabel(self):
    if self.__dict__['_value'] is None:
      return '-'
    else:
      return str(self.__dict__['_value'])
    

class CAltSpaceGroupList(CCP4Data.CList):
  SUBITEM = { 'class' : CAltSpaceGroup }

  def validity(self,arg):
    try:
      mode = self.parent().get('SGALT_SELECT').__str__()
    except:
      mode = 'LIST'
    #print 'CAltSpaceGroupList.validity',mode
    if mode != 'LIST':
      return CErrorReport()
    else:
      err = CCP4Data.CList.validity(self,arg)
      if len(arg)<1: err.append(self.__class__,101,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    return err

class CMapDataFile(CCP4File.CDataFile):
  '''A CCP4 Map file'''
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-map',
                 'mimeTypeDescription' : 'CCP4 Electron density map',
                 'fileExtensions' : ['map'],
                 'fileContentClassName' : None,
                 'guiLabel' : 'Electron Density Map',
                 'toolTip' : 'A map in CCP4 format',
                 'helpFile' : 'data_files#map_files' }

class CGenericReflDataFile(CCP4File.CDataFile):
   QUALIFIERS = { 'guiLabel' : 'Reflection data',
                 'mimeTypeName' : "application/CCP4-generic-reflections",
                 'toolTip' : 'A reflection data file in MTZ or a non-CCP4 format',
                 'fileContentClassName' : 'CUnmergedDataContent',
                 'fileExtensions' :  ['mtz','hkl','HKL','sca','SCA','mmcif','cif','ent'],
                 'downloadModes' : ['ebiSFs'],
                 'helpFile' : 'import_merged#file_formats' }

   def __init__(self,value={},qualifiers={},parent=None,name=None,fullPath=None,keywords={},**kw):
     CCP4File.CDataFile.__init__(self,value=value,qualifiers=qualifiers,parent=parent,name=name,fullPath=fullPath,keywords=keywords,**kw)

   def getFormat(self):
     if isinstance(self.__dict__['_fileContent'],CUnmergedDataContent):
       return str(self.__dict__['_fileContent'].format)
     elif  isinstance(self.__dict__['_fileContent'],CMtzData):
       return 'mtz'
     elif  isinstance(self.__dict__['_fileContent'],CMmcifReflData):
       return 'mmcif'
   
   def getMerged(self):
     if isinstance(self.__dict__['_fileContent'],CUnmergedDataContent):
       return self.__dict__['_fileContent'].merged
     elif  isinstance(self.__dict__['_fileContent'],CMtzData):
       return 'merged'
     elif  isinstance(self.__dict__['_fileContent'],CMmcifReflData):
       return 'merged'

   def getFileContent(self):
    #print 'CGenericReflDataFile.getFileContent',self,self.__dict__['_fileContent'].__class__,self.fileContentClass()
    contentClass = self.fileContentClass()
    if self.__dict__['_fileContent'] is None or self.__dict__['_fileContent'].__class__ != contentClass:
      self.__dict__['_fileContent'] = None
    rv = self.loadFile()
    if len(rv)>0:
      print rv.report() 
    if  isinstance(self.__dict__['_fileContent'],CUnmergedDataContent) and self.__dict__['_fileContent'].format == 'mtz' and self.__dict__['_fileContent'].merged == 'merged':
      import CCP4DataManager
      cls = CCP4DataManager.DATAMANAGER().getClass('CMtzData')
      self.__dict__['_fileContent'] = cls()
      self.loadFile()
    
    return self.__dict__['_fileContent']

      
   def fileContentClass(self,className=None):
     #print 'CGenericReflDataFile.fileContentClass',self.exists(),self.getExt()
     if className is None:
       if not self.exists() or (self.getExt() not in ['.mmcif','.cif','.ent']):
         className = 'CUnmergedDataContent'
       else:
         className = 'CMmcifReflData'
     import CCP4DataManager
     cls = CCP4DataManager.DATAMANAGER().getClass(className)
     if cls is None:
       raise CException(self.__class__,105,'Contents class name:'+str(className),name=self.objectPath())
     return cls

   def importFileName(self,jobId=None,jobDirectory=None,ext=None):
     if ext is None:
       try:
         ext = self.getExt()
       except:
         pass
     #print 'CGenericReflDataFile.importFileName ext',ext
     return CCP4File.CDataFile.importFileName(self,jobId=jobId,jobDirectory=jobDirectory,ext=ext)
     
class CMmcifReflDataFile(CCP4File.CMmcifDataFile):
  '''A reflection file in mmCIF format'''
  QUALIFIERS = { 'guiLabel' : 'mmCIF reflection data',
                 'mimeTypeName' : 'chemical/x-cif',
                 'toolTip' : 'A reflection file in mmCIF format',
                 'fileContentClassName' : 'CMmcifReflData',
                 'helpFile' : 'data_files#mmCIF' }

      
  def __init__(self,value={},qualifiers={},parent=None,name=None,fullPath=None,keywords={},**kw):
     CCP4File.CDataFile.__init__(self,value=value,qualifiers=qualifiers,parent=parent,name=name,fullPath=fullPath,keywords=keywords,**kw)
  
  def updateData(self):
    self.loadFile()
    self.emitDataChanged()

class CMmcifReflData(CCP4File.CMmcifData):
  '''Reflection data in mmCIF format'''

  CONTENTS = { 'cell' : { 'class' : CCell },
               'spaceGroup' : { 'class' : CSpaceGroup },
               'wavelength' : { 'class' : CWavelength },
               'haveFreeRColumn' : { 'class' : CCP4Data.CBoolean },
               'haveFobsColumn' : { 'class' : CCP4Data.CBoolean },
               'haveFpmObsColumn' : { 'class' : CCP4Data.CBoolean },
               'haveIobsColumn' : { 'class' : CCP4Data.CBoolean },
               'haveIpmObsColumn' : { 'class' : CCP4Data.CBoolean } }

  ERROR_CODES = { 101 : { 'description' : 'Attempting to load mmCIF data from non-existant/broken file' },
                  102 : { 'description' : 'Error reading interpreting line in cif file'  }
                }
  
  def loadFile(self,fileName=None):
    import os,math
    import CCP4Utils
    print 'CMmcifReflData.loadFile',fileName
    if fileName is None or not os.path.exists(str(fileName)):
        self.unSet()
        self.__dict__['lastLoadedFile'] = None
        return
    fileName = str(fileName)
    # Beware lastLoadedFile might not have been unSet
    #if fileName == self.__dict__.get('lastLoadedFile',None): return
    #import sys,traceback
    #traceback.print_stack()
    #return
    err = CErrorReport()

    mmcifLines = CCP4Utils.readFile(fileName).split("\n")[0:500]
    cell = {'a':None,'b':None,'c':None,'alpha':None,'beta':None,'gamma':None}
    wavelength = None
    pyStrSpaceGroupName = ''
    iSpaceGroupNumber = -1
    haveFreeRColumn = False
    haveFobsColumn = False
    haveFpmObsColumn = False
    haveIobsColumn = False
    haveIpmObsColumn = False
    from CCP4Utils import safeFloat
    for j, pyStrLine in enumerate(mmcifLines):
      try:
        if "_symmetry.Int_Tables_number" in pyStrLine:
          iSpaceGroupNumber = int(pyStrLine.split()[1])
        if "_symmetry.space_group_name_H-M" in pyStrLine:
          pyStrSpaceGroupName = pyStrLine[pyStrLine.index("_symmetry.space_group_name_H-M")+31:]
          pyStrSpaceGroupName = pyStrSpaceGroupName.strip().strip('"').strip("'").strip()
        if "_refln.status" in pyStrLine:
          haveFreeRColumn = True
        if "_refln.F_meas" in pyStrLine or "_refln.F_meas_au" in pyStrLine:
          haveFobsColumn = True
        if "_refln.pdbx_F_plus" in pyStrLine:
          haveFpmObsColumn = True
        if "_refln.intensity_meas" in pyStrLine or "_refln.F_squared_meas" in pyStrLine:
          haveIobsColumn = True
        if "_refln.pdbx_I_plus" in pyStrLine:
          haveIpmObsColumn = True
        if "_diffrn_radiation_wavelength.wavelength" in pyStrLine:
          print 'wavelength',pyStrLine.split()[1]
          wavelength = safeFloat(pyStrLine.split()[1])
        if "_cell." in pyStrLine:
          for cif_item in ['length_a','length_b','length_c','angle_alpha','angle_beta','angle_gamma']:
            if "_cell."+cif_item in pyStrLine:
              value = safeFloat(pyStrLine.split()[1])
              typ,item = cif_item.split('_')
              if typ == 'angle' and value<3.0:
                cell[item] = value * (180.0/math.pi)
              else:
                cell[item] = value
      except:
        err.append(self.__class__,102,str(pyStrLine))

    self.__dict__['_value']['cell'].set(cell)
    self.__dict__['_value']['wavelength'].set(wavelength)
    self.__dict__['_value']['spaceGroup'].set(pyStrSpaceGroupName)
    self.__dict__['_value']['haveFreeRColumn'].set(haveFreeRColumn)
    self.__dict__['_value']['haveFobsColumn'].set(haveFobsColumn)
    self.__dict__['_value']['haveFpmObsColumn'].set(haveFpmObsColumn)
    self.__dict__['_value']['haveIobsColumn'].set(haveIobsColumn)
    self.__dict__['_value']['haveIpmObsColumn'].set(haveIpmObsColumn)
    self.__dict__['lastLoadedFile'] = fileName
    #print err.report()
    self.emitSignal('dataLoaded')
    return err

  
class CMtzDataFile(CCP4File.CDataFile):
  '''An MTZ experimental data file'''
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-mtz',
                 'mimeTypeDescription' : 'MTZ experimental data',
                 'fileExtensions' : ['mtz'],
                 'fileContentClassName' : 'CMtzData',
                 'guiLabel' : 'Experimental data',
                 'toolTip' : "Experimental data in CCP4's MTZ format",
                 'sameCrystalAs' : NotImplemented,
                 'sameCrystalLevel' : NotImplemented,
                 'helpFile' : 'data_files#MTZ' }

  QUALIFIERS_DEFINITION = { 'sameCrystalAs' : { 'type' : str , 'description' : 'Name of CMtzDataFile object that crystal parameters should match - probably the observed data' },
                           'sameCrystalLevel' : { 'type' : int , 'description' : 'Rigour of same crystal test' } }

  
  ERROR_CODES = { 151 : { 'description' :'Failed converting MTZ file to alternative format' },
                  152 : { 'description' :'Failed merging MTZ file - invalid input' },
                  153 : { 'description' :'Failed merging MTZ files - error running cmtzjoin - see log' },
                  154 : { 'description' :'Failed merging MTZ files - error running cad - see log' },
                  401 : { 'description' :'MTZ file header data differs' },
                  402 : { 'description' :'MTZ file columns differ' },
                  403 : { 'description' :'Error trying to access number of reflections' , 'severity' : SEVERITY_WARNING },
                  404 : { 'description' :'MTZ files have different number of reflections' },
                  405 : { 'description' :'MTZ column mean value differs' },
                  406 : { 'description' :'MTZ file header data differs - may be autogenerated names',  'severity' : SEVERITY_WARNING },
                  }

  #Seems to be necessary to have this __init__ defined otherwise fails to load demo task widget
  # This needs looking into
  def __init__(self,value={},qualifiers={},parent=None,name=None,fullPath=None,**kw):
    #print 'CMtzDataFile.__init__'
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)
    CCP4File.CDataFile.__init__(self,value=value,qualifiers=qualis,parent=parent,name=name,fullPath=fullPath,keywords=kw)

  '''
  def updateData(self):
    #print 'CMtzDataFile.updateData',self.objectName(),self.fullPath
    try:
      self.loadFile()
      self.emitDataChanged()
    except CException as e:
      print 'ERROR loading data from:',self.__str__()
      print e.report()
    except Exception as e:
      print 'ERROR loading data from:',self.__str__()
      print e
    except:
      print 'ERROR loading data from:',self.__str__()
      print 'Unknown cause'
  '''

  
  def validity(self,arg={}):
    err = CCP4File.CDataFile.validity(self,arg)
    if err.maxSeverity()>SEVERITY_WARNING or not self.isSet(): return err
    otherMtz = self.getDataByKey('sameCrystalAs')
    #print 'CMtzDataFile.validity',self.objectName(),otherMtz
    if otherMtz is None or not otherMtz.isSet():  return err
    testLevel = self.qualifiers('sameCrystalLevel')
    if testLevel is not NotImplemented and testLevel is not None:
      err.extend(self.sameCrystal(otherMtz,testLevel))
    else:
      err.extend(self.sameCrystal(otherMtz))
    return err
  
  

  def sameCrystal(self,other=None,testLevel=None):
    #print self.objectName(),'calling CMtzDataFile.sameCrystal'
    if self._fileContent is None: self.loadFile()
    if other._fileContent is None: other.loadFile()
    return self.getFileContent().sameCrystal(other._fileContent,testLevel=testLevel)


  def runMtz2various(self,hklin=None,labin=[],hklout=None,output='SHELX',keywords={},**args):
    '''
    Run mtz2various with self as the input file
    keywords are the keyword and text value of mtz2various keyword input excluding OUTPUT,LABIN, and END
    Returns the hklout(str),err  (CErrorReport)
    '''
    labelMapping = { 'F' : 'FP' , 'SIGF' : 'SIGFP' , 'I' : 'I' , 'SIGI' : 'I',
                     'Fplus' : 'F(+)', 'SIGFplus' : 'SIGF(+)', 'Fminus' : 'F(-)', 'SIGFminus' : 'SIGF(-)',
                     'Iplus' : 'I(+)', 'SIGIplus' : 'SIGI(+)', 'Iminus' : 'I(-)', 'SIGIminus' : 'SIGI(-)',
                     'FREER' : 'FREE' }

    kw = {}
    kw.update(keywords)
    kw.update(args)

    import os, sys
    import CCP4Utils
    error = CErrorReport()
    bin = os.path.normpath(os.path.join( CCP4Utils.getOSDir(), 'bin', 'mtz2various' ))
    if not os.path.exists(bin):
       bin =  os.path.normpath(os.path.join( CCP4Utils.getCCP4Dir(), 'bin', 'mtz2various' ))
    if hklin is None: hklin=self.__str__()
    arglist = [ 'HKLIN',hklin ,'HKLOUT',hklout ]

    comText = 'OUTPUT '+output+'\nLABIN'
    for fileLabel in labin:
      progLabel = labelMapping.get(fileLabel,None)
      if progLabel is not None:
        comText = comText + ' ' +progLabel +'='+ fileLabel
    comText = comText +'\n'
    for key,value in kw.items():
      if key.lower() not in ['end','output','labin']:
        comText = comText + key + ' ' + value + '\n'
    comText = comText +'END\n'
    #print 'CMtzDataFile.runMtz2various',comText
    inputFile = os.path.normpath(os.path.splitext(hklout)[0]+'_mtz2various.com')
    CCP4Utils.saveFile(inputFile,comText)
    logFile =  os.path.normpath(os.path.splitext(hklout)[0]+'_mtz2various.log')
      
    pid = CCP4Modules.PROCESSMANAGER().startProcess(bin,arglist,logFile=logFile,inputFile=inputFile)
    status = CCP4Modules.PROCESSMANAGER().getJobData(pid)
    exitCode = CCP4Modules.PROCESSMANAGER().getJobData(pid,'exitCode')      
    if status == 0 and os.path.exists(hklout):
       return hklout,error
    else:
      error.append(self.__class__,151,self.__str__())
      return None,error

    
  def runMtzjoin( self,outfile, infiles ):
    error = CErrorReport()
    import os
    import CCP4Utils
    #print 'CMtzDataFile.runMtzjoin',infiles
    logFile = os.path.normpath(os.path.splitext(outfile)[0]+'_cmtzjoin.log')
    bin =  CCP4Utils.getCCP4Exe('cmtzjoin')
    arglist = [ '-mtzout', outfile ]
    try:
      for name,cols in infiles:
        arglist.append( '-mtzin' )
        arglist.append( name )
        if len(cols)>0:
          arglist.append( '-colout' )
          arglist.append( cols )
    except:
      error.append(self.__class__,152,str(name)+' '+str(cols))
    
    #print 'CMtzDataFile.runMtzjoin',arglist
    pid = CCP4Modules.PROCESSMANAGER().startProcess(bin,arglist,logFile=logFile)
    status = CCP4Modules.PROCESSMANAGER().getJobData(pid)
    exitCode = CCP4Modules.PROCESSMANAGER().getJobData(pid,'exitCode')
    #print 'joinMtz',status,exitCode
    if status not in [0,101] or not os.path.exists(outfile):
      error.append(self.__class__,153,logFile)
      outfile = None
      
    return outfile,error

  def runCad(self,hklout=None,hklinList=[],comLines=[]):
    '''
    Run cad
    hklout - output file name
    hklinList - list of mtz to be merged with self.__str__()
    comLines - list of strings to go into command file (END not necessary)
    If comLines is zero length then the compulsary LABIN input will be set to 'ALL' for all input files
    
    Returns the hklout(str),err  (CErrorReport)
    '''

    import os, sys
    import CCP4Utils
    error = CErrorReport()
    bin =  os.path.normpath(os.path.join( CCP4Utils.getCCP4Dir(), 'bin', 'cad' ))
    
    arglist = [ 'HKLOUT',hklout, 'HKLIN1' , self.__str__() ]
    nHklin = 1
    for hklin in hklinList:
      nHklin += 1
      arglist.extend([ 'HKLIN'+str(nHklin) , hklin ] )
    print 'CMtzDataFile.runCad', arglist

    comText = 'SYSAB_KEEP\n'

    labinLines = []
    for line in comLines:
      words = line.upper().split()
      if words[0] == 'LABIN': labinLines.append(int(words[2]))
        
    for n in range(1,nHklin+1):
      if n not in labinLines:
        comText = comText + 'LABIN FILE_NUMBER '+str(n)+' ALL\n'
    
    for line in comLines:
      if line.lower() not in ['end']:
        comText = comText + line + '\n'
    comText = comText +'END\n'
    print 'CMtzDataFile.runCad',comText
    inputFile = os.path.normpath(os.path.splitext(hklout)[0]+'_cad.com')
    CCP4Utils.saveFile(inputFile,comText)
    logFile =  os.path.normpath(os.path.splitext(hklout)[0]+'_cad.log')
      
    pid = CCP4Modules.PROCESSMANAGER().startProcess(bin,arglist,logFile=logFile,inputFile=inputFile)
    status = CCP4Modules.PROCESSMANAGER().getJobData(pid)
    exitCode = CCP4Modules.PROCESSMANAGER().getJobData(pid,'exitCode')      
    if status == 0 and os.path.exists(hklout):
       return hklout,error
    else:
      error.append(self.__class__,154,self.__str__())
      return None,error

  def assertSame( self,other,diagnostic=False,**kw):
    #print 'CMtzDataFile.assertSame',self,other
    #report = CErrorReport()
    report = CCP4File.CDataFile.assertSame(self,other,diagnostic=diagnostic,testChecksum=False,**kw)
    if report.maxSeverity()>SEVERITY_WARNING or report.count(code=315)>0 : return report
    try:
      self.loadFile()
    except:
      report.append(self.__class__,311,self.__str__(), name=self.objectPath(False))
      return report
    try:
      other.loadFile()
    except:
      report.append(self.__class__,312,other.__str__(), name=self.objectPath(False))
      return report
    for item in ['cell','spaceGroup']:
      if self.fileContent.__getattr__(item) != other.fileContent.__getattr__(item):
        report.append(self.__class__,401,item + ' : ' + str( self.fileContent.__getattr__(item) ) + ' : ' + str(other.fileContent.__getattr__(item)),stack=False, name=self.objectPath(False) )
    for item in ['low','high']:
      if self.fileContent.resolutionRange.__getattr__(item) != other.fileContent.resolutionRange.__getattr__(item):
        report.append(self.__class__,401,item + ' : ' + str( self.fileContent.resolutionRange.__getattr__(item) ) + ' : ' + str(other.fileContent.resolutionRange.__getattr__(item)),stack=False, name=self.objectPath(False) )
    ok = 0
    if len(self.fileContent.datasets) != len(other.fileContent.datasets):
      ok = 2
    else:
      for idx in range(len(self.fileContent.datasets)):
        selfDName = str(self.fileContent.datasets[idx])
        otherDName = str(other.fileContent.datasets[idx])
        if selfDName != otherDName and selfDName:
          if selfDName.count('unknown') and otherDName.count('unknown'):
            if ok==0: ok = 1
          else:
            ok = 2
    if ok == 2:
      report.append(self.__class__,401,'datasets ' + ' : ' + str( self.fileContent.datasets ) + ' : ' + str(other.fileContent.datasets),stack=False, name=self.objectPath(False) )
    elif ok == 1:
      report.append(self.__class__,406,'datasets ' + ' : ' + str( self.fileContent.datasets ) + ' : ' + str(other.fileContent.datasets),stack=False, name=self.objectPath(False) )

          
    if len(self.fileContent.listOfColumns) != len(other.fileContent.listOfColumns):
      report.append(self.__class__,402,stack=False, name=self.objectPath(False))
      
    # Test the number of reflections
    myRefnList = self.hklfileReflectionList()
    otherRefnList = other.hklfileReflectionList()
    if myRefnList is None or otherRefnList is None:
      report.append(self.__class__,403,stack=False, name=self.objectPath(False))
    else:
      if myRefnList.NumberReflections() != otherRefnList.NumberReflections():
        report.append(self.__class__,404,str (myRefnList.NumberReflections() )+' : '+str (otherRefnList.NumberReflections() ),stack=False , name=self.objectPath(False))
      else:
        myRefnList.ReadData()
        otherRefnList.ReadData()
        myStats = myRefnList.GetColumnStatistics()
        otherStats = otherRefnList.GetColumnStatistics()
        for icol in range(min(myRefnList.NumberColumns(),otherRefnList.NumberColumns())-3):
          if diagnostic:
            print 'Reflection data mean values for ',self.fileContent.listOfColumns[icol].columnLabel,' mine:',myStats[icol].Mean(),'other:',otherStats[icol].Mean()
          if not abs(otherStats[icol].Mean() - myStats[icol].Mean()) < max(abs(otherStats[icol].Mean()/100.0),0.0001):
             report.append(self.__class__,405,str(self.fileContent.listOfColumns[icol].columnLabel) + ' ' +  str( myStats[icol].Mean())+ ' : ' + str(otherStats[icol].Mean()), name=self.objectPath(False) )
    if diagnostic: print 'CMtzDataFile.assertSame NumberReflections',myRefnList.NumberReflections(),otherRefnList.NumberReflections()
    if len(report)==0: report.append(self.__class__,300,name=self.objectPath(False),stack=False)
    #print 'CMtzDataFile.assertSame done',report.report()
    return report
                                               

  def hklfileReflectionList(self):
    if not self.exists(): return None
    try:
      import hklfile
      return hklfile.ReflectionList(self.__str__())
    except:
      return None

    
  
class CCrystalName(CCP4Data.CString):
  QUALIFIERS  = { 'allowUndefined' : False,
                  'minLength' : 1,
                  'allowedChars' : 1,
                  'toolTip' : 'Unique identifier for crystal (one word)' }

class CDatasetName(CCP4Data.CString):
  QUALIFIERS  = { 'allowUndefined' : False,
                  'allowedChars' : 1,
                  'minLength' : 1,
                  'toolTip' : 'Unique identifier for dataset (one word)' }



def getClipperCell(clipperCell):
    cell = {}
    import math
    for item,roundTo in [['a',4],['b',4],['c',4],['alpha',5],['beta',5],['gamma',5]]:
      value = getattr(clipperCell,item)().__float__()
      if math.isnan(value) or value<0.0001:
         cell[item] = None
      elif value<3.2 and item in ['alpha','beta','gamma']:
         cell[item] = round(value * ( 180.0 / math.pi),roundTo)
      else:
         cell[item] = round(value,roundTo)
    return cell


class CUnmergedDataContent(CCP4File.CDataFileContent):
  CONTENTS = { 'format' : { 'class' : CCP4Data.CString,
                            'qualifiers' : { 'onlyEnumerators':True ,
                                             'enumerators' : ['unk','mtz' ,'xds', 'sca', 'saint', 'shelx'],
                                             'default' : 'unk' }  },
               'merged' : { 'class' : CCP4Data.CString,
                            'qualifiers' : { 'onlyEnumerators':True ,
                                             'enumerators' : ['unk','merged' ,'unmerged'],
                                             'default' : 'unk' }  },
               'crystalName' : { 'class' : CCrystalName },
               'datasetName'  : { 'class' : CDatasetName },
               'cell' : { 'class' : CCell },
               'spaceGroup' : { 'class' : CSpaceGroup },  
               'batchs' : { 'class' : CCP4Data.CString },
               'lowRes' : { 'class' : CCP4Data.CFloat },
               'highRes' : { 'class' : CCP4Data.CFloat },
               'knowncell' : { 'class' : CCP4Data.CBoolean },
               'knownwavelength' : { 'class' : CCP4Data.CBoolean },
               'numberLattices' : { 'class' : CCP4Data.CInt },
               'wavelength' :  { 'class' : CWavelength }
               }
  
  def loadFile(self,fileName=None):
    import os
    self.unSet()
    fileName = str(fileName)
    #print 'CUnmergedDataContent.loadFile',fileName
    # hklfile.ReflectionFileType NOTSET=0 ABSENT=1 UNKNOWN=2 MTZ = 3 SCA_MERGED=4 SCA_UNMERGED=5
    # SHELX=6 SAINT=7 XDS_INTEGRATE+8 XDS_ASCII=9 
    if fileName is None or not os.path.exists(fileName): return

    self.__dict__['_value']['knowncell'].set(True)
    self.__dict__['_value']['knownwavelength'].set(True)

    # get the file format
    try:
      import hklfile
    except Exception as e:
      print 'FAILED IMPORTING HKLFILE'
      print e

    if os.path.splitext(fileName)[1] in ['.mmcif','.cif']:
      self.format = 'mmcif'

    else:
        
      reflectionList = hklfile.ReflectionList(fileName)
      ftype = reflectionList.FileType()

      #print 'CUnmergedDataContent.loadFile ftype',ftype, ftype.FileType()
      #print "reflectionList.Merged()", reflectionList.Merged()

      if ftype.FileType() in [hklfile.ReflectionFileType.ABSENT,hklfile.ReflectionFileType.UNKNOWN]:
        raise CException(self.__class__,101,fileName,name=self.objectPath())

      self.merged = 'unk'
      # there may be settings of unk needed for some file types
      if (reflectionList.Merged()):
        self.merged = 'merged'
      else:
        self.merged = 'unmerged'
      if ftype.FileType() in [hklfile.ReflectionFileType.SCA_MERGED]:
        self.merged = 'merged'
      if ftype.FileType() in [hklfile.ReflectionFileType.SCA_UNMERGED]:
        self.merged = 'unmerged'

      self.format = 'unk'
      #print 'CUnmergedData.loadFile',ftype.FileType(),'XDS_INTEGRATE', hklfile.ReflectionFileType.XDS_INTEGRATE,'XDS_ASCII', hklfile.ReflectionFileType.XDS_ASCII

      self.__dict__['_value']['knowncell'].set(True)
      if ftype.FileType() in [hklfile.ReflectionFileType.SCA_UNMERGED,
                            hklfile.ReflectionFileType.SHELX]:
        self.__dict__['_value']['knowncell'].set(False)
        self.__dict__['_value']['knownwavelength'].set(False)

      if ftype.FileType() in [hklfile.ReflectionFileType.SCA_MERGED,
                            hklfile.ReflectionFileType.SHELX]:
        
        self.__dict__['_value']['knownwavelength'].set(False)

      if ftype.FileType() in [hklfile.ReflectionFileType.SCA_UNMERGED,
                            hklfile.ReflectionFileType.SCA_MERGED]:
        self.format = 'sca'
        
      if ftype.FileType() == hklfile.ReflectionFileType.SHELX:
        self.format = 'shelx'

      if ftype.FileType() in [hklfile.ReflectionFileType.XDS_INTEGRATE,
                            hklfile.ReflectionFileType.XDS_ASCII]:
        self.format = 'xds'
        # assuming xds in unmerged may be faulty
        self.merged = 'unmerged'
  
      if ftype.FileType() == hklfile.ReflectionFileType.SAINT:
        self.format = 'saint'
        self.merged = 'unmerged'
 
      if ftype.FileType() == hklfile.ReflectionFileType.MTZ:
        self.format = 'mtz'
        
      self.extractMtzData(ftype, reflectionList)

      #print "self.format", self.format

  def extractMtzData(self, ftype, reflectionList):
    import math
    #This is broken without swigged clipper
    #print 'extractMtzData',ftype, reflectionList
    listOfDatasets = []
    listOfXnames = []
    listOfWavelengths = []
    listOfCells = []
    xtalDataInf = reflectionList.GetDatasets()
    #print 'extractMtzData xtalDataInf',xtalDataInf
    #print 'extractMtzData', xtalDataInf.__len__()
    firstrealdataset = False
    self.wavelength = float(0.0)
    for n in range(len(xtalDataInf)):
      #print 'xtalDataInf',xtalDataInf[n].Dname()
      listOfDatasets.append(xtalDataInf[n].Dname().__str__())
      listOfXnames.append(xtalDataInf[n].Xname().__str__())
      listOfWavelengths.append(xtalDataInf[n].Wavelength().__float__())
      listOfCells.append(getClipperCell(xtalDataInf[n].Cell()))
      if not firstrealdataset and (xtalDataInf[n].Dname() != 'HKL_base'):
        firstrealdataset = True
        self.datasetName = listOfDatasets[n]
        self.crystalName = listOfXnames[n]
        self.wavelength = self.wavelength.fix(listOfWavelengths[n])
        
    #print 'CUnmergedDataContent.extractMtzData datasets:',listOfDatasets,'crystalNames:',listOfXnames
    #print 'listOfWavelengths', listOfWavelengths
    #print 'self.datasetName, self.crystalName, self.wavelength ', self.datasetName, self.crystalName, self.wavelength 
    #print 'type(self.datasetName)', type(self.datasetName)
    print 'extractMtzData listOfCells',listOfCells

    self.__dict__['_value']['cell'].set(getClipperCell(reflectionList.Cell() ) )
    #print 'CUnmergedDataContent.extractMtzData cell',self.__dict__['_value']['cell']
    try:
      hm = reflectionList.Spacegroup().symbol_hm()    
      if not isinstance(hm,str):
        hm = hklfile.ClipperStringAsString(hm)
      self.__dict__['_value']['spaceGroup'].set(hm)
    except:
      self.__dict__['_value']['spaceGroup'].set(reflectionList.CCP4_SpacegroupNumber().__int__())
    #print 'reflectionList.Spacegroup',reflectionList.CCP4_SpacegroupNumber().__int__(),reflectionList.Spacegroup().symbol_hm(),self.__dict__['_value']['spaceGroup']
    #print 'reflectionList.Spacegroup',reflectionList.Spacegroup(),type(reflectionList.Spacegroup())
    #self.__dict__['_value']['spaceGroup'].set(reflectionList.Spacegroup())
    self.__dict__['_value']['batchs'].set(reflectionList.formatBatchNumberlist())
    self.__dict__['_value']['lowRes'].set(reflectionList.Resolution().ResLow())
    self.__dict__['_value']['highRes'].set(reflectionList.Resolution().ResHigh())
    self.__dict__['_value']['numberLattices'].set(reflectionList.NumberLattices())
    #print "batchs", self.__dict__['_value']['batchs']


class CUnmergedDataFile(CCP4File.CDataFile):
  ''' Handle MTZ, XDS and scalepack files. Allow wildcard filename'''
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-unmerged-experimental',
                 'mimeTypeDescription' : 'Unmerged experimental data',
                 'fileExtensions' : ['mtz','hkl','HKL','sca','SCA'],
                 'fileContentClassName' : 'CUnmergedDataContent',
                 'guiLabel' : 'Unmerged reflections',
                 'toolTip' : "Unmerged experimental data in any format",
                 'helpFile' : 'data_files#unmerged_data' }

  def __init__(self,value={},qualifiers={},parent=None,name=None,fullPath=None,**kw):
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)
    CCP4File.CDataFile.__init__(self,value=value,qualifiers=qualis,parent=parent,name=name,fullPath=fullPath,keywords=kw)
    self.baseName.setQualifier('allowedCharacters','*?')
    self.relPath.setQualifier('allowedCharacters','*?')
    
  def isWildCard(self):
    if self.baseName.isSet() and (str(self.baseName).count('*')+str(self.baseName).count('?'))>0:
      return True
    else:
      return False

  def globFiles(self):
    import glob
    allFiles = glob.glob(self.fullPath().__str__())
    return allFiles

  def fileFormat(self):
    if not self.baseName.isSet(): return None
    import os
    base,ext = os.path.splitext( self.baseName.__str__())
    if ext == '.mtz':
      return 'mtz'
    
class CUnmergedDataFileList(CCP4Data.CList):
  SUBITEM = { 'class' : CUnmergedDataFile }

class CImportUnmerged(CCP4Data.CData):
  CONTENTS = {  'file' : { 'class' :CUnmergedDataFile , 'qualifiers' :  { 'allowUndefined' : False, 'mustExist': True, 'fromPreviousJob' : True } },
                'cell' :  { 'class' :CCell },
                'wavelength' :  { 'class' : CWavelength },
                'crystalName' : { 'class': CCP4Data.CString , 'qualifiers' : { 'allowUndefined' : False, 'minLength' : 1, 'guiLabel' : 'crystal name','allowedCharsCode' : 1 } },
                'dataset' : { 'class': CCP4Data.CString, 'qualifiers' : { 'allowUndefined' : False, 'minLength' : 1, 'guiLabel' : 'dataset name','allowedCharsCode' : 1 } },
                'excludeSelection' :  { 'class': CCP4Data.CRangeSelection, 'qualifiers' : { 'allowUndefined' : True } },
              }
  CONTENTS_ORDER = [ 'file','crystalName','dataset', 'excludeSelection']
  QUALIFIERS = { 'toolTip' : 'Unmerged experimental data file and name for crystal and dataset in the file' ,
                 'helpFile' : 'import_merged#file_formats',
                 'toolTip' : 'Imported data file, cell parameters and crystal/dataset identifiers' }
            #    'dataType' : { 'class' : CExperimentalDataType },
            #    'formFactor' : { 'class' : CAtomicFormFactors}

  def __init__(self,**kw):
    CCP4Data.CData.__init__(self,**kw)
    # These are calling loadDatasetName to overwrite valid dataset/crystalNames
    # try just calling it from the gui
    #self.connectSignal(self.file,'dataChanged',self.loadDatasetName)
    #self.loadDatasetName()

  def validity(self,args):
    keys = ['file','crystalName','dataset','excludeSelection']
    if self.file.isSet() and not self.file.fileContent.knowncell: keys.append('cell')
    if self.file.isSet() and not self.file.fileContent.knownwavelength: keys.append('wavelength')
    #print 'CImportUnmerged.validity keys',keys
    return self.itemValidity(args,keys=keys)
  
  def aimlessExcludeBatch(self):
    #Return a string of 'EXCLUDE BATCH ..' lines
    if not self.__dict__['_value']['excludeSelection'].isSet(): return ''
    ret = ''
    batchList = self.excludeSelection.removeWhiteSpace(str(self.__dict__['_value']['excludeSelection'])).split(",")
    listout = ''
    for batch in batchList:
      if "-" in batch:
        ret +=  "EXCLUDE BATCH %s\n" % batch.replace("-"," TO ")
      else:
        listout += " "+batch
    if len(listout)>0:  ret +=  "EXCLUDE BATCH %s\n" % listout
    #print 'CImportUnmerged.aimlessExcludeBatch',self.__dict__['_value']['excludeSelection'],ret
    return ret

  
  def loadDatasetName(self):
    #print 'CImportUnmerged.loadDatasetName',self.file
    disallowed =  ['dummy','new','none']
    self.file.loadFile()
    #print 'CImportUnmerged.loadDatasetName',self.file.fileContent.format,self.file.fileContent.crystalName,self.file.fileContent.datasetName,len(self.file.fileContent.crystalName)
    if self.file.fileContent.datasetName.isSet() and len(self.file.fileContent.datasetName)>0:
      name = self.file.fileContent.datasetName.__str__()
      if name.lower() not in disallowed:
        self.dataset = name
      #else:
      #  self.dataset.unSet()
    if self.file.fileContent.crystalName.isSet() and len(self.file.fileContent.crystalName)>0:
      name = self.file.fileContent.crystalName.__str__()
      if name.lower() not in disallowed:
        self.crystalName = name
      #else:
      #  self.crystalName.unSet()
    #else:
    #   self.dataset.unSet()
    #   self.crystalName.unSet()
    self.emitSignal('dataChanged')
   
  def getTextItem(self):
    return self.file.baseName.__str__()

  def getTableTextItems(self):
    return [self.file.baseName.__str__(),self.crystalName.__str__(),self.dataset.__str__(),self.excludeSelection.__str__()]

  # This is needed for the CImportUnmergedView to handle icon menu
  def exists(self):
    return self.__dict__['_value']['file'].exists()

class CImportUnmergedList(CCP4Data.CList):
  QUALIFIERS = { 'listMinLength' : 1 }
  SUBITEM = { 'class' : CImportUnmerged }

  def getCrystalList(self):
    xtalList = []
    #print 'CImportUnmergedList.getCrystalList',self.__dict__['_value']
    for item in self.__dict__['_value']:
      if item.crystalName.isSet():
        xtal = item.crystalName.__str__()
        if xtalList.count(xtal)==0: xtalList.append(xtal)
    return xtalList

  def saveToDb(self):
    fileObjList = []
    for item in self.__dict__['_value']: fileObjList.append(item.file)
    return fileObjList,None,{}

class CImageFile(CCP4File.CDataFile):
    QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-image',
                 'mimeTypeDescription' : 'Image file',
                 'fileExtensions' : ['img'],
                 'fileContentClassName' : None,
                 'guiLabel' : 'Image file',
                 'toolTip' : "First image file in a directory"}
    
class CUnmergedMtzDataFile(CMtzDataFile):
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-mtz-unmerged',
                 'mimeTypeDescription' : 'MTZ unmerged experimental data',
                 'fileExtensions' : ['mtz'],
                 'fileContentClassName' : None,
                 'guiLabel' : 'Unmerged MTZ reflections',
                 'toolTip' : "Unmerged experimental data in CCP4's MTZ format"}



class CColumnType(CCP4Data.CString):
  '''A list of recognised MTZ column types'''
  
  QUALIFIERS = { 'enumerators' :  ['H','J','F','D','Q','G','L','K','M','E','P','W','A','B','Y','I','R'],
                 'onlyEnumerators' : True,
                 'default' : 'F' }
  DESCRIPTION =  { 'H' :	'index h,k,l',
                   'J' :	'intensity',
                   'F' :	'structure amplitude, F',
                   'D' :	'anomalous difference',
                   'Q' :	'standard deviation of J,F,D or other (but see L and M below)',
                   'G' :	'structure amplitude associated with one member of an hkl -h-k-l pair, F(+) or F(-)',
                   'L' :	'standard deviation of a column of type G',
                   'K' :	'intensity associated with one member of an hkl -h-k-l pair, I(+) or I(-)',
                   'M' :	'standard deviation of a column of type K',
                   'E' :	'structure amplitude divided by symmetry factor ("epsilon"). Normally scaled as well to give normalised structure factor',
                   'P' :	'phase angle in degrees',
                   'W' :	'weight (of some sort)',
                   'A' :	'phase probability coefficients (Hendrickson/Lattman)',
                   'B' :	'BATCH number',
                   'Y' :	'M/ISYM, packed partial/reject flag and symmetry number',
                   'I' :	'any other integer',
                   'R' :	'any other real' }

  def __init__(self,value=NotImplemented,qualifiers={},parent=None,name=None,**kw):
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)
    CCP4Data.CString.__init__(self,value=value,qualifiers=qualis,parent=parent,name=name)

  
  def getDescription(self):
    return CColumnType.DESCRIPTION[self.__dict__['_value']]
   
  PROPERTIES = { 'description' : { 'fget' : getDescription } }

class CColumnTypeList(CCP4Data.CList):
  '''A list of acceptable MTZ column types'''
  SUBITEM = { 'class' : CColumnType }

  '''
  def __init__(self,value=NotImplemented,qualifiers={},parent=None,name=None,subItem={},**kw):
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)
    CCP4Data.CList.__init__(self,value=value,qualifiers=qualis,parent=parent,name=name,subItem=subItem)
  '''
 

  def setEtree(self,element,checkValidity=True):
    txt = str(element.text)
    cList = txt.split(',')
    for item in cList:
      self.addItem()
      self.__dict__['_value'][-1].set(item)
    #print 'CColumnTypeList.setEtree',repr(self),txt,cList,self.__dict__['_value']
      
  def getEtree(self,tag=None):
    if tag is None: tag = self.objectName()
    if tag is None or len(tag)==0: tag = self.className()
    element = etree.Element(tag)
    txt = ''
    for item in self.__dict__['_value']:
      txt = txt + item.get() + ','
    if len(txt)>0: txt = txt[0:-1]
    element.text = txt
    return element

class CMtzColumn(CCP4Data.CData):
  '''An MTZ column with column label and column type'''
  
  CONTENTS = { 'columnLabel' : { 'class' : CCP4Data.COneWord,
                                'qualifiers' : {'allowUndefined' : True } },
               'columnType' : { 'class' : CColumnType },
               'dataset'    : { 'class' : CCP4Data.COneWord },
               'groupIndex' : { 'class' : CCP4Data.CInt }
                                      }
  def guiLabel(self):
    if self.__dict__['_value']['dataset'].isSet() and self.__dict__['_value']['dataset'] != 'HKL_base':
      return str(self.__dict__['_value']['dataset'])+'/'+str(self.__dict__['_value']['columnLabel'])
    else:
      return str(self.__dict__['_value']['columnLabel'])

class CMtzColumnGroupType(CColumnType):
  pass

class CMtzColumnGroup(CCP4Data.CData):
  CONTENTS = { 'groupType' : { 'class' : CMtzColumnGroupType },
               'columns' : { 'class' : CCP4Data.CList ,  'subItem' : { 'class' : CMtzColumn } } }

  def guiLabel(self):
    text = ''
    for col in self.__dict__['_value']['columns']:
      text = text + str(col.guiLabel()) + ' '
    return text
  
class CMtzDataset(CCP4Data.CData):
   CONTENTS = { 'name' : { 'class' : CCP4Data.CString },
                'columnGroups' :  { 'class' : CCP4Data.CList,
                                   'subItem' : { 'class' : CMtzColumnGroup } } }
   pass



class CMtzData(CCP4File.CDataFileContent):
  '''Some of the data contents of an MTZ file'''
      
  CONTENTS = { 'cell' : { 'class' : CCell },
               'spaceGroup' : { 'class' : CSpaceGroup },    # HM space group name
               'resolutionRange' : { 'class' : CResolutionRange },
               'listOfColumns' : { 'class' : CCP4Data.CList,
                                   'subItem' : { 'class' : CMtzColumn } },
  
               'datasets' :  { 'class' : CCP4Data.CList,
                                   'subItem' : { 'class' : CCP4Data.CString } },
               'crystalNames' :  { 'class' : CCP4Data.CList,
                                   'subItem' : { 'class' : CCP4Data.CString } },
               'wavelengths' :  { 'class' : CCP4Data.CList,
                                   'subItem' : { 'class' : CWavelength } },
               'datasetCells' :  { 'class' : CCP4Data.CList,
                                   'subItem' : { 'class' : CCell } },
               'merged' : { 'class' : CCP4Data.CBoolean } 
                                   }
  

  ERROR_CODES = { 101 : { 'description' : 'Attempting to load MTZ data from non-existant/broken file' },
                  102 : { 'description' : 'Error creating command file for mtzdump' },
                  103 : { 'description' : 'No log file found from mtzdump'},
                  104 : { 'description' : 'Error reading log file from mtzdump'},
                  105 : { 'severity': SEVERITY_WARNING, 'description' : 'Different spacegroup'},
                  106 : { 'severity': SEVERITY_WARNING, 'description' : 'Different cell parameter'},
                  107 : { 'severity': SEVERITY_WARNING, 'description' : 'Different cell parameters'},
                  108 : { 'severity': SEVERITY_ERROR, 'description' : 'Different Laue group'},
                  109 : { 'severity': SEVERITY_ERROR, 'description' : 'Different point group'},
                  410 : { 'description' :'Invalid CSeqDataFile passed to matthewCoeff' },
                  411 : { 'description' :'Failed to run matthewCoeff' }
                  }


  
  def loadFile(self,fileName=None):
    #print 'CMtzData.loadFile',repr(self),fileName,'lastLoadedFile',self.__dict__.get('lastLoadedFile',None)
    #import traceback
    #traceback.print_stack(limit=5)
    #if fileName is None or str(fileName) == self.__dict__.get('lastLoadedFile',None): return
    if fileName is None: return
    #import sys,traceback
    #traceback.print_stack()
    #return
    import CCP4File
    self.__dict__['lastLoadedFile'] = str(fileName)
    #print 'CMtzData.loadFile lastLoadedFile', repr(self), self.__dict__['lastLoadedFile']
    import os
    import CCP4Utils
    if not os.path.exists(self.__dict__['lastLoadedFile']):
      self.unSet()
      raise CException(self.__class__,101,fileName,name=self.objectPath())

    try:
      import hklfile
      useHklfile = True
    except:
      useHklfile = False
    if useHklfile:
      self.extractMtzData(self.__dict__['lastLoadedFile'])
      self.emitSignal('dataLoaded')
      #print 'CMtzData.loadFile listOfColumns' , self.__dict__['_value']['listOfColumns']

    else:
      try:
        inputFile =  os.path.normpath(os.path.join(CCP4Utils.getTMP(),'runMtzdump.script'))
        if not os.path.exists(inputFile):
          CCP4Utils.saveFile(fileName=inputFile,text='HEADER\nEND\n')
      except:
        raise CException(self.__class__,102,inputFile,name=self.objectPath())

      logFile = self.logFileName()   
      pid = CCP4Modules.PROCESSMANAGER().startProcess(command='mtzdump',
                                  args=['HKLIN',self.__dict__['lastLoadedFile']],
                                  inputFile = inputFile,
                                  logFile = logFile,
                                  waitForFinished=1000 )   
      try:
        self.scrapeMtzdumpLog(pid)
      except CException as e:
        print e.report()
      except Exception as e:
        print 'ERROR extracting data from MTZ file: ',fileName
                                
    
  def logFileName(self):
    import os
    import CCP4Utils
    baseName = os.path.splitext(os.path.basename(self.__dict__['lastLoadedFile']))[0]
    return  os.path.normpath(os.path.join(CCP4Utils.getTMP(),baseName + '_mtzdump.log'))
                         
  def scrapeMtzdumpLog(self,processId,**kw):
    #print 'CMtzData.scrapeMtzdumpLog', processId
    import os
    import CCP4Utils
    logFile = self.logFileName()
    if not os.path.exists(logFile):
      raise CException(self.__class__,103,logFile,name=self.objectPath())

    logText = CCP4Utils.readFile(logFile)
    if logText == '':
      raise CException(self.__class__,104,logFile,name=self.objectPath())

    fileData = self.parseMtzdumpLog(logText)
    #print 'scrapeMtzdumpLog fileData',fileData
    rv = CErrorReport()
    for item in self.dataOrder():
      try:
        self.__dict__['_value'][item].set(fileData[item])
      except CException as e:
        rv.append(e)
      except:
        pass
    #print 'scrapeMtzdumpLog done set'
    self.emitSignal('dataLoaded')
    return rv
    
  def parseMtzdumpLog(self,logText=''):
      from CCP4Utils import safeFloat
      # Extract data from log file 
      # Code taken from EDNA example
      pyListLogLines = logText.split("\n")
      cell = []
      listOfColumns = []
      column_name_list = []
      column_type_list = []
      pyStrSpaceGroupName = ''
      iSpaceGroupNumber = -1
      lowerResolutionLimit = None
      upperResolutionLimit = None
      
      for j, pyStrLine in enumerate(pyListLogLines):
            if "* Dataset ID, project/crystal/dataset names, cell dimensions, wavelength:" in pyStrLine:
               cell = list(map(safeFloat, pyListLogLines[j + 5].split()))
            if " * Space group = " in pyStrLine:
                pyStrSpaceGroupName = pyStrLine.split("'")[1].strip()
                iSpaceGroupNumber = int(pyStrLine.replace("(", " ").replace(")", " ").split()[-1])
            if "*  Resolution Range" in pyStrLine:
                lowerResolutionLimit = float(((pyListLogLines[j + 2].split("(")[1]).split())[0])
                upperResolutionLimit = float(((pyListLogLines[j + 2].split("(")[1]).split())[2])
            if "* Column Labels" in pyStrLine:
                column_name_list = pyListLogLines[j + 2].split()
            if "* Column Types" in pyStrLine:
                column_type_list = pyListLogLines[j + 2].split()

      
      for j, column_name in enumerate(column_name_list):
            column_type = column_type_list[j]
            listOfColumns.append ( CMtzColumn(columnLabel=column_name,columnType=column_type) )

       
      rv =  { }
      if len(cell)==6:
            rv['cell'] = { 'a' : cell[0], 'b' : cell[1], 'c' : cell[2],
                         'alpha' : cell[3], 'beta' : cell[4], 'gamma' : cell[5] }
      rv['spaceGroup'] = pyStrSpaceGroupName
      rv['resolutionRange'] =  { 'high' : lowerResolutionLimit, 'low' : upperResolutionLimit }
      rv['listOfColumns'] = listOfColumns[3:]
      return rv

  def getNColumns(self):
    return len(self.__dict__['_value']['listOfColumns'])

  def getColumn(self,guiLabel):
    if guiLabel is None: return None
    columnLabel = guiLabel.split('/')[-1]
    #print 'CMtzData.getColumn',columnLabel
    for column in self.__dict__['_value']['listOfColumns']:
      if column.columnLabel == columnLabel:
        return column
    return None
  
  def getColumnIndex(self,guiLabel):
    if guiLabel is None: return -1
    columnLabel = guiLabel.split('/')[-1]
    #print 'CMtzData.getColumnIndex',guiLabel,columnLabel
    ii = -1
    for column in self.__dict__['_value']['listOfColumns']:
      ii = ii + 1
      if column.columnLabel == columnLabel:
        return ii
    return -1

  def getListOfWavelengths(self):
    #MN CHECK ME CHECKME expose list of wavelengths of datasets in an MTZ file
    return self.__dict__['_value']['wavelengths']
        
  def getListOfColumns(self,columnTypes=[]):
    if len(columnTypes) == 0:
      return self.__dict__['_value']['listOfColumns']
    else:
      listOfColumns = []
      for column in self.__dict__['_value']['listOfColumns']:
        if columnTypes.count(column.columnType):
          listOfColumns.append(column)
      return listOfColumns

  def getColumnType(self,guiLabel=''):
    if guiLabel is None: return ''
    columnLabel = guiLabel.split('/')[-1]
    #print 'CMtzData.getColumnType',columnLabel
    for column in self.__dict__['_value']['listOfColumns']:
      if column.colmnLabel == columnLabel:
        return column.columnType
    return ''

  #def getPartnerColumn(self,firstColumn='',partnerTypes=[],partnerOffset=1):
  def getPartnerColumn(self,firstColumn='',columnGroupItem=None):
    # NB returns a CMtzColumn
    partnerTypes = columnGroupItem.columnType
    if not columnGroupItem.partnerOffset.isSet():
      partnerOffset = 1
    else:
      partnerOffset = int(columnGroupItem.partnerOffset)
    columnIndex = self.getColumnIndex(firstColumn)
    #print 'CMtzData.getPartnerColumn columnIndex',columnIndex,len(self.__dict__['_value']['listOfColumns'])
    if columnIndex <0: return None
    columnIndex = columnIndex + partnerOffset
    if columnIndex>=  len(self.__dict__['_value']['listOfColumns']): return None
    #print 'CMtzData.getPartnerColumn columnType',self.__dict__['_value']['listOfColumns'][columnIndex].columnType
    if partnerTypes.count(self.__dict__['_value']['listOfColumns'][columnIndex].columnType):
      #print 'getPartnerColumn',self.__dict__['_value']['listOfColumns'][columnIndex].guiLabel()
      #return self.__dict__['_value']['listOfColumns'][columnIndex].columnLabel
      return self.__dict__['_value']['listOfColumns'][columnIndex].guiLabel()
    else:
      return None

  def extractMtzData(self,fileName):
    #print 'extractMtzData fileName',fileName
    try:
      import hklfile
    except:
      print 'FAILED IMPORTING HKLFILE'
    reflectionList = hklfile.ReflectionList(fileName)
    ftype = reflectionList.FileType()
    #print ftype.FileType()
    
    if ftype.FileType() in [hklfile.ReflectionFileType.ABSENT,hklfile.ReflectionFileType.UNKNOWN]:
      raise CException(self.__class__,101,fileName,name=self.objectPath())

    
    #This is broken without swigged clipper
    xtalDataInf = reflectionList.GetDatasets()
    listOfDatasets = []
    listOfCrystals = []
    listOfWavelengths = []
    listOfCells = []
    #print 'extractMtzData xtalDataInf',xtalDataInf
    #print 'extractMtzData', xtalDataInf.__len__()
    for n in range(len(xtalDataInf)):
      #print 'xtalDataInf',xtalDataInf[n].Dname()
      listOfDatasets.append(xtalDataInf[n].Dname().__str__())
      listOfCrystals.append(xtalDataInf[n].Xname().__str__())
      listOfWavelengths.append(xtalDataInf[n].Wavelength().__str__())
      listOfCells.append(getClipperCell(xtalDataInf[n].Cell()))
    self.__dict__['_value']['datasets'].set(listOfDatasets)
    self.__dict__['_value']['crystalNames'].set(listOfCrystals)
    self.__dict__['_value']['datasetCells'].set(listOfCells)
    self.__dict__['_value']['wavelengths'].unSet()
    for item in listOfWavelengths:
      try:
        wl = float(item)
      except:
        wl = None
      #MN I sem to have an MTZ for which one of the wavelengths is -1.0, i.e. outside the allowed range
      #To avoid the exception being thrown, I will try except this
      try:
        self.__dict__['_value']['wavelengths'].append(wl)
      except CException as e:
        print e
    nCol = reflectionList.NumberColumns()
    colLabels = reflectionList.ColumnLabels()
    colTypes = reflectionList.ColumnTypes()
    groupIndex = reflectionList.ColumnGroupIndex()
    dataSetIndex = reflectionList.GetColumnDataset()
    #print 'extractMtzData colLabels',colLabels
    listOfColumns = []
    '''
    # Diagnostic of hklfile.ReflectionList assignment of column groups
    lastCol = 0
    lastGroup = -1
    for n in range(3,nCol):
      if groupIndex[n] != lastGroup:
        if lastGroup>0:
          print 'Column group', lastGroup,'  ',
          for ii in range(lastCol,n):
            print colLabels[ii],colTypes[ii],'  ',
          print ' '
        lastCol = n
        lastGroup = groupIndex[n]
    print 'Column group', lastGroup,'  ',
    for ii in range(lastCol,n):
      print colLabels[ii],colTypes[ii],'  ',
      print ' '
    '''
    for n in range(3,nCol):
      #print 'extractMtzData',n,colLabels[n],colTypes[n],groupIndex[n],dataSetIndex[n]
      listOfColumns.append ( CMtzColumn(columnLabel=colLabels[n],columnType=colTypes[n],groupIndex=groupIndex[n]) )
    self.__dict__['_value']['listOfColumns'].set(listOfColumns)

    for n in range(nCol-3):
      try:
        #print 'extractMtzData listOfColumns',self.__dict__['_value']['listOfColumns']
        #print 'extractMtzData dataSetIndex', dataSetIndex[n],self.__dict__['_value']['datasets'][dataSetIndex[n+3]]
        self.__dict__['_value']['listOfColumns'][n].dataset.set(self.__dict__['_value']['datasets'][dataSetIndex[n+3]])
      except:     
        print 'ERROR: extractMtzData listOfColumns',self.__dict__['_value']['listOfColumns'],'nCol',nCol,'n',n

    self.__dict__['_value']['merged'].set(reflectionList.Merged())
      
    cell = reflectionList.Cell()
    import math
    for item in [ 'a','b','c','alpha','beta','gamma']:
        value = getattr(cell,item)().__float__()
        if not math.isnan ( value ) and value > 0.0001:
            if item in ['alpha','beta','gamma'] and value<3.0:
              getattr(self.__dict__['_value']['cell'],item).setRadians(value)
            else:
              getattr(self.__dict__['_value']['cell'],item).set(value)
    #print 'extractMtzData cell',self.cell
    
    try:
      hm = reflectionList.Spacegroup().symbol_hm()    
      if not isinstance(hm,str):
        hm = hklfile.ClipperStringAsString(hm)
      self.__dict__['_value']['spaceGroup'].set(hm)
    except:
      self.__dict__['_value']['spaceGroup'].set(reflectionList.CCP4_SpacegroupNumber().__int__())
    
    #print 'extractMtzData spaceGroup',self.spaceGroup

    #print 'CMtzData.extractMtzData Resolution',reflectionList.Resolution().ResLow().__str__(),reflectionList.Resolution().ResHigh().__str__()
    self.__dict__['_value']['resolutionRange'].low.set(reflectionList.Resolution().ResLow().__str__())
    self.__dict__['_value']['resolutionRange'].high.set(reflectionList.Resolution().ResHigh().__str__())

    '''
    # Set up new dataset/columngroup data
    self.datasets.unSet()
    currentDataset = None
    currentGroup = None
    currentGroupIndex = -1
    currentDatasetIndex = -1
    for n in range(3,nCol):
      if dataSetIndex[n] != currentDatasetIndex:
        currentDataset = CMtzDataset(name=str(dataSetIndex[n]))
        currentDataset.name.set('Dataset: '+str(currentDatasetIndex))
        currentDatasetIndex = dataSetIndex[n]
        self.datasets.append(currentDataset)
      if groupIndex[n] != currentGroupIndex:
        currentGroupIndex = groupIndex[n]
        currentGroup = CMtzColumnGroup(groupType=colTypes[n])
        currentDataset.columnGroups.append(currentGroup)
      column = CMtzColumn(columnType=colTypes[n],columnLabel=colLabels[n])
      currentGroup.columns.append(column)
    '''
     
    return listOfDatasets,listOfColumns

  def columnSignature(self):
    sig = ''
    for column in self.__dict__['_value']['listOfColumns']:
      sig += str(column.columnType)
    return sig

  def columnNames(self,indx=0,nColumns=None):
    nameList = []
    if nColumns is None: nColumns = len(self.__dict__['_value']['listOfColumns'])
    for ic in range(indx,indx+nColumns):
      nameList.append(str(self.__dict__['_value']['listOfColumns'][ic].columnLabel))
    #print 'CMtxData.columnNames',nameList
    return nameList
      
  def sameCrystal(self,other=None,testLevel=None):
    # testLevel = 0 no spacegroup test
    # testLevel = 1 same point group
    # testLevel = 2 same Laue group
    # testLevel = 3 same space group
    # testLevel = 4 same space group and cell
    if testLevel is None:
      testLevel = 4
    #print 'sameCrystal',self.parent().objectName(),':',self.parent(),self.spaceGroup,other.parent().objectName(),':',other.parent(),other.spaceGroup,'testLevel=',testLevel
    #import traceback
    #traceback.print_stack(limit=5)
    err= CErrorReport()
    if other is None: return err
    details = '('+str(self.spaceGroup)+') to '+str(other.parent().qualifiers('guiLabel'))+' ('+str(other.spaceGroup)+')'
    path = str(self.parent().qualifiers('guiLabel'))
    if not self.__dict__['_value']['spaceGroup'].isSet() or not other.__dict__['_value']['spaceGroup'].isSet():
      #print 'CMtzData.sameCrystal not set',self.__dict__['_value']['spaceGroup'],other.__dict__['_value']['spaceGroup']
      return err
    if testLevel == 1:
      same = False
      for name,sGList in SYMMETRYMANAGER().chiralSpaceGroups.items():
        if  self.__dict__['_value']['spaceGroup'].__str__() in sGList:
          if  other.__dict__['_value']['spaceGroup'].__str__() in sGList: same = True
          break
      if not same:
        err.append(self.__class__,109,details,name=path,stack=False)
    elif testLevel == 2:
      same=False
      for lG in SYMMETRYMANAGER().laueGroups:
        if  self.__dict__['_value']['spaceGroup'].__str__() in lG:
          if  other.__dict__['_value']['spaceGroup'].__str__() in lG: same = True
          break
      if not same:
        err.append(self.__class__,108,details,name=path,stack=False)
    elif testLevel in [3,4]:
      if self.__dict__['_value']['spaceGroup'] != other.__dict__['_value']['spaceGroup']:
        err.append(self.__class__,105,details,name=path,stack=False)
      if testLevel == 4:
        for item in ['a','b','c','alpha','beta','gamma']:
          dif = (float(self.__dict__['_value']['cell'].__dict__['_value'][item]) - float(other.__dict__['_value']['cell'].__dict__['_value'][item])) / float(self.__dict__['_value']['cell'].__dict__['_value'][item])
          if dif < -1.0 or dif > 1.0:
            err.append(self.__class__,106,details,name=path,stack=False)
            break
    #if err.maxSeverity()>=SEVERITY_WARNING: print 'sameCrystal error:',err.report()
    return err

  def clipperSameCell(self,other,tolerance=None):
    import sys
    if tolerance is None:
      myHigh = self.resolutionRange.high.get()
      otherHigh = other.resolutionRange.high.get()
      if myHigh is None:
        if otherHigh is None:
          tolerance = 1.0
        else:
          tolerance = 1.5 * otherHigh
      elif otherHigh is None:
        tolerance = 1.5 * myHigh
      else:
        tolerance = 1.5 * max(myHigh,otherHigh)
    '''
    myCell = []
    for item in ['a','b','c','alpha','beta', 'gamma']:
      mycell.append(self.cell.get(item))
    print 'myCell',myCell
    otherCell = []
    for item in ['a','b','c','alpha','beta', 'gamma']:
      othercell.append(other.cell.get(item))
    print 'otherCell',otherCell
    '''
    import clipper
    myCell = clipper.Cell(clipper.Cell_descr(float(self.cell.a), float(self.cell.b), float(self.cell.c),
                                           float(self.cell.alpha), float(self.cell.beta), float(self.cell.gamma)))
    otherCell = clipper.Cell(clipper.Cell_descr(float(other.cell.a), float(other.cell.b), float(other.cell.c),
                                           float(other.cell.alpha), float(other.cell.beta), float(other.cell.gamma)))
    print 'SameCell 3'

    recipdifference = self.clipperRecipCellDifference(myCell,otherCell)
    print 'recipdifference',recipdifference;sys.stdout.flush()
    difference = self.clipperCellDifference(myCell,otherCell)
    print 'recipdifference',difference;sys.stdout.flush()
    
    equals = myCell.equals(otherCell, tolerance)  # Boolean
    print "Result:",equals, difference, recipdifference;sys.stdout.flush()
    """
    Return dictionary of:
      'validity'           True if cells are simlar within resolution of tolerance
      'maximumResolution1' maximum allowed resolution in cell1
      'maximumResolution2' maximum allowed resolution in cell2
      'difference'  average cell difference in A
      'tolerance'   in A
    """
    result = {'validity': equals,
              'maximumResolution1': recipdifference[0],
              'maximumResolution2': recipdifference[1],
              'difference': difference, 'tolerance': tolerance}
        
    return result

                                                 
  def clipperRecipCellDifference(self,cell1,cell2):
    import math,sys
    fracmat1 = cell1.matrix_frac()
    fracmat2 =cell2.matrix_frac()
    s = 0.0
    print 'cRCD 4 fracmat1[0][0]', fracmat1[0][0]; sys.stdout.flush()
    for j in range(3):
        for i in range(3):
            s += (fracmat1[i][j] - fracmat2[i][j])**2
    print 's',s;sys.stdout.flush()
    s = math.sqrt(s)
    print 's',s;sys.stdout.flush()
    volume1 = cell1.volume()
    print 'volume1',volume1;sys.stdout.flush()
    volume2 = cell2.volume()
    print 'volume2',volume2;sys.stdout.flush()
    rv = s * math.pow(volume1, 0.66666666667), \
           s * math.pow(volume2, 0.66666666667)
    print 'rv',rv;sys.stdout.flush()
    return rv

  def clipperCellDifference(self,cell1,cell2):
    import math,sys
    "Get resolution at which cell2 is different from cell1"
    orthmat1 = cell1.matrix_orth()
    orthmat2 = cell2.matrix_orth()
    #print "orth1\n", orthmat1.format()
    #print "orth2\n", orthmat2.format()
    s = 0.0
    for j in range(3):
        for i in range(3):
           s += (orthmat1[i][j] - orthmat2[i][j])**2
    return math.sqrt(s)
        
  def getColumnGroups(self):
    groupIndex = 0
    groupList = []

    # Sort listOfColumns into a list of grouped columns
    for ii in range(len(self.__dict__['_value']['listOfColumns'])):
      fileColumn = self.__dict__['_value']['listOfColumns'][ii]
      if  fileColumn.groupIndex != groupIndex:
        groupList.append( CColumnGroup(dataset=str(fileColumn.dataset)) )
        groupIndex = int(fileColumn.groupIndex)
      groupList[-1].columnList.append(CMtzColumn(columnLabel=fileColumn.columnLabel,columnType=fileColumn.columnType,groupIndex=fileColumn.groupIndex,dataset=fileColumn.dataset))

    #print 'CMztData.getColumnGroups'
    #for item in groupList: print item.get()

    # Assign the columnGroupType and contentFlag
    for group in groupList:
      signature = ''
      for col in group.columnList: signature = signature + str(col.columnType)
      for cls,label in [[CObsDataFile,'Obs'],[CPhsDataFile,'Phs'],[CMapCoeffsDataFile,'MapCoeffs'],[CFreeRDataFile,'FreeR'] ]:
        if cls.QUALIFIERS['correctColumns'].count(signature):
          group.columnGroupType.set(label)
          group.contentFlag.set(cls.QUALIFIERS['correctColumns'].index(signature)+1)
      if not group.columnGroupType.isSet():
        if signature == 'FPW':
          group.columnGroupType.set('Phs')
          group.contentFlag.set(2)
          group.columnList.pop(0)
        elif signature == 'PWF':
          group.columnGroupType.set('Phs')
          group.contentFlag.set(2)
          group.columnList.pop(2)
        elif signature == 'FQDQ':
          group.columnGroupType.set('Obs')
          group.columnList.pop(3)
          group.columnList.pop(2)
          group.contentFlag.set(4)
    #print 'CMztData.getColumnGroups'
    #for item in groupList: print item.get()

    return groupList

  def matthewsCoeff(self,seqDataFile=None,nRes=None,molWt=None):
    #print 'CMtzDataFile.matthewsCoeff',seqDataFile,nRes,molWt
    if seqDataFile is not None:
      try:
        molWt = seqDataFile.fileContent.getAnalysis('molecularWeight')
      except:
        molWt = 0.0
    elif nRes is not None:
        #  Estimated residue wt as per ccp4 matthews_coeff documentation
        molWt = 112.5 * float(nRes)

    if molWt < 0.01:
      raise CException(self.__class__,410,str(seqDataFile))

    # temporary log and xml files
    import tempfile,os, math
    f1 = tempfile.mkstemp()
    os.close(f1[0])
    f2 = tempfile.mkstemp()
    os.close(f2[0])
    
    comText = 'MOLWEIGHT '+str(molWt)+'\nCELL'
    for p in ['a','b','c']:
      comText =  comText + ' ' + str(self.cell.get(p))
    for p in ['alpha','beta','gamma']:
      a = float(self.cell.get(p))
      if a<3.0: a = a * 180. / math.pi
      comText =  comText + ' ' + str(a)
    comText =  comText + '\nSYMM '+str(self.spaceGroup.number()) + '\n'
    comText = comText + 'XMLO\nAUTO\n'
    argList = [ 'XMLFILE' , f1[1] ]  

    #print 'CMtzData.matthewsCoeff',comText, argList
    pid = CCP4Modules.PROCESSMANAGER().startProcess('matthews_coef',argList,logFile=f2[1], inputText=comText)
    #status = CCP4Modules.PROCESSMANAGER().getJobData(pid)
    #exitCode = CCP4Modules.PROCESSMANAGER().getJobData(pid,'exitCode')
    #print 'CMtzData.matthewsCoeff',pid,status,exitCode

    if not os.path.exists(f1[1]):
      raise CException(self.__class__,411,str(seqDataFile))

    # Get results from xml file
    import CCP4Utils
    rv = {'results' : []}
    xTree = CCP4Utils.openFileToEtree(fileName=f1[1])
    try:
      rv['cell_volume'] = float( xTree.xpath('cell')[0].get('volume'))
    except:
      pass
    xResultList = xTree.xpath('result')
    for xResult in xResultList:
      rv['results'].append ( { 'nmol_in_asu' : int(xResult.get('nmol_in_asu'))} )
      for item in ['matth_coef','percent_solvent','prob_matth']:
        rv['results'][-1][item] = float(xResult.get(item))
    #print 'CMtzData.matthewsCoeff',rv
    return rv
    
  def getWavelength(self):
    #print 'CMtzData.getWavelength',self.__dict__['_value']['wavelengths']
    for item in self.__dict__['_value']['wavelengths']:
      if item.isSet() and item > 0.01: return item
    return self.__dict__['_value']['wavelengths'][0]

  PROPERTIES  = { 'wavelength' : { 'fget' : getWavelength } }
                    

class CColumnGroup(CCP4Data.CData):
  """Groups of columns in MTZ - probably from analysis by hklfile"""
  CONTENTS =  { 'columnGroupType' : { 'class' : CCP4Data.COneWord, 'qualifiers' : { 'onlyEnumerators' : True, 'enumerators' : [ 'Obs', 'Phs', 'MapCoeffs', 'FreeR' ] }},
                'contentFlag' : { 'class' : CCP4Data.CInt },
                'dataset' :  { 'class' : CCP4Data.CString },
                'columnList' : { 'class' : CCP4Data.CList, 'subItem' : { 'class' : CMtzColumn } },
                'selected' :  { 'class' : CCP4Data.CBoolean }
                 }

  def columnListStr(self,withTypes=True,splitter=','):
    columnList = self.__dict__['_value']['columnList']
    if len(columnList)>0:
      label = str(columnList[0].columnLabel)
      if withTypes: label = label +'('+str(columnList[0].columnType)+')'        
      for col in columnList[1:]:
        label = label + splitter+str(col.columnLabel)
        if withTypes: label = label + '('+str(col.columnType)+')'
      return label
    else:
      return ''

class CColumnGroupList(CCP4Data.CList):
  SUBITEM = { 'class' : CColumnGroup }
                 


# This is a simple class to hold items of the requiredColumnsList qualifier for CMtzColumnGroup
# *****  Needs some validity checking **************

class CColumnGroupItem(CCP4Data.CData):
  """Definition of set of columns that form a 'group'"""
  # defaultList is string that is a comma separated list 
  CONTENTS =  { 'columnName' : { 'class' : CCP4Data.COneWord },
                'defaultList' :  { 'class' : CCP4Data.CString },
                'columnType' : { 'class' : CColumnTypeList },
                'partnerTo' : { 'class' : CCP4Data.COneWord },
                'partnerOffset' :  { 'class' : CCP4Data.CInt } }
  
  ERROR_CODES = { 1 : { 'description' : 'Attempting to change immutable object'},
                  2 : { 'description' : 'Attempting to access unknown attribute' }
                  }

  '''
  def __init__(self,value=[],qualifiers={},parent=None,name='', **kw):
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)
    CCP4Data.CData.__init__(self,value=value,qualifiers=qualis,parent=parent,name=name)
  '''

  def getEtree(self):
    #print 'CColumnGroupItem.getEtree',self.__dict__['_value']
    ele = etree.Element('columnGroupItem')
    ele.set('id',str(self.__dict__['_value']['columnName']))
    ele.append(self.__dict__['_value']['columnType'].getEtree())
    if self.__dict__['_value']['partnerTo'].get() is not None:
      rcData = etree.Element('partnerTo')
      rcData.text = str(self.__dict__['_value']['partnerTo'])
      ele.append(rcData)
      if self.__dict__['_value']['partnerOffset'].get() is not None:
        rcOffset = etree.Element('partnerOffset')
        rcOffset.text = str(self.__dict__['_value']['partnerOffset'])
        ele.append(rcOffset)
    if self.__dict__['_value']['defaultList'].get() is not None:
      e = self.__dict__['_value']['defaultList'].getEtree()
      ele.append(e)
    return ele
        
  def setEtree(self,element,checkValidity=True):
    self.__dict__['_value']['columnName'].set(str(element.get('id')))
    for ele in element.iterchildren():
      name = str(ele.tag)
      #print 'CColumnGroupItem.setEtree',repr(self),name
      if self.__dict__['_value'].has_key(name):
        self.__dict__['_value'][name].setEtree(ele)
    #print 'CColumnGroupItem.setEtree',self.__dict__['_value']


class CProgramColumnGroup0(CCP4Data.CData):
  
  CONTENTS = { 'columnGroup' : { 'class' : CMtzColumnGroup },
              'datasetName' : { 'class' : CCP4Data.CString } }

  QUALIFIERS = { 'mustExist' : False,
                 'mtzFileKey' : '',
                 'groupTypes' : [] }
  
  QUALIFIERS_ORDER = ['groupTypes','mtzFileKey','mustExist']
  QUALIFIERS_DEFINITION = {'groupTypes'   : { 'type' : types.ListType,
                                            'description' : 'Type of columnGroup required by program' },
                          'mtzFileKey'  : { 'type' :types.StringType,
                                            'description' : 'The key for a CMtxDataFile in the same CContainer' },
                          'mustExist'   : { 'type' : types.BooleanType,
                                            'description' : 'Flag if the parameter must be set at run time' } }
  
  ERROR_CODES = { 101 : { 'description' : 'Column not in MTZ file' },
                  102 : { 'description' : 'Column wrong type' },
                  103 : { 'description' : 'MTZ file is not defined',  'severity' : SEVERITY_WARNING },
                  104 : { 'description' : 'No column group selected' },
                  105 : { 'description' : 'No column group selected', 'severity' : SEVERITY_WARNING }
                  }

  
  def __getattr__(self,name):
    # This method necessary to get reference to columns (used most in scripts) to work
    # e.g. c.inputData.F_SIGF.FP
    #print 'CProgramColumnGroup0.__getattr__',self.__dict__['_qualifiers'].has_key('columnGroup'),self.__dict__['_value'].has_key('columnGroup')
    if self.__dict__['_qualifiers'].has_key('columnGroup') and self.__dict__['_value'].has_key('columnGroup'):
      ii = -1
      for column in self.__dict__['_qualifiers']['columnGroup']:
        ii = ii + 1
        #print 'CProgramColumnGroup0.__getattr__',column.columnName
        if str(column.columnName) == name:
          if len( self.__dict__['_value']['columnGroup'].columns)<=ii:
            return None
          else:
            return self.__dict__['_value']['columnGroup'].columns[ii].columnLabel.__str__()
    return CCP4Data.CData.__getattr__(self,name)

  
  def __setattr__(self,name,value):
    if self.__dict__['_qualifiers'].has_key('columnGroup') and self.__dict__['_value'].has_key('columnGroup'):
      ii = -1
      for column in self.__dict__['_qualifiers']['columnGroup']:
        ii = ii + 1
        if str(column.columnName) == name:
          if len( self.__dict__['_value']['columnGroup'].columns)<=ii:
            pass
          else:
            return self.__dict__['_value']['columnGroup'].columns[ii].set({'columnLabel':value})
    return CCP4Data.CData.__setattr__(self,name,value)
  


  def getMtzData(self):
    mtz =  self.getDataByKey('mtzFileKey')
    if mtz is None: return None
    return mtz.fileContent
    
  def setColumnGroup(self,datasetIndex=None,columnGroupIndex=None):
    mtzData = self.getMtzData()
    if mtzData is None: return
    try:
      cg = mtzData.datasets[datasetIndex].columnGroups[columnGroupIndex]
    except:
      print 'ERROR attemting to get dataset',datasetIndex,'and columngroup',columnGroupIndex,'from mtzdata'
      return
    self.__dict__['_value']['columnGroup'].set(cg)
    self.__dict__['_value']['datasetName'].set(mtzData.datasets[datasetIndex].name.__str__())

    
  def validity(self,value ={}):
    #print 'CProgramColumnGroup0.validity value',value,len(value)
    v = CErrorReport()
    if len(value) == 0 or not value.has_key('columnGroup') or value['columnGroup'].isSet():
      if  not self.qualifiers('allowUndefined'):
        v.append(self.__class__,104,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      else:
        v.append(self.__class__,105,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      return v
    if value['columnGroup'].type not in self.qualifers('types'):
      v.append(self.__class__,102,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    mtzData = self.getMtxData()
    if mtzData is None:
       v.append(self.__class__,103,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    else:
      group = mtz.fileContent.getColumnGroup(value)
      if group is None: v.append(self.__class__,103,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    return v
      
  def setColumnGroupQualifier(self,columnGroup=[],parent=None,initialise=False):
    # Expecting columnGroup to be a list of dicts or CColumnGroupItem
    # Each dict should have attributes for CColumnGroupItem object
    #print 'setColumnGroupQualifier',columnGroup
    if not self.__dict__.has_key('_qualifiers'):self.__dict__['_qualifiers'] = {}
    if initialise or (not self.__dict__['_qualifiers'].has_key('columnGroup')):
      self.__dict__['_qualifiers']['columnGroup'] = []
    indx = -1
    for item in columnGroup:
      indx = indx + 1
      if isinstance(item,CColumnGroupItem):
        itemObj = item
      else:
        itemObj = CColumnGroupItem(item,parent=parent)
      if indx>0:
        if itemObj.partnerTo is None: itemObj.partnerTo = str(self.__dict__['_qualifiers']['columnGroup'][0].columnName)
        if itemObj.partnerOffset is None: itemObj.partnerOffset = indx
      self.__dict__['_qualifiers']['columnGroup'].append(itemObj)
    #print 'setColumnGroupQualifier',initialise,len(self.__dict__['_qualifiers']['columnGroup'])
    #self.build()

  def qualifiers(self,name=None,default=True,custom=True,contentQualifiers=False):
    return CCP4Data.CData.qualifiers(self,name=name,default=default,custom=custom,contentQualifiers=False)
    
  def setQualifiers(self,qualifiers={},**kw):
    qualis = {}
    qualis.update(qualifiers)
    if qualis.has_key('columnGroup'):
      self.setColumnGroupQualifier(qualis['columnGroup'])
      del qualis['columnGroup']
    CCP4Data.CData.setQualifiers(self,qualis)
    

  def setQualifiersEtree(self,element=None):
    # Custom parsing etree to extract the columnGroup info
    rv = CErrorReport()
    self.__dict__['_qualifiers']['columnGroup'] = []
    if element is not None:
      cGele = element.find('columnGroup')
      if cGele is not None:
        for ele in cGele.iterchildren():
          #print 'CProgramColumnGroup.setQualifiersEtree ele',self.objectName(),ele.tag,ele.text
          try:
            if str(ele.tag) == 'columnGroupItem':
              item = CColumnGroupItem(parent=self)
              item.setEtree(ele)
              self.__dict__['_qualifiers']['columnGroup'].append(item)
          except CException as e:
            rv.extend(e)
          except:
            rv.append(self.__class__,107,name=self.objectName())
        # Remove the columnGroup element from the etree
        # and parse the rest with the base class method
        element.remove(cGele)
      #print 'CProgramColumnGroup.setQualifiersEtree',len(self.qualifiers('columnGroup'))
      #print 'setQualifiersEtree',self.objectName(),etree.tostring(element,pretty_print=True)
      rv.extend(CCP4Data.CData.setQualifiersEtree(self,element))
      #print 'CProgramColumnGroup.setQualifiersEtree',self.objectName(),self.__dict__['_qualifiers']

      #self.build()
      return rv
      
  def qualifiersEtree(self,customOnly=True,tag=None):
    error = CErrorReport()
    if tag is None: tag = self.objectName()
    if tag is None or len(tag)==0:
        tag = self.__class__.__name__
        error.append(CCP4Data.CData,14)

    root,err = CCP4Data.CData.qualifiersEtree(self,customOnly=customOnly,tag=tag)
    error.extend(err)
    # Remove the columnGroup element which needs doing differently
    try:
      ele = root.find('columnGroup')
      #print 'CProgramCOlumnGroup.qualifiersEtree',ele
      if ele is not None: root.remove(ele)
    except:
      pass    
    ele = etree.Element('columnGroup')
    columnGroups = self.qualifiers('columnGroup')
    if columnGroups is not NotImplemented:    
      for item in columnGroups:
        e = item.getEtree()
        ele.append(e)
    root.append(ele)
    
    return root,error

  def columnGroupNames(self):
    columnGroups = self.qualifiers('columnGroup')
    if columnGroups is NotImplemented: return []
    columnNameList = []
    for column in columnGroups:
      #columnNameList.append(str(column.columnName))
      columnNameList.append(column.guiLabel())
    return columnNameList
    
class CProgramColumnGroup(CCP4Data.CData):
  '''A group of MTZ columns required for program input'''

  ERROR_CODES = { 101 : { 'description' : 'Column not in MTZ file' },
                  102 : { 'description' : 'Column wrong type' },
                  103 : { 'description' : 'Error setting columnGroup qualifier' },
                  104 : { 'description' : 'Missing column selection' },
                  105 : { 'description' : 'Specified column not found in MTZ file' },
                  106 : { 'description' : 'Specified column has wrong type in MTZ file' },
                  107 : { 'description' : 'Error reading columnGroup qualifier from XML file' },
                  108 : { 'description' : 'No columnGroup qualifier' }
                  }

  CONTENTS = { }
  QUALIFIERS = { 'mustExist' : False,
                 'mtzFileKey' : '', # This is the name of the data item in the parent dataContainer
                 'toolTipList' : [],
                 'default' : []
                  } # A list of CColumnGroupItem
  QUALIFIERS_ORDER = ['mtzFileKey','mustExist','toolTipList','default']
  QUALIFIERS_DEFINITION = { 'mtzFileKey'  : { 'type' :types.StringType,
                                              'description' : 'The key for a CMtxDataFile in the same CContainer' },
                            'mustExist' : { 'type' : types.BooleanType,
                                            'description' : 'Flag if the parameter must be set at run time' },
                            'toolTipList' : { 'type' : types.ListType,
                                            'description' : 'Tooltips for columns in group' },
                            'default' : { 'type' : types.ListType, 'listItemType': types.StringType,
                                            'description' : 'Preferred values for column names' } }
  
  def build(self,qualifiers={}):
    self.__dict__['_value'] = {}
    for columnName in self.columnGroupNames():
        self.__dict__['_value'][columnName] = None
    #print 'CProgramColumnGroup.build value',self.__dict__['_value']

  def __init__(self,value={},qualifiers={},parent=None,name=None,**kw):
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)    
    CCP4Data.CData.__init__(self,qualifiers=qualis,parent=parent,name=name)  
  

  def contents(self):
    c = {}
    for columnName in self.columnGroupNames():
      c[columnName] = { 'class' : CCP4Data.CString }
    return c
      

  def getColumnGroupQualifier(self):
    if not self.__dict__['_qualifiers'].has_key('columnGroup'):
      return []
    
    colGroupList = []
    columnGroups = self.__dict__['_qualifiers']['columnGroup']
    #print 'CProgramColumnGroup.getColumnGroupQualifier',columnGroups
    for colGroupObj in columnGroups:
      colGroup = colGroupObj.get()
      colGroupList.append(colGroup)
    #print 'CProgramColumnGroup.getColumnGroupQualifier',colGroupList
    return colGroupList

  def nColumns(self):
    return len(self.__dict__['_value'])


  def findColumnsInMtz(self,data={}):
    '''
    Input is set of string values for columnLabels -
    Test if these columnLabels exist in the MTZ
    and return the CMtzColumns
    '''
    mtz = self.getDataByKey('mtzFileKey')
    #print 'findColumnsInMtz',data,self.MTZ_file,mtz,self.parent()
    if mtz is None or mtz.fileContent is None: return {}
    mtzColumns = {}
    for key,value in data.items():
      mtzColumns[key] = mtz.fileContent.getColumn(value)
      #if mtzColumns[key] is not None: nFound = nFound + 1
    #print 'findColumnsInMtz found',mtzColumns
    return mtzColumns


  def validity(self,data = {}):
    
    if self.qualifiers('mustExist'):
      mtzColumns = self.findColumnsInMtz(data=data)
    else:
      mtzColumns = {}
    #print 'CProgramColumnGroup.validity',self.objectName(),mtzColumns
    v = CErrorReport()
    unsetItems = 0
    columnGroups = self.qualifiers('columnGroup')
    if columnGroups is NotImplemented: return v
    for column in self.qualifiers('columnGroup'):
      columnName = str(column.columnName)
      if not data.has_key(columnName):
        if not self.qualifiers('allowUndefined'): v.append(self.__class__,104,columnName,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      elif data[columnName] is None or  data[columnName] is NotImplemented or data[columnName] == '':
        if not self.qualifiers('allowUndefined'): v.append(self.__class__,104,columnName,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      else:
        if self.qualifiers('mustExist'):
          if not mtzColumns.has_key(columnName) or mtzColumns[columnName] is None:
             v.append( self.__class__,105,columnName,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
          elif column.columnType.count(str(mtzColumns[columnName].columnType))==0:
             v.append( self.__class__,106,columnName,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    #print 'CProgramColumnGroup.validity',self.objectName(),v
    return v
    

  def fix(self,data={}):
    mtz = self.getDataByKey('mtzFileKey')
    #if self.objectName() == 'ABCD':
    #  print 'CProgramColumnGroup.fix',self.objectName(),data,mtz
    #  import traceback; traceback.print_stack(limit=5)
    if mtz is None: return -1
    nUnset = 0
    columnGroups = self.qualifiers('columnGroup')
    if columnGroups is NotImplemented: return {}

    for column in columnGroups:
      columnName = str(column.columnName)
      if data.get(columnName,None) is None:
        nUnset = nUnset + 1
        if column.partnerTo is not None and data.get(str(column.partnerTo),None) is not None:
          
          partnerColumn = mtz.fileContent.getPartnerColumn(firstColumn=data.get(str(column.partnerTo)),columnGroupItem=column)
      
          if  partnerColumn is not None:
            data[columnName] = str(partnerColumn)
            nUnset = nUnset - 1
    #if self.objectName() == 'ABCD': print 'CProgramColumnGroup.fix DONE',data
    #print 'CProgramColumnGroup.fix',data
    return data

  def __setattr__(self,name,value):
    if self.columnGroupNames().count(name):
      self.__dict__['_value'][name] = value
    else:
      CCP4Data.CData.__setattr__(self,name,value)

 
  def set(self,data={},fix=False,**kw):
    #print 'CProgramColumnGroup.set',self.objectName(),data
    if isinstance(data,CProgramColumnGroup):
      data = data.get()
    data.update(kw)
    validity = self.validity(data)
    #print 'CProgramColumnGroup.set',self.objectName(),data,validity,self.__dict__['_value'].keys()
 
    # If insufficient data try filling out from partner column information
    if validity.count(code=104)>0 or fix:
      data = self.fix(data)
      validity = self.validity(data)
      #print 'CProgramColumnGroup.set fixed',data,validity.report()
      
    if validity.maxSeverity()<SEVERITY_ERROR:
      for column in self.qualifiers('columnGroup'):
        columnName = str(column.columnName)
        #print 'CProgramColumnGroup.set',columnName,data.get(columnName,None)
        if data.has_key(columnName):
          #self.__dict__['_value'][columnName] = data[columnName]
          if data[columnName] is None:
            self.__dict__['_value'][columnName] = None
          else:
            self.__dict__['_value'][columnName] = data[columnName].split('/')[-1]
      self.emitDataChanged()
    else:
      e = CException()
      e.extend(validity)
      raise e

  def unSet(self):
    for column in self.qualifiers('columnGroup'):
      self.__dict__['_value'][str(column.columnName)] = None

  def get(self,name='columnGroup'):
    #print 'CProgramColumnGroup.getData',self.value
    if name == 'guiLabels':
      data = {}
      mtzColumns = self.findColumnsInMtz(data=self.__dict__['_value'])
      for key,columnObj in mtzColumns.items():
        if columnObj is None:
          data[key] = None
        else:
          data[key] = columnObj.guiLabel()
      return data
    elif name == 'columnGroup':
      return self.__dict__['_value']
    else:
      return CCP4Data.CData.get(self,name)


  def setEtree(self,element=None,checkValidity=True):
    #print 'CProgramColumnGroup.setEtree',element,self.qualifiers('columnGroup')

    rv = CErrorReport()
    if element is not None:
      data = {}     
      for columnName in self.columnGroupNames():
        ele = element.find(columnName)
        if ele is not None:
          if ele.text is None:
            data[columnName] = None
          else:
            data[columnName] = str(ele.text)
      #print 'CMtzColumnGroup.parseEtree',self,key,data
      rv.extend(self.set(data))
      return rv

  def getEtree(self):   
    name = self.objectName()
    if name is None or len(name)==0: name = self.className()
    #print 'CProgramColumnGroup.getEtree',name,self.__dict__['_value']
    element = etree.Element(name)
    for columnName in self.columnGroupNames():
        ele = etree.Element(columnName)
        if self.__dict__['_value'][columnName] is not None:
          ele.text = self.__dict__['_value'][columnName]
        else:
          ele.text = ''
        element.append(ele)
    return element

  def setColumnGroupQualifier(self,columnGroup=[],parent=None,initialise=False):
    # Expecting columnGroup to be a list of dicts or CColumnGroupItem
    # Each dict should have attributes for CColumnGroupItem object
    #print 'setColumnGroupQualifier',columnGroup
    if not self.__dict__.has_key('_qualifiers'):self.__dict__['_qualifiers'] = {}
    if initialise or (not self.__dict__['_qualifiers'].has_key('columnGroup')):
      self.__dict__['_qualifiers']['columnGroup'] = []
    indx = -1
    for item in columnGroup:
      indx = indx + 1
      if isinstance(item,CColumnGroupItem):
        itemObj = item
      else:
        itemObj = CColumnGroupItem(item,parent=parent)
      if indx>0:
        if itemObj.partnerTo is None: itemObj.partnerTo = str(self.__dict__['_qualifiers']['columnGroup'][0].columnName)
        if itemObj.partnerOffset is None: itemObj.partnerOffset = indx
      self.__dict__['_qualifiers']['columnGroup'].append(itemObj)
    #print 'setColumnGroupQualifier',initialise,len(self.__dict__['_qualifiers']['columnGroup'])
    self.build()

  def qualifiers(self,name=None,default=True,custom=True,contentQualifiers=False):
    return CCP4Data.CData.qualifiers(self,name=name,default=default,custom=custom,contentQualifiers=False)
    
  def setQualifiers(self,qualifiers={},**kw):
    qualis = {}
    qualis.update(qualifiers)
    if qualis.has_key('columnGroup'):
      self.setColumnGroupQualifier(qualis['columnGroup'])
      del qualis['columnGroup']
    CCP4Data.CData.setQualifiers(self,qualis)
    

  def setQualifiersEtree(self,element=None):
    # Custom parsing etree to extract the columnGroup info
    rv = CErrorReport()
    self.__dict__['_qualifiers']['columnGroup'] = []
    if element is not None:
      cGele = element.find('columnGroup')
      if cGele is not None:
        for ele in cGele.iterchildren():
          #print 'CProgramColumnGroup.setQualifiersEtree ele',self.objectName(),ele.tag,ele.text
          try:
            if str(ele.tag) == 'columnGroupItem':
              item = CColumnGroupItem(parent=self)
              item.setEtree(ele)
              self.__dict__['_qualifiers']['columnGroup'].append(item)
          except CException as e:
            rv.extend(e)
          except:
            rv.append(self.__class__,107,name=self.objectName())
        # Remove the columnGroup element from the etree
        # and parse the rest with the base class method
        element.remove(cGele)
      #print 'CProgramColumnGroup.setQualifiersEtree',len(self.qualifiers('columnGroup'))
      #print 'setQualifiersEtree',self.objectName(),etree.tostring(element,pretty_print=True)
      rv.extend(CCP4Data.CData.setQualifiersEtree(self,element))
      #print 'CProgramColumnGroup.setQualifiersEtree',self.objectName(),self.__dict__['_qualifiers']

      self.build()
      return rv
      
  def qualifiersEtree(self,customOnly=True,tag=None):
    error = CErrorReport()
    if tag is None: tag = self.objectName()
    if tag is None or len(tag)==0:
        tag = self.__class__.__name__
        error.append(CCP4Data.CData,14)

    root,err = CCP4Data.CData.qualifiersEtree(self,customOnly=customOnly,tag=tag)
    error.extend(err)
    # Remove the columnGroup element which needs doing differently
    try:
      ele = root.find('columnGroup')
      #print 'CProgramCOlumnGroup.qualifiersEtree',ele
      if ele is not None: root.remove(ele)
    except:
      pass    
    ele = etree.Element('columnGroup')
    columnGroups = self.qualifiers('columnGroup')
    if columnGroups is not NotImplemented:    
      for item in columnGroups:
        e = item.getEtree()
        ele.append(e)
    root.append(ele)
    
    return root,error

  def columnGroupNames(self):
    columnGroups = self.qualifiers('columnGroup')
    if columnGroups is NotImplemented: return []
    columnNameList = []
    for column in columnGroups:
      columnNameList.append(str(column.columnName))
    return columnNameList

  def isSet(self,allowUndefined=False,allowDefault=True,allSet=True):
    #print 'CProgramColumnGroup.isSet',self.contents().keys()
    for key in self.columnGroupNames():
      if self._value[key] is None:
        return False
    return True

class CFSigFColumnGroup(CProgramColumnGroup):

  QUALIFIERS = { 'guiLabel' : 'Structure factor and sigma' }

  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'F',
                                                    'columnType' : [ 'F' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    item =  CColumnGroupItem( parent=self, value ={ 'columnName' : 'SIGF',
                                                    'columnType' : [ 'Q' ],
                                                    'partnerTo': 'F',
                                                    'partnerOffset' : 1 })
    self.__dict__['_qualifiers']['columnGroup'].append(item)

    CProgramColumnGroup.build(self)
    
class CISigIColumnGroup(CProgramColumnGroup):
  QUALIFIERS = { 'guiLabel' : 'Intensity and sigma' }

  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'I',
                                                    'columnType' : [ 'J' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    item =  CColumnGroupItem( parent=self, value ={ 'columnName' : 'SIGI',
                                                    'columnType' : [ 'Q' ],
                                                    'partnerTo': 'I',
                                                    'partnerOffset' : 1 })
    self.__dict__['_qualifiers']['columnGroup'].append(item)

    CProgramColumnGroup.build(self)

class CFPairColumnGroup(CProgramColumnGroup):
  QUALIFIERS = { 'guiLabel' : 'Anomalous structure factors and sigma' }
  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'Fplus',
                                                    'columnType' : [ 'G' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    offset = 0
    for name,coltype in [['SIGFplus','L'],['Fminus','G'],['SIGFminus','L']]:      
      offset += 1
      item =  CColumnGroupItem( parent=self, value ={ 'columnName' : name,
                                                    'columnType' : [ coltype ],
                                                    'partnerTo': 'Fplus',
                                                    'partnerOffset' : offset })
      self.__dict__['_qualifiers']['columnGroup'].append(item)
    CProgramColumnGroup.build(self)

class CIPairColumnGroup(CProgramColumnGroup):
  QUALIFIERS = { 'guiLabel' : 'Anomalous intensities and sigma' }
  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'Iplus',
                                                    'columnType' : [ 'K' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    offset = 0
    for name,coltype in [['SIGIplus','M'],['Iminus','K'],['SIGIminus','M']]:      
      offset += 1
      item =  CColumnGroupItem( parent=self, value ={ 'columnName' : name,
                                                    'columnType' : [ coltype ],
                                                    'partnerTo': 'Iplus',
                                                    'partnerOffset' : offset })
      self.__dict__['_qualifiers']['columnGroup'].append(item)
    CProgramColumnGroup.build(self)
  
    
class CHLColumnGroup(CProgramColumnGroup):
  QUALIFIERS = { 'guiLabel' : 'Hendrickson-Lattmann coefficients' }
  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'HLA',
                                                    'columnType' : [ 'A' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    offset = 0
    for name in ['HLB','HLC','HLD']:      
      offset += 1
      item =  CColumnGroupItem( parent=self, value ={ 'columnName' : name,
                                                    'columnType' : [ 'A' ],
                                                    'partnerTo': 'HLA',
                                                    'partnerOffset' : offset })
      self.__dict__['_qualifiers']['columnGroup'].append(item)
    CProgramColumnGroup.build(self)
    
class CPhiFomColumnGroup(CProgramColumnGroup):
  QUALIFIERS = { 'guiLabel' : 'Phase and figure of merit' }

  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'PHI',
                                                    'columnType' : [ 'P' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    item =  CColumnGroupItem( parent=self, value ={ 'columnName' : 'FOM',
                                                    'columnType' : [ 'W' ],
                                                    'partnerTo': 'PHI',
                                                    'partnerOffset' : 1 })
    self.__dict__['_qualifiers']['columnGroup'].append(item)

    CProgramColumnGroup.build(self)
    
class CMapColumnGroup(CProgramColumnGroup):
  QUALIFIERS = { 'guiLabel' : 'Structure factor and phase to define a map' }

  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'F',
                                                    'columnType' : [ 'F' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    item =  CColumnGroupItem( parent=self, value ={ 'columnName' : 'PHI',
                                                    'columnType' : [ 'P' ],
                                                    'partnerTo': 'F',
                                                    'partnerOffset' : 1 })
    self.__dict__['_qualifiers']['columnGroup'].append(item)

    CProgramColumnGroup.build(self)
    
class CFreeRColumnGroup(CProgramColumnGroup):
  
  QUALIFIERS = { 'guiLabel' : 'Set of FreeR flags' }
  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'FREER',
                                                    'columnType' : [ 'I' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    CProgramColumnGroup.build(self)

class CAnomalousIntensityColumnGroup(CProgramColumnGroup):

  '''
  Selection of I and AnomI columns from MTZ.
  Expected to be part of ab initio phasing dataset ( CDataset)
  '''
  QUALIFIERS = { 'toolTipList' : [ 'The real part of the experimental intensity',
                                   'The anomalous part of the experimental intensity' ],
                 'guiLabel' : 'Intensity and anomalous intensity' }
  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'I',
                                                    'columnType' : [ 'J' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    item =  CColumnGroupItem( parent=self, value ={ 'columnName' : 'AnomI',
                                                    'columnType' : [ 'J' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)

    CProgramColumnGroup.build(self)

  def columnGroupNames(self):
    return ['I','AnomI']
  
  # The CProgramColumnGroup base class expects a qualifier mtzFileKey which is the
  # name of an CMtzDatFile object - how does it get this if it is part of a CDataset?
    
class CAnomalousColumnGroup(CProgramColumnGroup):

  '''
  Selection of F/I and AnomF/I columns from MTZ.
  Expected to be part of ab initio phasing dataset ( CDataset)
  '''
  QUALIFIERS = { 'toolTipList' : [ 'The real part of the experimental structure factors',
                                   'The anomalous part of the experimental structure factors' ] }
  
  def build(self,qualifiers={}):
    self.__dict__['_qualifiers']['columnGroup'] = []
    item = CColumnGroupItem( parent=self, value ={ 'columnName' : 'F/I',
                                                    'columnType' : [ 'F','J' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)
    item =  CColumnGroupItem( parent=self, value ={ 'columnName' : 'AnomF/I',
                                                    'columnType' : [ 'F','J' ] })
    self.__dict__['_qualifiers']['columnGroup'].append(item)

    CProgramColumnGroup.build(self)
     
  def columnGroupNames(self):
    return ['F/I','AnomF/I']

  # Use the generic getEtree/setEtree 
  def getEtree(self):
    CCP4Data.CData.getEtree(self)

  def setEtree(self,element,checkValidity=True):
    CCP4Data.CData.setEtree(self,element)
    
class CExperimentalDataType(CCP4Data.CString):
  '''Experimental data type e.g. native or peak'''
  QUALIFIERS = { 'onlyEnumerators' : True,
                 'enumerators' : ['native','derivative','SAD','peak','inflection','high_remote','low_remote',''],
                 'default' : 'SAD' }

class CFormFactor(CCP4Data.CData):

  '''
  The for factor (Fp and Fpp) for a giving element and wavelength
  '''

  CONTENTS = { 'Fp' : { 'class' : CCP4Data.CFloat, 'qualifiers' : { 'toolTip' : "Form factor F' for element at given wavelength" }  },
               'Fpp' : { 'class' : CCP4Data.CFloat, 'qualifiers' : { 'toolTip' : "Form factor F'' for element at given wavelength" } } }
  CONTENTS_ORDER = [ 'Fp', 'Fpp' ]

  def validity(self,arg):
    err = CErrorReport()
    return err

  def guiValue(self):
    return self.__dict__['_value']['Fp'].__str__()+','+self.__dict__['_value']['Fpp'].__str__()

  # Do we need lookup/calculate values if not provided?

  

class CAnomalousScatteringElement(CCP4ModelData.CElement):
  '''Definition of a anomalous scattering element'''
  
  QUALIFIERS = { 'onlyEnumerators' : False,
                 'enumerators' : ['Br','Fe','Pt','Se'],
                 'charWidth' : 4,
                 'default' : 'Se' }
  


class CShelxLabel(CCP4Data.CString):
  QUALIFIERS = { 'onlyEnumerators' : True,
                         'default' : 'UNDEFINED',
                     'enumerators' : [ 'UNDEFINED','HREM','LREM','PEAK','INFL','NAT','DERI'],
                        'menuText' : [ 'undefined','high remote','low remote','peak','inflection',
                                                 'native','derivative' ],
                       'toolTip'   : 'Hint to Shelx for the use of the dataset' }
            
  
  


class CAsuComponent(CCP4Data.CData):
  '''A component of the asymmetric unit. This is for use in MR, defining
     what we are searching for. '''

  CONTENTS = { 'moleculeType' : { 'class' : CCP4Data.CString,
                     'qualifiers' : { 'onlyEnumerators' : True,
                                      'enumerators' : ['PROTEIN','NUCLEIC'],
                                      'menuText' : ['protein','nucleic acid'],
                                      'default' : 'PROTEIN',
                                      'toolTip' : 'Molecule type' } },
               'seqFile' : { 'class' :  CCP4ModelData.CSeqDataFile,
                             'qualifiers' : { 'jobCombo' : False,
                                              'mustExist' : True,
                                              'allowUndefined' : False } },
               'numberOfCopies' : { 'class' : CCP4Data.CInt,
                                 'qualifiers' : { 'allowUndefined': False,
                                                  'toolTip' : 'Number of copies of sequence',
                                                  'min' : 0,
                                                   'max' : 999,
                                                   'default': 1,
                                                   'enumerators' : [ 1,2,3,4,5,6,7,8,9,10,11,12 ] } } }

  def getTableTextItem(self):
    return getTextItem()
  
  def getTextItem(self):
    if self.__dict__['_value']['seqFile'].isSet():
      if self.__dict__['_value']['seqFile'].annotation.isSet():
        return str(self.__dict__['_value']['numberOfCopies']) + ' x '+str(self.__dict__['_value']['seqFile'].annotation)
      else:
        return str(self.__dict__['_value']['numberOfCopies']) + ' x '+str(self.__dict__['_value']['seqFile'])
    else:
      return '--'
    
class CAsuComponentList(CCP4Data.CList):
  SUBITEM = { 'class' : CAsuComponent }
  QUALIFIERS = { 'listMinLength' : 1,
                 'guiLabel' : 'Contents of asymmetric unit' } 

  def validity(self,arg):
    try:
      mode = self.parent().parent().find('COMP_BY').__str__()
    except:
      mode = 'ASU'
    #print 'CAsuComponentList.validity',mode
    if mode != 'ASU':
      return CErrorReport()
    else:
      err = CCP4Data.CList.validity(self,arg)
      if len(arg)<1: err.append(self.__class__,101,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    return err

  '''
  def isSet(self):
    try:
      mode = self.parent().parent().find('COMP_BY').__str__()
    except:
      mode = 'ASU'
    print 'CAsuComponentList.isSet',mode
    if mode != 'ASU':
      return True
    else:
      return  CCP4Data.CList.isSet(self)
  '''
  
  def saveToDb(self):
    saveList = []
    for obj in self.__dict__['_value']:
      if obj.seqFile.isSet():
        saveList.append(obj.seqFile)
    #print 'CAsuComponentList.saveToDb',saveList
    return saveList,None,{}

  def molecularWeight(self):
    wt = 0.0
    for ii in range(self.__len__()):
      seqAnalysis = self.__dict__['_value'][ii].seqFile.fileContent.getAnalysis()
      wt = wt + ( seqAnalysis * float(self.__dict__['_value'][ii].numberOfCopies) ) 
    return wt


class CMiniMtzDataFile(CMtzDataFile):

  SUBTYPE_MENU = None
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-mtz-mini',
                 'fileExtensions' : ['mtz','cif','ent'],
                 'fileContentClassName' : 'CMtzData',
                 'saveToDb' : True,
                 'correctColumns' :  ['FQ','JQ','GLGL','KMKM','AAAA','PW','FP','I'],
                 'columnGroupClassList' : NotImplemented,
                 'toolTip' : 'Mini-MTZ file containing reflection,phases,FreeR set or map coefficients',
                 'helpFile' : 'data_files#MTZ' 
                 }
  QUALIFIERS_ORDER = ['fileExtensions','mimeTypeName','mimeTypeDescription','allowUndefined','mustExist','fromPreviousJob','jobCombo','fileContentClassName','isDirectory','saveToDb','requiredSubType','requiredContentFlag','correctColumns','columnGroupClassList','sameCrystalAs']
  QUALIFIERS_DEFINITION = {'correctColumns' :  { 'type' : types.ListType, 'listItemType' : types.StringType,
                                 'description' : 'A list of coloumn data types expected in the file'} }

  ERROR_CODES = { 201 : { 'description' : 'Wrong number of columns' } ,
                  202 : { 'description' : 'Wrong column types' } ,
                  203 : { 'description' : 'No correct column types found in file' },
                  204 : { 'description' : 'Duplicate or additional column types found in file' },
                  205 : { 'description' : 'Columns in file have non-standard labels' },
                  206 : { 'description' : 'File contains unmerged data' },
                  210 : { 'description' :  'Failed creating mini-MTZ' },
                  211 : { 'description' :  'Insufficient columns selected from imported MTZ' },
                  212 : { 'description' :  'Data already imported as', 'severity' : SEVERITY_WARNING },
                  220 : { 'description' :  'Can not convert file content, file does not exist' },
                  221 : { 'description' :  'Can not convert file content, existing content insufficiently rich' },
                  222 : { 'description' :  'Can not convert file content, bad input for target content' },
                  223 : { 'description' :  'Can not recognise file content' },
                  224 : { 'description' :  'Not possible to convert to required content - no mechanism implemented' },
                  225 : { 'description' :  'Failed importing from an mmcif file - failed running cif2mtz' },
                  226 : { 'description' :  'Failed importing from an mmcif file - no output from cif2mtz' }
                  }
  
  def __init__(self,value={},qualifiers={},parent=None,name=None,fullPath=None,**kw):
    #print 'CMiniMtzDataFile.__init__'
    qualis = {}
    qualis.update(qualifiers)
    qualis.update(kw)
    CCP4File.CDataFile.__init__(self,value=value,qualifiers=qualis,parent=parent,name=name,fullPath=fullPath,keywords=kw)
    self.__dict__['sourceFileName'] = None

  def getSourceFileName(self):
    return self.__dict__['sourceFileName']
  
  def conversion(self,targetContent):
    return 'ok',targetContent

  def miniMtzType(self):
    '''
    Test for mini-MTZ type and contentFlag
    '''
    self.loadFile()
    signature = self.fileContent.columnSignature()
    #print 'CMiniMtzDataFile.miniMtzType signature',self.objectName(),self,signature,fileInfo
    for cls in [CObsDataFile,CPhsDataFile,CMapCoeffsDataFile,CFreeRDataFile]:
      contentFlag = 0
      for correctSig in cls.QUALIFIERS['correctColumns']:
        #print 'CMiniMtzDataFile.miniMtzType',correctSig,signature == correctSig
        contentFlag += 1
        if signature == correctSig:
          #print 'CMiniMtzDataFile.miniMtzType',cls.__name__,contentFlag
          return cls,contentFlag
    return None,None
    
  def validColumns(self,correctColumns=None):
    import re
    rv = CErrorReport()
    #if self.fileContent is None: self.loadFile()
    self.loadFile()
    if not self.fileContent.merged:
      rv.append(self.__class__,206,name=self.objectPath())
    #iprint 'CXObsDataFile.validColumns',self,self.fileContent.listOfColumns,self.qualifiers('correctColumns')
    if correctColumns is None: correctColumns = self.qualifiers('correctColumns')
    signature = self.getFileContent().columnSignature()
    #print 'validColumns signature',signature
    nMap = 0
    nDup = 0
    badColumnNames = False
    sigListIndx = -1
    for items in correctColumns:
      sigListIndx += 1
      n = signature.count(items)
      if n>1: nDup += 1
      if n>=1:
        # Do the columns have the correct column names?
        columnNames = self.fileContent.columnNames(signature.index(items),len(items))
        #print 'CMiniMtzDataFile.validColumns columnLabels',columnNames,sigListIndx,self.CONTENT_SIGNATURE_LIST[sigListIndx]
        if columnNames != self.CONTENT_SIGNATURE_LIST[sigListIndx]:
          badColumnNames = True
      nMap += n
      signature = re.sub(items,'',signature)
    # None of required columns found
    if nMap == 0:
      rv.append(self.__class__,203,name=self.objectPath())
    # Ambiguity of required column or additional columns
    # Column selection will be displayed
    elif nDup>1 or len(signature)>0:
      rv.append(self.__class__,204,name=self.objectPath())
    elif badColumnNames:
      #print 'CMiniMtzDataFile.validColumns badColumnNames True'
      """
      # Set the columnGroup data that woud normally be set by the column selection
      # dialog so we can just use the same code to call splitMtz
      # NO-- just handle this case the usual way for 'monster' mtz so it will go through the dialog
      columnGroup = self.columnGroup()
      for name in columnNames:
        columnGroup[sigListIndx].columnName.set(name)
      print 'CMiniMtzDataFile.validColumns setting columnGroup',columnGroup[sigListIndx]
      """
      rv.append(self.__class__,205,name=self.objectPath())
    return rv

  def columnGroup(self):
    if not self.__dict__.has_key('columnGroupList'):
      self.__dict__['columnGroupList'] = []
      clsList = self.qualifiers('columnGroupClassList')
      if not isinstance(clsList,list): clsList = [clsList]
      for cls in clsList:
        self.__dict__['columnGroupList'].append(cls(parent=self.parent(),name=self.objectName()+'_COLUMNS',qualifiers={'mtzFileKey': self.objectName() }) )
      #print 'CMiniMtzDataFile.columnGroup',clsList,self.objectName(),self.__dict__['columnGroupList']
    return self.__dict__['columnGroupList']

  def defaultName(self,jobId=None):
    import os
    jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId)
    return  os.path.normpath(os.path.join(jobDirectory,self.objectName()+CCP4File.CDataFile.SEPARATOR+self.qualifiers('fileLabel')+'.mtz'))

  def set(self,value={},**kw):
    import CCP4File
    #print 'CMiniMtzDataFile.set',self.objectName(),self.__dict__['sourceFileName']
    apply(CCP4File.CDataFile.set,[self,value],kw)
    self.__dict__['sourceFileName'] = None



  def splitMtz(self,jobId=None,projectId=None,contentFlag=None,i2Labels=[],columnLabels=[]):
    #print 'CMiniMtzDataFile.splitMtz',contentFlag,i2Labels,columnLabels
    errorReport = CErrorReport()
    import os
    import CCP4Utils
    
    # Set name for new split file and if it already exists remove previous refernce from db
    jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId)
    filename = self.importFileName(jobDirectory=jobDirectory)

    #Set up the colin/colout
    colin = ''
    for item in columnLabels: colin = colin + item +','
    colin = colin[0:-1]
    colout=''
    for item in i2Labels: colout = colout + item + ','
    colout = colout[0:-1]

    if isinstance(self,CFreeRDataFile):
      fileType = 10
    elif isinstance(self,CObsDataFile):
      fileType = 11
    elif isinstance(self,CPhsDataFile):
      fileType = 12
    elif isinstance(self,CMapCoeffsDataFile):
      fileType = 13
    else:
      fileType = None
      
  
    #Have we done the same import before?
    dbFileId,importId,checksum,dbAnnotation = CCP4Modules.PROJECTSMANAGER().alreadyImportedId(sourceFileName=self.__str__(),projectId=projectId,
                                                        contentFlag=contentFlag, sourceFileReference=colin,fileType=fileType)
    print 'CMiniMtzDataFile.splitMtz alreadyImportedId in',self.__str__(),projectId,fileType,contentFlag,colin
    print 'Testing if file previously imported previous db id',dbFileId
    if dbFileId is not None:
      self.setDbFileId(dbFileId)
      errorReport.append(self.__class__,212,name=self.objectPath(),details=dbAnnotation)
      return errorReport

    # Run cmtzsplit
    logFile =  os.path.normpath(os.path.join(jobDirectory,self.objectName()+'_mtzsplit.log'))
    bin = os.path.normpath(os.path.join( CCP4Utils.getOSDir(), 'bin', 'cmtzsplit' ))
    if not os.path.exists(bin):
      bin = os.path.normpath(os.path.join( CCP4Utils.getCCP4Dir(), 'bin', 'cmtzsplit' ))
    arglist = [ '-mtzin', self.__str__() ]
    arglist.extend(['-mtzout',filename])
    arglist.extend(['-colin',colin,'-colout',colout])
    #print 'CMiniMtzDataFile.splitMtz arglist',bin,arglist
    pid = CCP4Modules.PROCESSMANAGER().startProcess(bin,arglist,logFile=logFile)
    status = CCP4Modules.PROCESSMANAGER().getJobData(pid)
    exitCode = CCP4Modules.PROCESSMANAGER().getJobData(pid,'exitCode')
    #print 'CMiniMtzDataFile.splitMtz',status,exitCode
    if status != 0:
      #print 'CMiniMtzDataFile.splitMtz arglist',bin,arglist
      errorReport.append(self.__class__,210,'Exit status:'+str(status),name=self.objectPath())
      return errorReport

    #Replace 'dataset' in the annotation with the correct dataset name
    annotation = self.annotation.__str__()
    if annotation.count('/dataset'):
      mtzColumn = self.fileContent.getColumn(columnLabel)
      dataset = ''
      if mtzColumn is not None and mtzColumn.dataset is not None and mtzColumn.dataset != 'HKL_base':
        dataset = '/'+mtzColumn.dataset.__str__()
      else:
        dataset = ''
      import re
      annotation = re.sub('/dataset',dataset,annotation)
    # Set sourceFileName and new file name for handling by PROJECTSMANAGER.importFiles()      
    sourceFileName = self.__str__()
    self.setFullPath(filename)
    self.unsetFileContent()
    self.contentFlag.set(contentFlag)
    self.annotation.set(annotation)
    #print 'CMiniMtzDataFile.splitMtz after split sourceFileName',sourceFileName,colin[0:-1]
    self.__dict__['sourceFileName'] = sourceFileName
    self.__dict__['sourceFileReference'] = colin[0:-1]

    return errorReport


  def contentSignature(self):
    return []

  
  def setContentFlag(self,reset=False):
    # Test file content to determine the contentFlag
    # is None => don't know or is not valid column label signature
    #print 'CMiniMtzDataFile.setContentFlag',self.objectName(),
    #print str(self.getFullPath()),self.isSet(),self.exists()
    #print 'CMiniMtzDataFile.setContentFlag',self.objectName(),self,fileInfo
    if (not reset) and self.__dict__['_value']['contentFlag'].isSet():
      return int(self.__dict__['_value']['contentFlag'])

    self.__dict__['_value']['contentFlag'].unSet()
    if (not self.isSet()) or (not self.exists()): return None

    # Try is info in Db
    if self.dbFileId.isSet() and not reset:
      flag = CCP4Modules.PROJECTSMANAGER().db().getFileInfo(fileId=str(self.dbFileId),mode='filecontent')
      #print 'CMiniMtzDataFile.setContentFlag from db',self.objectPath(),flag
      if flag is not None: 
        self.__dict__['_value']['contentFlag'].set(flag)
        return flag
    
    sigList = self.contentSignature()
    #print 'CMiniMtzDataFile.setContentFlag sigList', sigList
    columnList = self.getFileContent().getListOfColumns()
    labelList=[]
    for item in columnList: labelList.append(item.columnLabel.__str__())
    #print 'CMiniMtzDataFile.setContentFlag labelList',self.__str__(),labelList    
    flag = 1
    while flag<=len(sigList):
      if labelList == sigList[flag-1]:
        self.__dict__['_value']['contentFlag'].set(flag)
        #print 'CMiniMtzDataFile.setContentFlag',flag
        return flag
      else:
        flag += 1
    return None
    
  def updateData(self):
    CMtzDataFile.updateData(self)
    #self.setContentFlag(True)
    #self.__dict__['_value']['contentFlag'].unSet()

  def importFromCif(self,jobId=None):
    import os
    import CCP4Utils
    errorReport = CErrorReport()
    #print 'CMiniMtzDataFile.importFromCif',str(self)
    # Set name for new split file and if it already exists remove previous reference from db
    if jobId is not None:
      jobDirectory = CCP4Modules.PROJECTSMANAGER().jobDirectory(jobId = jobId)
    else:
      jobDirectory = CCP4Utils.makeTmpFile(dir=True)
    # Run cif2mtz
    logFile = os.path.normpath(os.path.join(jobDirectory,self.objectName()+'_cif2mtz.log'))
    #print 'CMiniMtzDataFile.importFromCif logFile',logFile
    bin = os.path.normpath(os.path.join( CCP4Utils.getCCP4Dir(), 'bin', 'cif2mtz' ))
    hklout = os.path.normpath(os.path.join(jobDirectory,self.stripedName()+'.mtz'))
    arglist = [ 'hklin', self.__str__(),'hklout',hklout ]
    inputText = '''END\n'''
    pid = CCP4Modules.PROCESSMANAGER().startProcess(bin,arglist,logFile=logFile,inputText=inputText)
    status = CCP4Modules.PROCESSMANAGER().getJobData(pid)
    exitCode = CCP4Modules.PROCESSMANAGER().getJobData(pid,'exitCode')
    #print 'CMtzDataFile.importFromCif',status,exitCode
    if status != 0:
      errorReport.append(self.__class__,225,'Exit status:'+str(status),name=self.objectPath())
      return errorReport

    if os.path.exists(hklout):
      self.setFullPath(hklout)
    else:
      errorReport.append(self.__class__,226,name=self.objectPath())
    return errorReport
    
  def saveAs(self,format='SHELX',fileName=None,mergeWith=[]):
    # Save as non-MTZ format probably by running CMtzDataFile.runMtz2various
    # mergeWith - list of other CMiniMtzDataFiles to be merged into the output file
    import CCP4Utils

    #Keep a list of the file labels (assumes we want mtz2various to ouput all)
    labin = self.columnNames()

    # Merge in any other required data objects using cmtzjoin
    if len(mergeWith)>0:
      tmpFile = CCP4Utils.makeTmpFile(name=self.baseName.__str__(),extension='mtz')
      infiles = [ [ self.__str__(), self.columnNames(ifString=True) ] ]
      for mtzObj in mergeWith:
        labin.extend(mtzObj.columnNames())
        infiles.append ( [ mtzObj.__str__(), mtzObj.columnNames(ifString=True)] )
      hklin,err = self.runMtzjoin(tmpFile, infiles)
      if hklin is None or err.maxSeverity()>SEVERITY_WARNING: return None,err                     
    else:
      hklin = self.__str__()

    # Run mtz2various and return hklout,errorReport  (hklout is None if job failed)
    if format in ['SHELX']:     
      return self.runMtz2various(hklin=hklin,labin=labin,output=format,hklout=fileName)

    # Simple test from pyi2..
    #import CCP4XtalData
    #o = CCP4XtalData.CObsDataFile('/Users/lizp/Desktop/rnase_obs_sf.mtz')
    #f = CCP4XtalData.CFreeRDataFile('/Users/lizp/Desktop/rnase_freer.mtz')
    #out,err = o.saveAs(fileName='/Users/lizp/Desktop/shelx.hkl',mergeWith=[f])

  def datasetName(self):
    if not self.isSet(): return ''
    for d in self.fileContent.datasets:
      if d != 'HKL_base':
        return str(d)
    return ''

class CMiniMtzDataFileList(CCP4Data.CList):
  SUBITEM={ 'class' : CMiniMtzDataFile }

class CObsDataFile(CMiniMtzDataFile):
  SUBTYPE_OBSERVED = 1
  SUBTYPE_DERIVED = 2
  SUBTYPE_REFERENCE = 3
  CONTENT_FLAG_IPAIR = 1
  CONTENT_FLAG_FPAIR = 2
  CONTENT_FLAG_IMEAN = 3
  CONTENT_FLAG_FMEAN = 4
  CONTENT_ANNOTATION = [ 'Anomalous Is', 'Anomalous SFs', 'Mean Is' ,'Mean SFs']
  # 4=not possible, 0 = same, 1=using mtzjoin, 2=using ctruncate, 3=ctruncate & mtzsplit
  #                               TO
  CONTENT_CONVERSION =  [ [  0, 2, 2,  2 ] ,
                          [  4, 0, 4,  2 ] ,
                          [  4, 4, 0,  2 ] ,
                          [  4, 4, 4,  0 ] ]
  CONTENT_SIGNATURE_LIST = [ ['Iplus','SIGIplus','Iminus','SIGIminus'], ['Fplus','SIGFplus','Fminus','SIGFminus'], ['I','SIGI'], ['F','SIGF'] ]
  CONTENTS = {}
  CONTENTS.update(CMiniMtzDataFile.CONTENTS)
  CONTENTS['subType'] = { 'class' : CCP4Data.CInt,
             'qualifiers' : { 'default' :SUBTYPE_OBSERVED, 'enumerators' : [SUBTYPE_OBSERVED,SUBTYPE_DERIVED,SUBTYPE_REFERENCE], 'onlyEnumerators':True, 'menuText' : ['observed data','derived data','reference data'] }  }
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-mtz-observed',
                 'mimeTypeDescription' : 'MTZ observed',
                 'fileExtensions' : ['mtz','cif','ent'],
                 'fileContentClassName' : 'CMtzData',
                 'fileLabel' : 'observed_data',
                 'guiLabel' : 'Reflections',
                 'toolTip' : "Observed structure factors or intensities",
                 'correctColumns' : ['KMKM','GLGL','JQ','FQ'],
                 'columnGroupClassList' : [CIPairColumnGroup,CFPairColumnGroup,CISigIColumnGroup,CFSigFColumnGroup],
                 'downloadModes' : [ 'ebiSFs' ],
                 'helpFile' : 'data_files#Obs'
                 }
  ERROR_CODES = { 301 : { 'description' : 'Running ctruncate failed' },
                  302 : { 'description' : 'Running cmtzsplit to convert observed data type failed'  },
                  303 : { 'description' : 'Running sftools failed'  }
                  }

  def conversion(self,targetContent):
    if not self.contentFlag.isSet(): return 'ok',targetContent
    if isinstance(targetContent,list):
      import copy
      targetContentList = copy.deepcopy(targetContent)
    else:
      targetContentList = [targetContent]
    for targetContent in targetContentList:
      c = ['ok','mtzjoin','convert','','no'][self.CONTENT_CONVERSION[self.contentFlag-1][targetContent-1]]
      if c != 'no': return c,targetContent
    #print 'CObsDataFile.conversion',self.contentFlag,targetContent,c
    return c,targetContentList[0]


  def requiredContent(self):
    #print 'CObsDataFile.requiredContent',self.qualifiers('requiredContentFlag')
    # Return the allowed contentFlag values for CDataFileView.getJobsWithOutputFiles to call to db getJobsWithOutputFiles
    contentList = self.qualifiers('requiredContentFlag')
    if contentList is None or contentList is NotImplemented or contentList==[self.CONTENT_FLAG_FMEAN]: return None
    retList = []
    for content in contentList:
      for n in range(1,5):
        if self.CONTENT_CONVERSION[n-1][content-1] != 4:
          if retList.count(n)==0: retList.append(n)
    #print 'CObsDataFile.requiredContent',contentList,retList
    if len(retList)==1:
      return retList[0]
    else:
      return retList
  
  def contentSignature(self):
    return CObsDataFile.CONTENT_SIGNATURE_LIST

    

  def columnNames(self,ifString=False,content=None):
    #print 'CObsDataFile.columnNames',self.__dict__['_value']['contentFlag'],type(self.__dict__['_value']['contentFlag']),self.CONTENT_FLAG_IPAIR
    if content is None:
      if not self.__dict__['_value']['contentFlag'].isSet():
        #print 'CObsDataFile contentFlag not set:',self.objectName()
        self.setContentFlag()
      if not self.__dict__['_value']['contentFlag'].isSet():
        return []
      else:
        content = int(self.contentFlag)
      
    if content == CObsDataFile.CONTENT_FLAG_IPAIR:
      if ifString:
        return 'Iplus,SIGIplus,Iminus,SIGIminus'
      else:
        return ['Iplus','SIGIplus','Iminus','SIGIminus']
    elif content == CObsDataFile.CONTENT_FLAG_IMEAN:
      if ifString:
        return 'I,SIGI'
      else:
        return ['I','SIGI']
    elif content == CObsDataFile.CONTENT_FLAG_FPAIR:
      if ifString:
        return 'Fplus,SIGFplus,Fminus,SIGFminus'
      else:
        return ['Fplus','SIGFplus','Fminus','SIGFminus']
    elif content == CObsDataFile.CONTENT_FLAG_FMEAN:
      if ifString:
        return 'F,SIGF'
      else:
        return ['F','SIGF']
    else:
      if ifString:
        return ''
      else:
        return []

  def convert(self,targetContent=None,targetFile=None,parentPlugin=None):
    #print 'CObsDataFile.convert',targetContent,targetFile,parentPlugin,self.__dict__['_value']['contentFlag']
    error = CErrorReport()
    if not self.isSet() or not self.exists():
      error.append(self.__class__,220,name=self.objectPath())
      return None,error
    if targetContent is None or not isinstance(targetContent,int) or targetContent<1 or targetContent>4:
      error.append(self.__class__,222,name=self.objectPath())
      return None,error
    if not self.__dict__['_value']['contentFlag'].isSet():
      flag = self.setContentFlag()
      if flag is None:
        error.append(self.__class__,223,name=self.objectPath())
        return None,error
    if self.__dict__['_value']['contentFlag'] == targetContent:
      return self.fullPath.__str__(),error
    elif self.__dict__['_value']['contentFlag'] > targetContent:
      error.append(self.__class__,221,name=self.objectPath())
      return None,error
    #print 'CObsDataFile OK',self.__dict__['_value'],type(self.__dict__['_value'])
    
    if targetFile is None:
      import os
      fsplit = os.path.splitext(self.fullPath.__str__())
      mode = ('asIPAIR','asFPAIR','asIMEAN','asFMEAN')[targetContent-1]
      targetFile = fsplit[0]+'_'+mode+ fsplit[1]
      #print 'CObsDataFile.convert targetFile',targetFile
      if os.path.exists(targetFile): return targetFile,error

    #print 'CObsDataFile.convert',self.objectName(),self.__dict__['_value']['contentFlag'],'target',targetContent

    if self.__dict__['_value']['contentFlag'] == CObsDataFile.CONTENT_FLAG_IPAIR:
      if targetContent in (CObsDataFile.CONTENT_FLAG_FPAIR,CObsDataFile.CONTENT_FLAG_FMEAN,CObsDataFile.CONTENT_FLAG_IMEAN):
        return self.runTruncate(targetContent=targetContent,targetFile=targetFile,parentPlugin=parentPlugin)
    if self.__dict__['_value']['contentFlag'] == CObsDataFile.CONTENT_FLAG_IMEAN:
      if targetContent == CObsDataFile.CONTENT_FLAG_FMEAN:
        return self.runTruncate(targetContent=targetContent,targetFile=targetFile,parentPlugin=parentPlugin)
    if self.__dict__['_value']['contentFlag'] == CObsDataFile.CONTENT_FLAG_FPAIR:
      if targetContent == CObsDataFile.CONTENT_FLAG_FMEAN:
        return self.runSftools(targetContent=targetContent,targetFile=targetFile,parentPlugin=parentPlugin,mode='Amplitudes')
    #if self.__dict__['_value']['contentFlag'] == CObsDataFile.CONTENT_FLAG_IPAIR:
    #  if targetContent == CObsDataFile.CONTENT_FLAG_IMEAN:
    #    return self.runSftools(targetContent=targetContent,targetFile=targetFile,parentPlugin=parentPlugin,mode='Intensities')
    
    # Its broke - no conversion possible
    error.append(self.__class__,224,name=self.objectPath())
    return None,error

  def runSftools(self,targetContent=None,targetFile=None,parentPlugin=None,mode='Amplitudes'):
        error = CErrorReport()
        #Use SFTOOLS to calculate Fmean, SIGFMean
        import os
        import CCP4Utils
        bin =  os.path.normpath(os.path.join( CCP4Utils.getCCP4Dir(), 'bin', 'sftools' ))
        if parentPlugin is not None:
          myDir =  os.path.normpath(os.path.join(parentPlugin.workDirectory,'sftools'))
          os.mkdir(myDir)
        logFile =  os.path.normpath(os.path.join(myDir, 'sftools.log'))
        argList = []
        
        if mode == 'Amplitudes':
            colPlus,colSigPlus,colMinus,colSigMinus,colOut,colTypeOut,colSigOut,colTypeSigOut = ('Fplus','SIGFplus','Fminus','SIGFminus','F','F','SIGF','Q')
        elif mode == 'Intensities':
            colPlus,colSigPlus,colMinus,colSigMinus,colOut,colTypeOut,colSigOut,colTypeSigOut = ('Iplus','SIGIplus','Iminus','SIGIminus','I','J','SIGI','Q')        
        
        inputText = "READ "+self.fullPath.__str__()+"\n"
        #Evaluate weighted mean only if Fplus and Fminus both present
        inputText += "SELECT ONLY COL "+colPlus+" PRESENT\n"
        inputText += "SELECT MINUS COL "+colMinus+" ABSENT\n"
        inputText += "CALC COL SIGSQm = COL "+colSigMinus+" COL "+colSigMinus+" *\n"#Col 5
        inputText += "CALC COL FpXSIGSQm = COL "+colPlus+" COL SIGSQm *\n"#Col 6
        inputText += "CALC COL SIGSQp = COL "+colSigPlus+" COL "+colSigPlus+" *\n" #Col 7
        inputText += "CALC COL FmXSIGSQp = COL "+colMinus+" COL SIGSQp *\n"#Col 8
        inputText += "CALC COL SIGSQpPSIGSQm = COL SIGSQp COL SIGSQm +\n"#Col 9
        inputText += "CALC COL SIGSQpXSIGSQm = COL SIGSQp COL SIGSQm *\n"#Col 10
        inputText += "CALC COL FpXSIGSQmPFmXSIGSQp = COL FpXSIGSQm COL FmXSIGSQp +\n"#Col 11
        inputText += "CALC COL "+colOut+" = COL FpXSIGSQmPFmXSIGSQp COL SIGSQpPSIGSQm /\n"#Col 12
        inputText += "CALC COL SIGSQF = COL SIGSQpXSIGSQm COL SIGSQpPSIGSQm /\n"#Col 13
        inputText += "CALC COL "+colSigOut+" = COL SIGSQF LN 2 / EXP\n"#Col 14
        #Now deal with missing Fplus or Fminus
        inputText += "SELECT ONLY COL "+colMinus+" ABSENT\n"
        inputText += "CALC COL "+colOut+" = COL "+colPlus+" 0 +\n"
        inputText += "CALC COL "+colSigOut+" = COL "+colSigPlus+" 0 +\n"
        inputText += "SELECT ONLY COL "+colPlus+" ABSENT\n"
        inputText += "CALC COL "+colOut+" = COL "+colMinus+" 0 +\n"
        inputText += "CALC COL "+colSigOut+" = COL "+colSigMinus+" 0 +\n"
        inputText += "SELECT ALL\n"
        inputText += "SET TYPE COL "+colOut+"\n"
        inputText += colTypeOut+"\n"
        inputText += "SET TYPE COL "+colSigOut+"\n"
        inputText += colTypeSigOut+"\n"
        inputText += "WRITE "+targetFile+" COL "+colOut+" "+colSigOut+"\n"
        inputText += "STOP\n"

        pid = CCP4Modules.PROCESSMANAGER().startProcess(bin, argList, inputText=inputText, logFile=logFile, cwd=myDir)
        status = CCP4Modules.PROCESSMANAGER().getJobData(pid)
        exitCode = CCP4Modules.PROCESSMANAGER().getJobData(pid,'exitCode')

        if status != 0 or not os.path.exists(targetFile):
            error.append(self.__class__,303,str(targetFile),name=self.objectName())
            return None,error
        else:
            print "CObsDataFile.runSftools targetFile error",targetFile, error
            return targetFile,error


  def runTruncate(self,targetContent=None,targetFile=None,parentPlugin=None):
    #print 'CObsDataFile.runTruncate',self.contentFlag,targetContent,targetFile,parentPlugin
    error = CErrorReport()
    import os, ctruncate, CCP4PluginScript
    wrapper = ctruncate.ctruncate(self)
    if parentPlugin is not None:
      myDir =  os.path.normpath(os.path.join(parentPlugin.workDirectory,'ctruncate'))
      os.mkdir(myDir)
      wrapper.workDirectory = myDir
   
    inp = wrapper.container.inputData
    inp.HKLIN.setFullPath(self.fullPath.__str__())
    wrapper.container.controlParameters.OUTPUTMINIMTZ.set(True)
    wrapper.container.controlParameters.OUTPUTMINIMTZCONTENTFLAG.set(targetContent)
    
    if self.contentFlag == CObsDataFile.CONTENT_FLAG_IPAIR:
      inp.ISIGIanom.Ip='Iplus'
      inp.ISIGIanom.SIGIp='SIGIplus'
      inp.ISIGIanom.Im='Iminus'
      inp.ISIGIanom.SIGIm='SIGIminus'
    elif self.contentFlag == CObsDataFile.CONTENT_FLAG_IMEAN:
      inp.ISIGI.I='I'
      inp.ISIGI.SIGI='SIGI'
    elif self.contentFlag == CObsDataFile.CONTENT_FLAG_FPAIR:
      wrapper.container.controlParameters.AMPLITUDES.set(True)
      inp.FSIGFanom.Fp = 'Fplus'
      inp.FSIGFanom.SIGFp='SIGFplus'
      inp.FSIGFanom.Fm='Fminus'
      inp.FSIGFanom.SIGFm='SIGFminus'
    wrapper.container.outputData.OBSOUT.setFullPath(targetFile)

    #MN Trying to get Ipair->Imean to work: have to tell ctruncate to output intensities
    if targetContent in [1,3]:
        wrapper.container.controlParameters.OUTPUT_INTENSITIES.set(True)
    
    status = wrapper.process()
    #print 'CObsDataFile.runTruncate',status,wrapper.errorReport.report()
    if status != CCP4PluginScript.CPluginScript.SUCCEEDED:
      error.append(self.__class__,301,name=self.objectName())
      return None,error
    else:
      return targetFile,error


  def convertObsMtz( self, infile=None, outfile=[], parentPlugin=None ):
      #print 'CObsDataFile.convertObsMtz',infile, outfile
      # Expect to use this to convert Fpairs to Fmean 
      import os, sys
      import CCP4Modules,CCP4Utils
      error = CErrorReport()
      bin = os.path.normpath(os.path.join( CCP4Utils.getOSDir(), 'bin', 'cmtzsplit' ))
      if not os.path.exists(bin):
         bin =  os.path.normpath(os.path.join( CCP4Utils.getCCP4Dir(), 'bin', 'cmtzsplit' ))
      arglist = [ '-mtzin', infile ]
      if len(outfile)==2:
        name,colin = outfile
        colout = ''
      else:
        name,colin,colout = outfile
      if parentPlugin is not None:
        logFile =  os.path.normpath(os.path.join(self.parentPlugin.workDirectory,self.objectName()+'_splitMtz.log'))
      else:
        logFile =  os.path.normpath(os.path.join(os.path.split(name)[0],self.objectName()+'_splitMtz.log'))
      arglist.append( '-mtzout' )
      arglist.append( name )
      arglist.append( '-colin' )
      arglist.append( colin )
      arglist.append( '-colout' )
      if len (colout)>0:
        arglist.append( colout )
      else:
        arglist.append( colin )
      pid = CCP4Modules.PROCESSMANAGER().startProcess(bin,arglist,logFile=logFile)
      status = CCP4Modules.PROCESSMANAGER().getJobData(pid)
      exitCode = CCP4Modules.PROCESSMANAGER().getJobData(pid,'exitCode')      
      if status == 0 and os.path.exists(outfile[0]):
        return outfile[0],error
      else:
        error.append(self,302,self.__str__())
        return None,error

    
      
class CPhsDataFile(CMiniMtzDataFile):
  SUBTYPE_UNBIASED = 1
  SUBTYPE_BIASED = 2
  CONTENT_FLAG_HL = 1
  CONTENT_FLAG_PHIFOM = 2
  CONTENT_SIGNATURE_LIST = [ ['HLA','HLB','HLC','HLD'],  ['PHI','FOM']]
  CONTENT_ANNOTATION = [ 'Hendrickson-Lattmann coeffs','Phi,FOM' ]
  CONTENTS = {}
  CONTENTS.update(CMiniMtzDataFile.CONTENTS)
  CONTENTS['subType'] =  { 'class' : CCP4Data.CInt,
             'qualifiers' : { 'default' : SUBTYPE_UNBIASED, 'enumerators' : [SUBTYPE_UNBIASED,SUBTYPE_BIASED], 'onlyEnumerators':True, 'menuText' :  ['unbiased data','biased data']  }  }
  
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-mtz-phases',
                 'mimeTypeDescription' : 'MTZ phases',
                 'fileExtensions' : ['mtz','cif','ent'],
                 'fileContentClassName' : 'CMtzData',
                 'guiLabel' : 'Phases',
                 'fileLabel' : 'phases',
                 'toolTip' : "Phases in Hendrickson-Lattmann or Phi/FOM form",
                 'correctColumns' : ['AAAA','PW'],
                 'columnGroupClassList' : [CHLColumnGroup,CPhiFomColumnGroup],
                 'helpFile' : 'data_files#Phs' }
  
  def contentSignature(self):
    return CPhsDataFile.CONTENT_SIGNATURE_LIST

  def columnNames(self,ifString=False,content=None):
    #print 'CPhsDataFile.columnNames',self.objectPath(),self.__dict__['_value']['contentFlag']
    #if not self.__dict__['_value']['contentFlag'].isSet():
      #print 'CPhsDataFile contentFlag not set:',self.objectName()
    self.setContentFlag()
    #print 'CPhsDataFile.columnNames updated contentFlag',self.__dict__['_value']['contentFlag']

    if content is None: content = self.__dict__['_value']['contentFlag']
      
    if content == CPhsDataFile.CONTENT_FLAG_HL:
      if ifString:
        return 'HLA,HLB,HLC,HLD'
      else:
        return ['HLA','HLB','HLC','HLD']
    elif content == CPhsDataFile.CONTENT_FLAG_PHIFOM:
      if ifString:
        return 'PHI,FOM'
      else:
        return ['PHI','FOM']

  def conversion(self,targetContent):
    if isinstance(targetContent,list):
      import copy
      targetContentList = copy.deepcopy(targetContent)
    else:
      targetContentList = [targetContent]
    for targetContent in targetContentList:
      if targetContent == self.contentFlag:
        return 'ok',targetContent
      else:
        return 'convert',targetContent

  def convert(self,targetContent=None,targetFile=None,**kw):
    #print 'CPhsDataFile.convert',targetContent,targetFile
    error = CErrorReport()
    if not self.isSet() or not self.exists():
      error.append(self.__class__,220,name=self.objectPath())
      return None,error
    if targetContent is None or not isinstance(targetContent,int) or targetContent<1 or targetContent>4:
      error.append(self.__class__,222,name=self.objectPath())
      return None,error
    if self.__dict__['_value']['contentFlag'] == targetContent:
      return self.fullPath.__str__(),error
    
    #MN CHECKME CHECK ME
    #I am going to disable this test...in fact Phi FOM can be "up-converted" to H L Coeffs
    #elif self.__dict__['_value']['contentFlag'] > targetContent:
    #  error.append(self.__class__,221,name=self.objectPath())
    #  return None,error
    #print 'CObsDataFile OK',self.__dict__['_value'],type(self.__dict__['_value'])
    
    if targetFile is None:
      import os
      fsplit = os.path.splitext(self.fullPath.__str__())
      mode = 'PHIFOM'
      targetFile = fsplit[0]+'_'+mode+ fsplit[1]
    return self.runChltofom(targetContent=targetContent,targetFile=targetFile)

  def runChltofom(self,targetContent=None,targetFile=None):
    #print 'CPhsDataFile.runChltofom',self.contentFlag,targetContent,targetFile
    error = CErrorReport()
    import chltofom, CCP4PluginScript
    wrapper = chltofom.chltofom(self)
    wrapper.container.inputData.HKLIN.setFullPath(self.fullPath.__str__())
    wrapper.container.outputData.HKLOUT.setFullPath(targetFile)
    wrapper.container.controlParameters.OUTPUTMINIMTZ.set(True)
    if self.contentFlag == 2 and targetContent == 1:
        wrapper.container.controlParameters.DIRECTION = 'FOMTOHL'
    status = wrapper.process()
    #print 'CPhsDataFile.runChltofom',status
    if status != CCP4PluginScript.CPluginScript.SUCCEEDED:
      error.append(self.__class__,301,name=self.objectName())
      return None,error
    else:
      return targetFile,error

class CMapCoeffsDataFile(CMiniMtzDataFile):
  SUBTYPE_NORMAL = 1
  SUBTYPE_DIFFERENCE = 2
  CONTENTS = {}
  CONTENTS.update(CMiniMtzDataFile.CONTENTS)
  CONTENT_FLAG_FPHI = 1
  CONTENT_SIGNATURE_LIST = [ ['F','PHI']]
  CONTENT_ANNOTATION = [ 'FPhi' ]
  CONTENTS['subType'] = { 'class' : CCP4Data.CInt,
             'qualifiers' : { 'default': SUBTYPE_NORMAL, 'enumerators' : [SUBTYPE_NORMAL,SUBTYPE_DIFFERENCE], 'onlyEnumerators':True, 'menuText' : ['normal map','difference map'] }  } 
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-mtz-map',
                 'mimeTypeDescription' : 'MTZ F-phi',
                 'fileExtensions' : ['mtz','cif','ent'],
                 'fileContentClassName' : 'CMtzData',
                 'fileLabel' : 'map_coefficients',
                 'guiLabel' : 'Map coefficients',
                 'toolTip' : "Electron density map coefficients: F,Phi",
                 'correctColumns' : ['FP','FQP'],
                 'columnGroupClassList' : [CMapColumnGroup],
                 'downloadModes' : ['Uppsala-EDS'],
                 'helpFile' : 'data_files#MapCoeffs' }
  
  def contentSignature(self):
    return CMapCoeffsDataFile.CONTENT_SIGNATURE_LIST
  
  def columnNames(self,ifString=False,content=None):
    if ifString:
      return 'F,PHI'
    else:
      return ['F','PHI']


class CFreeRDataFile(CMiniMtzDataFile):
  CONTENTS = {}
  CONTENTS.update(CMiniMtzDataFile.CONTENTS)
  CONTENT_SIGNATURE_LIST = [ [ 'FREER' ] ]
  CONTENT_ANNOTATION = [ 'FreeR' ]
  CONTENTS['subType'] = { 'class' : CCP4Data.CInt,
             'qualifiers' : { 'enumerators' : [], 'onlyEnumerators':True }  } 
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-mtz-freerflag',
                 'mimeTypeDescription' : 'FreeR flag',
                 'fileExtensions' : ['mtz','cif','ent'],
                 'fileContentClassName' : 'CMtzData',
                 'fileLabel' : 'freeRflag',
                 'guiLabel' : 'Free R set',
                 'toolTip' : "Set of reflections used for FreeR calculation",
                 'correctColumns' : ['I'],
                 'columnGroupClassList' : [CFreeRColumnGroup],
                  'helpFile' : 'data_files#FreeR' }
  
  def contentSignature(self):
    return CFreeRDataFile.CONTENT_SIGNATURE_LIST

  def columnNames(self,ifString=False,content=None):
    if ifString:
      return 'FREER'
    else:
      return ['FREER']

  def sameCrystal(self,other=None,testLevel=None):
    if self.fileContent is None: self.loadFile()
    if testLevel is None: testLevel = 1
    return self.getFileContent().sameCrystal(other.getFileContent(),testLevel)

class CShelxFADataFile(CCP4File.CDataFile):
  
  QUALIFIERS = { 'mimeTypeName' : 'application/CCP4-shelx-FA',
                 'mimeTypeDescription' : 'Shelx FA',
                 'fileExtensions' : [ 'hkl' ],
                 'fileContentClassName' : None,
                 'fileLabel' : 'shelx_FA',
                 'guiLabel' : 'Shelx FA',
                 'toolTip' : "Data used by Shelx programs",
                 'helpFile' : 'data_files#shelxfa'
                 }
  
class CPhaserSolDataFile(CCP4File.CDataFile):
  
  QUALIFIERS = { 'mimeTypeName' : 'application/phaser-sol',
                 'mimeTypeDescription' : 'Phaser solution file',
                 'fileExtensions' : [ 'phaser_sol.pkl' ],
                 'fileContentClassName' : None,
                 'fileLabel' : 'phaser_sol',
                 'guiLabel' : 'Phaser solutions',
                 'toolTip' : "Possible solutions passed between runs of the Phaser program",
                 'helpFile' : 'data_files#phasersol'
                 }
      
  def assertSame(self,other,diagnostic=False,**kw):
    try:
      import phaser
      import pickle
      selfStr = pickle.load(open(self.__str__())).unparse()
      print 'CPhaserSolDataFile.assertSame selfStr',selfStr
      otherStr = pickle.load(open(other.__str__())).unparse()
      print 'CPhaserSolDataFile.assertSame otherStr',otherStr
    except:      
      return CErrorReport(self.__class__,301,name=self.objectPath(),details=str(self)+' : '+str(other))

    # Unsophisicated diff
    import diff_match_patch
    dmp =  diff_match_patch.diff_match_patch()
    diffs = dmp.diff_main(selfStr,otherStr)
    print 'CPhaserSolDataFile.assertSame diffs',diffs

    if len(diffs)>1:
      return CErrorReport(self.__class__,313,name=self.objectPath(),details=str(self)+' : '+str(other))
    else:
      return CErrorReport(self.__class__,300,name=self.objectPath())
    

class CImosflmXmlDataFile(CCP4File.CDataFile):

  '''An iMosflm data file'''

  QUALIFIERS = { 'fileLabel' : 'imosflm',
                 'mimeTypeName' : 'application/iMosflm-xml',
                 'mimeTypeDescription' : 'iMosflm data',
                 'guiLabel' : 'iMosflm data',
                 'fileExtensions' : ['imosflm.xml'],
                 'fileContentClassName' : None }

class CMergeMiniMtz(CCP4Data.CData):
  CONTENTS = { 'fileName' : { 'class' : CMiniMtzDataFile, 'qualifiers' : { 'fromPreviousJob' : False } },
               'columnTag' :  { 'class' : CCP4Data.CString },
               'columnNames' :  { 'class' : CCP4Data.CString } }
  CONTENTS_ORDER = [ 'fileName', 'columnTag', 'columnNames' ]
  ERROR_CODES = { 201 : { 'description' : "Selected file is not a suitable 'mini' MTZ containing experimental data object" },
                  202 :  { 'description' : 'Output column name list does not have correct number of names' }
                  }

  def qualifiers(self,name=None,default=True,custom=True,contentQualifiers=True):
    if name == 'mimeTypeName':
      return "application/CCP4-mtz-mini"
    else:
      return  CCP4Data.CData.qualifiers(self,name=name,default=default,custom=custom,contentQualifiers=contentQualifiers)

  
  def validity(self,arg):
    v = CErrorReport()
    if self.__dict__['_value']['fileName'].isSet() and self.__dict__['_value']['fileName'].exists():
      cls,contentFlag = self.__dict__['_value']['fileName'].miniMtzType()
      if cls is None:
        v.append(self.__class__,201,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
      elif self.__dict__['_value']['columnNames'].isSet() :
        stdColumnNames = cls().columnNames(True,contentFlag)
        if  self.__dict__['_value']['columnNames'].__str__().count(',') != stdColumnNames.count(','):
           v.append(self.__class__,202,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    #print 'CMergeMiniMtz.validity',v
    return v


  def getTableTextItems(self):
    return [self.__dict__['_value']['fileName'].guiLabel(useAnnotation=False,useObjectName=False),self.__dict__['_value']['columnNames'].__str__() ]

  def setColumnTag(self,overwrite=False):
    #print 'CMergeMiniMtz.setColumnTag',overwrite,self.__dict__['_value']['fileName'].get()
    if not self.__dict__['_value']['fileName'].isSet():
      self.__dict__['_value']['columnTag'].unSet()
    if not overwrite and self.__dict__['_value']['columnTag'].isSet(): return

    if not self.__dict__['_value']['fileName'].dbFileId.isSet():
      self.__dict__['_value']['columnTag'].unSet()
      return
    else:
      import CCP4Modules
      fileInfo = CCP4Modules.PROJECTSMANAGER().db().getFileInfo(fileId =  self.__dict__['_value']['fileName'].dbFileId.__str__(),
                                                                mode=['jobnumber','jobparamname','taskname'])
      #print 'CMergeMiniMtz.setColumnTag fileInfo',fileInfo
      if fileInfo['taskname'] is not None:
        self.__dict__['_value']['columnTag'].set(fileInfo['jobnumber']+'_'+fileInfo['taskname'][0:20])
      else:
        self.__dict__['_value']['columnTag'].set(fileInfo['jobnumber'][0:20])

  def setColumnNames(self,mode='fromFile',overwrite=False):
    if not self.__dict__['_value']['fileName'].isSet():
      self.__dict__['_value']['columnNames'].unSet()
      return
    if not overwrite and self.__dict__['_value']['columnNames'].isSet(): return
    
    fileColumnNames= self.__dict__['_value']['fileName'].fileContent.columnNames()
      
    if mode == 'applyTag' and self.__dict__['_value']['columnTag'].isSet():
      cls,contents = self.__dict__['_value']['fileName'].miniMtzType()
      if cls is not None:
        columnNames = cls.CONTENT_SIGNATURE_LIST[contents-1]
      else:
        columnNames = fileColumnNames
      #print 'CMergeMiniMtz.setColumnNames',cls,contents,columnNames,self.__dict__['_value']['fileName'].subType
      if cls == CMapCoeffsDataFile and self.__dict__['_value']['fileName'].subType.isSet() and self.__dict__['_value']['fileName'].subType==2:
        columnNames = [ 'DIFF_'+columnNames[0],'DIFF_'+columnNames[1]]
      text = ''
      for name in columnNames: text = text + name+'_'+self.__dict__['_value']['columnTag'].__str__()+','
      self.__dict__['_value']['columnNames'].set(text[0:-1])
    else:
      text = str(fileColumnNames[0])
      for name in fileColumnNames[1:]: text = text + ',' + str(name)
      self.__dict__['_value']['columnNames'].set(text)
      
        

class CMergeMiniMtzList(CCP4Data.CList):
  SUBITEM = { 'class' : CMergeMiniMtz }
  QUALIFIERS = { 'listMinLength' : 2,
                 'saveToDb' : True }

  def saveToDb(self):
    fileObjList = []
    for item in self.__dict__['_value']: fileObjList.append(item.fileName)
    #print 'CMergeMiniMtzList.saveToDb',fileObjList
    return fileObjList,None,{}

class CRunBatchRange(CCP4Data.CData):
  CONTENTS = { 'runNumber' : { 'class' : CCP4Data.CInt, 'qualifiers' : { 'allowUndefined' : True, 'min' : 1 } },
               'batchRange0' : { 'class' : CCP4Data.CInt, 'qualifiers' : { 'allowUndefined' : True, 'min' : 1 } },
               'batchRange1' : { 'class' : CCP4Data.CInt, 'qualifiers' : { 'allowUndefined' : True, 'min' : 1 } },
               'fileNumber' : { 'class' : CCP4Data.CInt, 'qualifiers' : { 'allowUndefined' : True, 'min' : 1 } } 
             }
  QUALIFIERS = { 'toolTip' : 'Specify range of reflections to treat as one run' }
  ERROR_CODES = { 101 : { 'description' : 'End of batch range less than start' },
                  102 : { 'description' : 'All items must be set' } }


  def validity(self,arg):
    # Check validity of the individual items
    v = self.itemValidity(arg)
    if v.maxSeverity()>SEVERITY_WARNING: return v

    if not isinstance(arg,dict):
      arg = arg.get()

    #All or none must be set
    nSet = 0
    for item in ['runNumber','batchRange0','batchRange1']:
      if arg[item] is not None: nSet+=1
    #print 'CRunBatchRange.validity',arg,nSet
    if nSet not in [0,3]:  v.append(self.__class__,102,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    if v.maxSeverity()>0: return v

    # Batch range 
    if arg.get('batchRange1').__cmp__(arg.get('batchRange0'))<0:
        v.append(self.__class__,101,name=self.objectPath(),label=self.qualifiers('guiLabel'),stack=False)
    #print 'CRunBatchRange.validity',v.maxSeverity()
    return v

  def getTableTextItems(self):
    ret = []
    for key in ['runNumber','batchRange0','batchRange1']:
      if not self.__dict__['_value'][key].isSet():
        ret.append('')
      else:
        ret.append( str(self.__dict__['_value'][key]))
    return ret

class CRunBatchRangeList(CCP4Data.CList):
  SUBITEM = { 'class' : CRunBatchRange }
  QUALIFIERS = { 'listMinLength' : 1 }

  '''
  # Set the default run number - problems
  def addItem(self,value= NotImplemented , index = -1):
    obj = CCP4Data.CList.addItem(self,value=value,index=index)
    # If its an append we can guess a run number
    print 'CRunBatchRangeList.addItem',index, len(self._value)
    if index<0:
      if len(self._value) == 1:
        self._value[0].runNumber.set(0)
      else:
        self._value[-1].runNumber.set( int(self._value[-2].runNumber)+1)
   '''

class CDataset(CCP4Data.CData):
  '''
  The experimental data model for ab initio phasing
  '''
  CONTENTS = { 'selected' : { 'class' : CCP4Data.CBoolean },
               'obsDataFile' : { 'class' : CObsDataFile },
               'crystalName' :  { 'class' : CCrystalName },
               'datasetName' :  { 'class' : CDatasetName },
               'formFactors' : { 'class' : CFormFactor },
               'formFactorSource' : { 'class' : CCP4Data.CString, 'qualifiers' : { 'onlyEnumerators' : True,
                                                                          'enumerators' : [ 'no' , 'composition', 'xia2'],
                                                                          'menuText' : [ 'user input' , 'atomic composition', 'from XIA2' ],
                                                                          'default' : 'no' } }
               }
  def getTextItem(self):
    return self.obsDataFile.baseName.__str__()

  def getTableTextItems(self):
    formText = ''
    if self.formFactors.Fp.isSet: formText+=str(self.formFactors.Fp)
    formText+=','
    if self.formFactors.Fpp.isSet: formText+=str(self.formFactors.Fpp)
      
    return [self.obsDataFile.baseName.__str__(),self.crystalName.__str__(),self.datasetName.__str__(),formText]


class CDatasetList(CCP4Data.CList):
  SUBITEM = { 'class' : CDataset }


#===========================================================================================================
import unittest
def TESTSUITE():
  '''
  suite = unittest.TestLoader().loadTestsFromTestCase(testAssorted)
  suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(testCell))
  suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(testMtz))
  suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(testSpaceGroup))
  suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(testComponents))
  suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(testComposition))
  suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(testCObsDataFile))
  suite.addTests(unittest.defaultTestLoader.loadTestsFromTestCase(testCPhsDataFile))
  '''
  suite= unittest.TestLoader().loadTestsFromTestCase(testCPhsDataFile)
  return suite

def testModule():
  suite = TESTSUITE()
  unittest.TextTestRunner(verbosity=2).run(suite)

class testAssorted(unittest.TestCase):

  def setUp(self):
    if QT():
      from PyQt4 import QtCore
      self.app = CCP4Modules.QTAPPLICATION()
    import CCP4Container
    self.mummy = CCP4Container.CContainer()


  def testMtzColumn(self):
    t = CMtzColumn(columnLabel='foo',columnType='A',parent=self.mummy)
    self.assertEqual(t.columnLabel,'foo','Error instantiating CMtzColumn')
    try:
      t = CMtzColumn(columnLabel='foo',columnType='Z',parent=self.mummy)    
    except CException as e:
      self.assertEqual(e[0]['code'],103,'Wrong error when instantiating CMtzColumn with bad column type')
    except:
      self.fail('Unexpected exception when instantiating CMtzColumn with bad column type')
    

  def testMtzData(self):
    import os,CCP4Utils
    filename =  os.path.normpath(os.path.join(CCP4Utils.getCCP4I2Dir(),'test','data','gere_nat.mtz'))
    t = CMtzData(parent=self.mummy,name='foo')
    t.loadFile(filename)
    self.assertEqual( 19, t.getNColumns(),'CMtzData loaded MTZ reports wrong number of columns')
  
class testMtz(unittest.TestCase):

  def setUp(self):
    import os
    import CCP4Utils
    self.testDataDir =  os.path.normpath(os.path.join(CCP4Utils.getCCP4I2Dir(),'test','data'))
    #print 'mtzFilename',self.mtzFilename,os.path.exists(self.mtzFilename)
    # make all background jobs wait for completion
    CCP4Modules.PROCESSMANAGER().setWaitForFinished(10000)
    if QT():
      from PyQt4 import QtCore
      self.app = CCP4Modules.QTAPPLICATION()
      self.mummy = QtCore.QObject(self.app)
    else:
      self.mummy = None

  def tearDown(self):
    CCP4Modules.PROCESSMANAGER().setWaitForFinished(-1)

  def test_1(self):
    import os
    self.mtz = CMtzDataFile( os.path.normpath(os.path.join(self.testDataDir,'gere_nat.mtz'),parent=self.mummy))
    print "test_1 getNColumns",self.mtz.getFileContent().getNColumns()
    self.assertEqual( 19, self.mtz.getFileContent().getNColumns(),'CMtzDataFile loaded MTZ reports wrong number of columns')

  def test_2(self):
    import os
    import CCP4Container
    self.dataContainer = CCP4Container.CContainer()
    self.dataContainer.loadContentsFromXml( os.path.normpath(os.path.join(self.testDataDir,'test_mtz_2.def.xml')))
    columns = self.dataContainer.testCProgramColumnGroup.F_SIGF.qualifiers('columnGroup')
    self.assertEqual(str(columns[0].columnName),'F','CProgramColumnGroup failed to load from test_mtz_2.def.xml')
    self.assertEqual(str(columns[1].columnName),'SIGF','CProgramColumnGroup failed to load from test_mtz_2.def.xml')
    

    self.dataContainer.loadDataFromXml( os.path.normpath(os.path.join(self.testDataDir,'test_mtz_2.params.xml')))
    #print 'test_2',self.dataContainer
    dataF = self.dataContainer.inputData.F_SIGF.F
    self.assertEqual(dataF,'F_nat','CProgramColumnGroup failed to load from test_mtz_2.params.xml')

  def test_3(self):
    import os
    import CCP4Container
    self.dataContainer = CCP4Container.CContainer()
    self.dataContainer.loadContentsFromXml( os.path.normpath(os.path.join(self.testDataDir,'test_mtz_2.def.xml')))
    # def file does not have SIGF defined
    self.dataContainer.loadDataFromXml( os.path.normpath(os.path.join(self.testDataDir,'test_mtz_3.params.xml')))
    fixed = self.dataContainer.inputData.F_SIGF.fix(self.dataContainer.testCProgramColumnGroup.F_SIGF.get())
    #print 'test_3 fixed',fixed,self.dataContainer.testCProgramColumnGroup.F_SIGF
    self.assertEqual(str(self.dataContainer.testCProgramColumnGroup.F_SIGF.SIGF),'SIGF_nat','CProgramColumnGroup.Partner failed to find unset column')
    
class testCell(unittest.TestCase):

  def testLength1(self):
    l = CCellLength(56.8)
    m = CCellLength()
    m.nm = 5.69
    self.assertEqual(l>m,False,'Comparison of CCellLength failed')
    self.assertEqual(l+0.2>m,True,'Addition and comparison of CCellLength failed')

  def testAngle1(self):
    import math
    t = CCellAngle(90.0)
    if t.rad<math.pi/2.0-0.001 or t.rad>math.pi/2.0+0.001:
        self.fail('Error return CCellAngle as radians')
        
  def testAngle2(self):
    try:
      t = CCellAngle(-56.0)
    except CException as e:
      self.assertEqual(len(e),1,'Unexpected exception length in setting  CCellAngle')
      self.assertEqual(e[0]['code'],101,'Unexpected exception in setting  CCellAngle')
    except:
      self.fail('Unexpected exception in setting  CCellAngle')
    else:
      self.fail('No exception in setting CCellAngle')

  def testCell1(self):
    c = CCell(a=78.0,b=56.0,c=13.5,alpha=89.0,beta=93.8,gamma=103.4)
    if c.a.nm > 7.81 or c.a.nm < 7.79:
      self.fail('Error returning cell length as nm')

class testSpaceGroup(unittest.TestCase):

  def setUp(self):
    self.symMan = SYMMETRYMANAGER()

  def test1(self):
    # test that the hard-coded chiral space groups match to xHM names in syminfo.lib
    for xSys in self.symMan.crystalSystems:
      for sgp in self.symMan.chiralSpaceGroups[xSys]:
        status,newSgp = self.symMan.spaceGroupValidity(sgp)
        if status is 5:
          newSgpChiral = []
          for item in newSgp:
            ii = self.symMan.hmSpaceGroupList.index(item)
            if self.symMan.pointGroupList[ii].count('-') == 0:
              newSgpChiral.append(item)
          print sgp,'*',status,'*',newSgpChiral
        elif status is not 0:
         self.fail('SYMMETRYMANAGER chrial space group name not found in syminfo.lib:'+sgp)

  def test2(self):

    s = CSpaceGroup()
    for sgp,expectedErr,expectedFix  in [ [ 'P 21 21 21' , None, 'P 21 21 21' ],
                                    [ 'P -1', 102,'P -1' ],
                                    [ 'P4/n b m', 103, 'P 4/n b m :1'],
                                    [ 'P21 1 1', 105 ,'P 21 1 1' ] ]:
      rv = s.validity( sgp )
      if len(rv) == 0:
        if expectedErr is not None:
          self.fail('No validity fail for CSpaceGroup:'+sgp)
      elif len(rv)>1:
        self.fail('CErrorReport for CSpaceGroup longer than 1:'+sgp)     
      elif rv[0]['code'] != expectedErr:
        self.fail('CErrorReport for CSpaceGroup does not give expected error:'+sgp)
      fix = s.fix(sgp)
      #print 'test2',sgp,fix
      if fix != expectedFix:
        self.fail('Incorrect CSpaceGroup.fix() for:'+sgp)

  def test3(self):

    s = CSpaceGroupCell()
    for sgp,cell,expectedErr in [
      ['P 21 21 21', { 'a': 64.900, 'b':78.320, 'c':38.790, 'alpha':90.00, 'beta':90.00, 'gamma':  90.00 }, None ],
      ['P 21 21 21', { 'a': 64.900, 'b':78.320, 'c':38.790, 'alpha':90.00, 'beta':91.00, 'gamma':  90.00 }, 103 ],
      ['P 21 21 21', { 'a': 64.900, 'b':78.320, 'c':64.900, 'alpha':90.00, 'beta':90.00, 'gamma':  90.00 }, 101 ],
      ['P 21 1 1', { 'a': 64.900, 'b':78.320, 'c':38.790, 'alpha':90.00, 'beta':90.00, 'gamma':  90.00 }, 104 ]
      ]:

      rv = s.validity( { 'spaceGroup' : sgp, 'cell' : cell}  )
      #print rv.report()
      if len(rv) == 0:
        if expectedErr is not None:
          self.fail('No validity fail for CSpaceGroupCell:'+sgp)
      elif len(rv)>1:
        self.fail('CErrorReport for CSpaceGroupCell longer than 1:'+sgp)     
      elif rv[0]['code'] != expectedErr:
        self.fail('CErrorReport for CSpaceGroupCell does not give expected error:'+sgp)



class testCObsDataFile(unittest.TestCase):
  def setUp(self):
    import os,CCP4Utils
    self.testDataDir =  os.path.normpath(os.path.join(CCP4Utils.getCCP4I2Dir(),'test','data'))
    self.app = CCP4Modules.QTAPPLICATION()
    from PyQt4 import QtCore
    self.mummy = QtCore.QObject(self.app)
    
  def test1(self):
    import os,CCP4Utils
    self.obs = CObsDataFile(parent=self.mummy,fullPath= os.path.normpath(os.path.join(self.testDataDir,'rnase_obs_fpair.mtz')))
    self.obs.setContentFlag()
    print 'testCObsDataFile.test1 contentFlag',self.obs.contentFlag

    outfile =  os.path.normpath(os.path.join(CCP4Utils.getTestTmpDir(),'testCObsDataFile.mtz'))
    if os.path.exists(outfile): os.remove(outfile)
    self.obs.convert(targetContent=CObsDataFile.CONTENT_FLAG_FMEAN,targetFile=outfile)
    self.assertTrue(os.path.exists(outfile),'CObsDataFile.convert failed')
    
class testCPhsDataFile(unittest.TestCase):
  def setUp(self):
    import os,CCP4Utils
    self.testDataDir =  os.path.normpath(os.path.join(CCP4Utils.getCCP4I2Dir(),'test','data'))
    self.app = CCP4Modules.QTAPPLICATION()
    from PyQt4 import QtCore
    self.mummy = QtCore.QObject(self.app)
    
  def test1(self):
    import os,CCP4Utils
    self.phs = CPhsDataFile(parent=self.mummy,fullPath= os.path.normpath(os.path.join(self.testDataDir,'rnase25_mini_HL.mtz')))
    self.phs.setContentFlag()
    print 'testCPhsDataFile.test1 contentFlag',self.phs.contentFlag

    outfile =  os.path.normpath(os.path.join(CCP4Utils.getTestTmpDir(),'testCPhsDataFile.mtz'))
    if os.path.exists(outfile): os.remove(outfile)
    self.phs.convert(targetContent=CPhsDataFile.CONTENT_FLAG_PHIFOM,targetFile=outfile)
    self.assertTrue(os.path.exists(outfile),'CPhsDataFile.convert failed')
    # beware self.phs suddenly is something else..
    self.phs.setFullPath(outfile)
    self.phs.loadFile()
    columns = self.phs.fileContent.getListOfColumns()
    print 'testCPhsDataFile.test1',columns
    self.assertEqual(len(columns),2,'Output from testCPhsDataFile has wrong number of columns')
    self.assertTrue(columns[0].columnLabel.__str__() in ['PHI','FOM'] and columns[1].columnLabel.__str__() in ['PHI','FOM'],'Output from testCPhsDataFile has wrong column labels')


  
