#!/usr/bin/python3
'''
create orthoxml file

orthoxml structure

orthoXML:
  origin           [str]
  version          [float]
  originVersion    [str]
  notes            [class notes]
  species          [class species]
    database
      genes  array of <gene>
        gene (id[int], protId, geneId, transcripId )

  scores           [class scores]
    scoreDef
    score

  groups           [class groups]
    group
      geneRef
      orthologGroup   - subgroups
      paralogGroup    - paralogs
    
'''

import os
import argparse
import sys

from collections import OrderedDict

import orthoxml
###from ete3 import orthoxml
from ete3 import NCBITaxa
from ete3 import Tree

def print_error(s):
    print(('ERROR   : %s')%s,file=sys.stderr)

def get_dict_float(d,var,default=None):
    val = d.get(var,default)
    if val is None:
        return None
    return float(val)

def get_dict_int(d,var,default=None):
    val = d.get(var,default)
    if val is None:
        return None
    return int(val)

def getMapFiles( options ):
    allfiles = []
    if options.maps is not None:
        allfiles = allfiles + options.maps
    if options.mapfile is not None:
        with open(options.mapfile,'r') as fh:
            for line in fh:
                line = line.strip().rstrip('\n')
                if len(line)==0: continue
                if line[0]=='#': continue
                allfiles.append(line)

    if options.labels is not None:
        with open(options.labels,'r') as fh:
            for line in fh:
                line = line.strip().rstrip('\n')
                if len(line)==0: continue
                if line[0]=='#': continue
                line = line+'.fs.maptxt'
                fname=os.path.join(options.mapdir,line)
                allfiles.append(fname)
        
    return allfiles

def readMap( fnames, colKey, colValue ):
    '''
    reads an id map from a file with at least 2 whitespace separated columns
    first column : the ID - no whitespaces allowed in this id
    second column: the original label, any further
    '''
    if fnames is None:
        return {},{}

    if len(fnames)==0:
        return {},{}
    
    # get the min nr of columns required
    maxcol = max(colKey,colValue)+1
    
    # get map
    labelMap = {}
    detailsMap = {}
    for f in fnames:
        if f is None:
            continue
        # skip if file does not exist
        if not os.path.isfile(f):
            continue
        # open file
        with open(f,mode='r') as fh:
            # read map
            for rawline in fh:
                line = rawline.strip().rstrip('\n')
                if len(line)==0: continue
                if line[0]=='#': continue
                # split line in:
                #   <id> <orig id>
                elems=line.split(maxsplit=maxcol)
                if len(elems)<maxcol:
                    continue

                key   = elems[colKey]
                value = elems[colValue]
                
                if len(elems)>maxcol:
                    # add rest to description map
                    detailsMap[key] = elems[maxcol]

                labelMap[key]=value

    return labelMap,detailsMap

def readMaps( options ):
    fmaps = getMapFiles( options )
    idmap,detmap = readMap( fmaps,0,1 )
    return idmap,detmap


class ClusterElementStats:
    '''
    Handles stat lines in brhclus output.
    #STATS id:1,nkeys:37,nspec:34,ndupl:3,nedges:238,connectivity:0.3,singlecopyness:0.9,unicopyness:0.6,pid:98.0,score:160.0
    Data is considered 'good' if cluster id is found.
    '''
    def __init__(self,line):
        self.good = True
        elems = line.split(',')
        self._data={}
        for e in elems:
            var=e.split(':')
            self._data[var[0]] = var[1]
        self.clid = get_dict_int(self._data,'id')
        self.nkeys = get_dict_int(self._data,'nkeys',None)
        self.nspec = get_dict_int(self._data,'nspec',None)
        self.ndupl = get_dict_int(self._data,'ndupl',None)
        self.nedges = get_dict_int(self._data,'nedges',None)
        self.connectivity = get_dict_float(self._data,'connectivity',None)
        self.singlecopyness = get_dict_float(self._data,'singlecopyness',None)
        self.unicopyness = get_dict_float(self._data,'unicopyness',None)
        self.pid = get_dict_float(self._data,'pid',-1.0)
        self.score = get_dict_float(self._data,'score',-1.0)
        self.good = self.clid is not None
        
class ClusterElement:
    '''
    Container for one cluster element.
    It assumes lines of the format:
    <clid> <label> <type> <length> <start> <end> <pid> <score> <eval>
    Element is 'good' if exactly 9 columns are found.
    '''
    def __init__(self,line,taxsep):
        self.good = True
        elems = line.split()
        if not len(elems)==9:
            self.good = False
        else:
            self.clid = int(elems[0])
            self.label = elems[1]
            self.cltype = int(elems[2])
            self.start = int(elems[4])
            self.end = int(elems[5])
            self.pid = float(elems[6])
            self.score = float(elems[7])
            self.evalue = float(elems[8])
            self.geneid = None
            self.species = None
            
            if taxsep is not None:
                e = self.label.split(taxsep)
                if len(e)>1:
                    self.species = e[0]
                        
                        
            
class ClusterData:
    '''
    Container for one cluster.
    data : maps species id to cluster element
    stats: an instance of ClusterElementStats , set externally
    '''
    def __init__(self,numid=None):
        self.clid = numid
        self.data = {}
        self.stats = None
        
    def add(self, celem):
        if self.clid is None:
            self.clid = celem.clid
        if celem.species not in self.data:
            self.data[celem.species] = []
        self.data[celem.species].append(celem)
        return True

    def dataSpecies(self,sp):
        if sp not in self.data:
            return None
        return self.data[sp]
        
    def hasSpecies(self,sp):
        return sp in self.data

class pair:
    def __init__(self,label,geneid):
        self.label = label
        self.geneid = geneid

def clusterId(clid,level):
    return '{}at{}'.format(clid,level)

def readClusterFile( fname, level, taxsep, species, geneid, allgenes ):
    '''
    Reads clusters from input file.
    The reader supports special stat lines #STATS ...
    A cluster element line has the format:
     <clid> <label> <type> <length> <start> <end> <pid> <score> <eval>

    It returns
      cldata  : maps cluster id to ClusterData instance
      species : maps species id to a list of gene ids
    '''
    hasStats=False
    
    cldata  = None
    hasspec = taxsep is not None
    if geneid is None:
        geneid=0
    
    with open( fname, mode='r' ) as fh:
        print('process file {}'.format(fname))
        cldata  = OrderedDict()
        clstats = {}
        if species is None:
            species = {}
        # loop over cluster file
        for line in fh:
            line = line.strip().rstrip('\n')
            # remove empty lines
            if len(line)==0:
                continue
            
            # check if the line is a stats line
            if line.startswith('#STATS'):
                line = line[7:]
                cls = ClusterElementStats(line)
                clstats[cls.clid] = cls
                hasStats = True
                continue

            # comment line
            if line[0]=='#':
                continue
            
            # create a new cluster element -> one gene
            celem = ClusterElement(line,taxsep)

            # if not a good line, skip silently
            if not celem.good:
                continue

            # check species
            if hasspec:
                if celem.species is None:
                    print_error('label and taxonomy separator are incompatible for gene {}'.format(celem.label))
                    continue

            # set numerical gene id
            if celem.label in allgenes:
                celem.geneid = allgenes[celem.label]['geneid']
                allgenes[celem.label]['clid'].append([celem.clid,level])
            else:
                celem.geneid = geneid
                geneid = geneid+1
                allgenes[celem.label] = {}
                allgenes[celem.label]['geneid'] = celem.geneid
                allgenes[celem.label]['clid']   = [[celem.clid,level]]


            # now group all genes under its species
            # if no species is defined, sp==None
            sp = celem.species
            if sp not in species:
                species[sp] = OrderedDict()
                
            species[sp][celem.geneid] = celem.label

            #{'label':celem.label,'geneid':geneid})


            # update cluster
            if celem.clid not in cldata:
                cldata[celem.clid] = ClusterData(celem.clid)
            cldata[celem.clid].add(celem)
        
        # fill cluster stats in cl elements
        # if no stats is found for the cluster, the stats member will remain None
        if len(clstats)>0:
            for clid,cls in clstats.items():
                if clid in cldata:
                    cldata[clid].stats = cls
        
    # return clusters and species map
    return cldata,species,geneid,allgenes,hasStats

def readClustersTree( options ):
    if options.tree is None:
        return None,None,None,None,0,False
    nwstr=None
    with open(options.tree,'r') as fh:
        for line in fh:
            line.strip()
            if line[0]=='#':
                continue
            nwstr = line
            if not nwstr.endswith(';'):
                nwstr = nwstr+';'
            break
    if nwstr is None:
        print_error('tree file not readable or contains no valid data : {}'.format(tree))
        return None,None,None,None,0
    
    # parse the tree, format=1 -> keep names of internal nodes
    tree = Tree(nwstr,format=1)

    hasStats = False
    hasMissing = False
    generefs=OrderedDict() # genes ordered by species
    clusters=OrderedDict() # clusters ordered by taxid of level
    allgenes=OrderedDict()
    nclusters=0
    geneid=0
    allnodes = []
    if options.reroot is None:
        allnodes = [ tree ]
    savenodes=False
    for node in tree.get_descendants():
        #print('NODE {} up is root {} up name {}'.format(node.name,node.up.is_root(),node.up.name))
        if options.reroot is not None:
            if node.name == options.reroot:
                for nodenew in node.get_descendants():
                    print('Adding node {} from new root {}'.format(nodenew.name,options.reroot))
                    allnodes.append(nodenew)
                break
        else:
            print('Adding node {}'.format(node.name))
            allnodes.append(node)

    
    for c in allnodes:
        if c.is_leaf():
            continue
        level = c.name
        fname = os.path.join(options.inpdir,'{}{}'.format(level,options.inpext))
        if not os.path.isfile(fname):
            print_error('missing cluster file {}'.format(fname))
            hasMissing = True
            break
        clusters[level] = OrderedDict()
        clusters[level],generefs,geneid,allgenes,hs = readClusterFile( fname, level, options.taxsep, generefs, geneid, allgenes )
        nclusters = nclusters + len(clusters[level])
        if hs:
            hasStats = True
        
    if hasMissing:
        return None,None,None,None,0,False

    return tree,clusters,generefs,allgenes,nclusters,hasStats

def getNCBITaxids( speciesrefs ):
    spset = set()
    for sp in speciesrefs:
        if sp is not None:
            try:
                spint = int(e[0])
                spset.add(spint)
            except:
                spint = None
    
        
def createOrthoXML( options, clusters, allmaps, speciesrefs, hasStats ):
    '''
    create OrthoXML output file
    '''
    if len(clusters)==0:
        return

    # orthoXML
    #    species
    #      database
    #         genes
    oxml = orthoxml.orthoXML()

    oxml.set_version('0.30')
    oxml.set_origin('OrthoDB')
    oxml.set_originVersion('1.0')
    
    # loop over species to genes map
    for sp,generefs in speciesrefs.items():

        # add species object (will contain DB class)
        species = orthoxml.species()
    

        # add DB per species id
        database = orthoxml.database()

        # add genes to DB
        genes = orthoxml.genes()

        # loop over all genes for this species
        for geneid,label in generefs.items():
            # set gene info (prot id, gene id, transcript id)
            gidstr = allmaps.get(label,label)
            gene = orthoxml.gene(gidstr,geneid)
            genes.add_gene(gene)
        
        # set database
        database.set_genes(genes)
        database.set_name(sp)
        database.set_version('unknown')
        
        # add database
        species.add_database(database)
        species.set_name(sp)
        try:
            taxid=int(sp[0:sp.find('_')])
        except:
            taxid=0
        species.set_NCBITaxId(taxid)
        
        # add set of species
        oxml.add_species(species)
    
    # Add an ortho group container to the orthoXML document
    ortho_groups = orthoxml.groups()
    oxml.set_groups(ortho_groups)

    # score definitions
    scores = orthoxml.scores()
    if hasStats:
        scores.add_scoreDef( orthoxml.scoreDef('cscore','mean score in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('cpid','mean percentage ID in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('nedges','nr of edges in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('nkeys','nr of keys in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('connectivity','cluster connectivity') )
        scores.add_scoreDef( orthoxml.scoreDef('singlecopyness','nr of species divided by nr of genes in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('unicopyness','nr of species divided by total nr of species') )
        scores.add_scoreDef( orthoxml.scoreDef('score','score for gene') )
        scores.add_scoreDef( orthoxml.scoreDef('pid','percentage ID for gene') )
        
    oxml.set_scores(scores)
    
    # loop over clusters
    for clid,cluster in clusters.items():
        # Add an orthology group including two sequences
        orthologs = orthoxml.group(clid)
        clstats = cluster.stats
        if clstats is not None:
            orthologs.add_score(orthoxml.score('cscore',clstats.score))
            orthologs.add_score(orthoxml.score('cpid',clstats.pid))
            orthologs.add_score(orthoxml.score('nedges',clstats.nedges))
            orthologs.add_score(orthoxml.score('nkeys',clstats.nkeys))
            orthologs.add_score(orthoxml.score('connectivity',clstats.connectivity))
            orthologs.add_score(orthoxml.score('singlecopyness',clstats.singlecopyness))
            orthologs.add_score(orthoxml.score('unicopyness',clstats.unicopyness))
        # loop over cluster
        for sp,vcelems in cluster.data.items():
            for celem in vcelems:
                # get numerical gene id
                generef = orthoxml.geneRef(celem.geneid)
                generef.add_score(orthoxml.score('score',celem.score))
                generef.add_score(orthoxml.score('pid',celem.pid))
                orthologs.add_geneRef(generef)
            
        ortho_groups.add_orthologGroup(orthologs)

    print('writing to file {}'.format(options.output))
    oxml_file = open(options.output, mode='w')
    oxml.export(oxml_file, level=0, namespace_='',name_="orthoXML",namespacedef_='xmlns="http://orthoXML.org/2011/"')
    oxml_file.close()

def findSubGroup( group, name ):
    ind=0
    for g in group.orthologGroup:
        if g.id==name:
            return ind
        ind=ind+1
    return None

def addLeaf2Group( level, taxid, clustermap, group ):
    
    if level not in clustermap:
        print_error('addLeaf: missing level {}'.format(level))
        return group
#    if taxid not in clustermap[level].data:
#        print_error('addLeaf: level {} -> missing taxid {}'.format(level,taxid))
#        return group
    nfound=0
    for clid,cldata in clustermap[level].items():
        # cldata -> ClusterData
        celems = cldata.dataSpecies(taxid)
        if celems is None:
            continue

        name = clusterId(clid,level)
        indgrp = findSubGroup( group, name )
        addgrp = False
        if indgrp is None:
            addgrp = True
            clgroup = orthoxml.group(name)
            taxrange = orthoxml.property('TaxRange',level)
            clgroup.add_property(taxrange)

        else:
            clgroup = group.orthologGroup[indgrp]
            
        for e in celems:
            generef = orthoxml.geneRef(e.geneid)
            clgroup.add_geneRef(generef)

        if addgrp:
            group.add_orthologGroup(clgroup)
    
        #    print('info({}): taxid {} found {} times'.format(level,taxid,nfound))

    return group
    
def fillHierarchialGroups( clustermap, node, ingroup ):
    level = node.name
    if level not in clustermap:
        print_error('missing level in clustermap {}'.format(level))
        for x,y in clustermap.items():
            print(x)
        return None

    if ingroup is None:
        ingroup = orthoxml.group(level)
    # possibly add score defs here
    for c in node.children:
        if c.is_leaf():
            ingroup = addLeaf2Group(level,c.name,clustermap,ingroup)
        else:
            group = orthoxml.group(c.name)
            group = fillHierarchialGroups(clustermap,c,group)
            if group is not None:
                ingroup.add_orthologGroup(group)
            
    return ingroup



def findMostSpecific( group ):
    singleton = True
    tlevel=0
    while singleton:
        singleton = len(group.geneRef)==0 and len(group.orthologGroup)==1
        if singleton:
            group = group.orthologGroup[0]
            tlevel = tlevel+1
    return tlevel,group

def squashGroups( group ):
    t,group = findMostSpecific( group)
    
    for ind in range(0,len(group.orthologGroup)):
        t,g = findMostSpecific(group.orthologGroup[ind])
        if t>0:
            # squashed at least one group
            group.orthologGroup[ind] = g
        group.orthologGroup[ind] = squashGroups(group.orthologGroup[ind])
    return group

def fillGenesToGroups( clustermap, allgenes, squash ):
    ortho_groups = orthoxml.groups()

    # dictionary with all groups
    # key = clusterId()
    # value = [group,
    allgroupsInd={}
    for geneid,data in allgenes.items():
        # data contains the branch for one gene id:
        #  'geneid' : numerical geneid
        #  'clid'   : list of numerical cluster ids
        #  'level'  : list of numerical level taxids
        # The order in the lists are from root to leaf nodes
        # By construction, all gene id's is member of a group in root
        clroot = data['clid'][0]
        clid = clroot[0]
        level = clroot[1]
        
        # get cluster id string for root <clid>at<level>
        # and set root group
        rootStr = clusterId(clid,level)
        if clid not in allgroupsInd:
            # new group at root
            #print('add new grp {}'.format(rootStr))
            taxrange = orthoxml.property('TaxRange',level)
            ortho_groups.add_orthologGroup(orthoxml.group(rootStr,None,[taxrange]))
            index = len(ortho_groups.orthologGroup)-1
            allgroupsInd[clid] = index
        else:
            index = allgroupsInd[clid]
            #print('found old grp {} at index {}'.format(rootStr,index))
        
        rootGroup = ortho_groups.orthologGroup[index]

        
        # loop over the rest
        # set start to root
        group = rootGroup
        lstr = ' '
        for clinfo in data['clid'][1:]:
            clid = clinfo[0]
            level = clinfo[1]
            # get cluster id string <clid>at<level>
            clidStr = clusterId(clid,level)
            
            # find sub-group in current group
            indgrp = findSubGroup( group, clidStr )
            if indgrp is None:
                #print('{}add new grp {}'.format(lstr,clidStr))
                # group is not found - add a new one
                taxrange = orthoxml.property('TaxRange',level)
                group.add_orthologGroup(orthoxml.group(clidStr,None,[taxrange]))
                group = group.orthologGroup[-1]
            else:
                # it is found
                group = group.orthologGroup[indgrp]
                #print('{}found old grp {} at index {}'.format(lstr,clidStr,index))
                
            lstr = lstr+' '
        
        #print('{}add gene ref {} {}'.format(lstr,geneid,data))
        group.add_geneRef(orthoxml.geneRef(data['geneid']))

    if squash:
        for ind in range(0,len(ortho_groups.orthologGroup)):
            ortho_groups.orthologGroup[ind] = squashGroups(ortho_groups.orthologGroup[ind])
    
    return ortho_groups
        
def createHierarchialOrthoXML( options, allgenes, clustermap, allmaps, speciesrefs, hasStats ):
    '''
    create OrthoXML output file
    '''
    if len(clustermap)==0:
        return

    # orthoXML
    #    species
    #      database
    #         genes
    oxml = orthoxml.orthoXML()

    oxml.set_version('0.30')
    oxml.set_origin('OrthoDB')
    oxml.set_originVersion('1.0')

    ncbi = None
    sciname = None
    if options.scinames:
        ncbi = NCBITaxa()
    
    # loop over species to genes map
    for sp,generefs in speciesrefs.items():

        # <species>
        # add species object (will contain DB class)
        species = orthoxml.species()
        # set taxid
        try:
            taxid=int(sp[0:sp.find('_')])
            species.set_NCBITaxId(taxid)
        except:
            taxid=None
        
        # set name -> map NCBI taxid to scientific name
        if taxid is not None:
            if ncbi is not None:
                sciname = ncbi.get_taxid_translator([taxid])
                species.set_name(sciname[taxid])
            else:
                species.set_name(taxid)
        else:
            species.set_name('unknown')
        
        # <database>
        # add DB per species id
        database = orthoxml.database()
        # we dont have this info (?)
        database.set_name('unknown')
        database.set_version('unknown')
        
        # <genes>
        # add genes to DB
        genes = orthoxml.genes()

        # loop over all genes for this species
        for geneid,label in generefs.items():
            # set gene info (prot id, gene id, transcript id)
            gidstr = allmaps.get(label,label)
            gene = orthoxml.gene(gidstr,geneid)
            genes.add_gene(gene)
        
        # set database
        database.set_genes(genes)
        
        # add database
        species.add_database(database)

        # add the species
        oxml.add_species(species)
    
    # score definitions
    scores = orthoxml.scores()
    if hasStats:
        scores.add_scoreDef( orthoxml.scoreDef('cscore','mean score in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('cpid','mean percentage ID in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('nedges','nr of edges in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('nkeys','nr of keys in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('connectivity','cluster connectivity') )
        scores.add_scoreDef( orthoxml.scoreDef('singlecopyness','nr of species divided by nr of genes in cluster') )
        scores.add_scoreDef( orthoxml.scoreDef('unicopyness','nr of species divided by total nr of species') )
        scores.add_scoreDef( orthoxml.scoreDef('score','score for gene') )
        scores.add_scoreDef( orthoxml.scoreDef('pid','percentage ID for gene') )
    
    oxml.set_scores(scores)

    ortho_groups = fillGenesToGroups( clustermap, allgenes, options.squash )
    oxml.set_groups(ortho_groups)

    print('writing to file {}'.format(options.output))
    oxml_file = open(options.output, mode='w')
    oxml_file.write("<?xml version='1.0' encoding='UTF-8'?>\n")
    oxml.export(oxml_file, level=0,namespace_='',name_="orthoXML",namespacedef_='xmlns="http://orthoXML.org/2011/"')
    oxml_file.close()




#-----------------------------------------------------------------
# MAIN


parser = argparse.ArgumentParser(description='Prepare fasta file')

parser.add_argument('-f', '--file',    dest='fname',     help='input cluster file',         metavar='FILE',   default = None, required=False)
parser.add_argument('-l', '--label',   dest='label',     help='root label if no tree is given',     metavar='FILE',   default = None, required=False)
parser.add_argument('-w', '--tree',    dest='tree',      help='input newick tree file',     metavar='FILE',   default = None, required=False)
parser.add_argument('-r', '--reroot',  dest='reroot',    help='set root in newick',         metavar='label',  default = None, required=False)
parser.add_argument('-D', '--inpdir',  dest='inpdir',    help='directory with input files', metavar='DIR',    default = 'Cluster', required=False)
parser.add_argument('-e', '--inpext',  dest='inpext',    help='extension of input files',   metavar='string', default = '.og', required=False)
parser.add_argument('-o', '--output',  dest='output',    help='output orthoxml file',       metavar='FILE', required=True)

parser.add_argument('-q', '--squash',  dest='squash',    help='squash clusters whose child is identical', action='store_true', default=False)

parser.add_argument('-m', '--map',     dest='maps',      help='gene map file <X,Y>',  action='append', metavar='FILE', required=False, default=None)
parser.add_argument('-n', '--mapfile', dest='mapfile',   help='file with map files to be used (instead of opt -m)',  metavar='FILE', required=False, default=None)
parser.add_argument('-p', '--labels',  dest='labels',    help='file with labels (instead of -n)',  metavar='FILE', required=False, default=None)
parser.add_argument('-d', '--mapdir',  dest='mapdir',    help='directory where map files are found (with -p)',  metavar='FILE', required=False, default=None)
parser.add_argument('-s', '--swap',    dest='swap',      help='swap mapping order', action='store_true', default=False)

parser.add_argument('-k', '--keep',    dest='keep',      help='keep also unmapped elements', action='store_true', default=False)
parser.add_argument('-F', '--fail',    dest='forcefail', help='mapping fails if any unmapped geneids and/or cluster ids are found', action='store_true', required=False, default=None)

parser.add_argument('-T', '--taxsep',  dest='taxsep',    help='taxid separator - separates taxid and gene id', required=False, default=None)
parser.add_argument('-t', '--ncbitaxids',    dest='ncbitaxids',      help='taxids are NCBI taxids, requires --taxsep', action='store_true', default=False)
parser.add_argument('-c', '--scinames',    dest='scinames',      help='get scientific names from NCBI', action='store_true', default=False)

options = parser.parse_args()
if options.fname is None and options.tree is None:
    print_error('one of --tree and --fname is required')
    sys.exit(255)
    
if not ( options.fname is None or options.tree is None ):
    print_error('both --tree and --fname cannot be set')
    sys.exit(255)

idmap,detmap = readMaps( options )
print('read {} mapped ids'.format(len(idmap)))

if options.fname is not None:
    generefs=OrderedDict() # genes ordered by species
    clusters=OrderedDict() # clusters ordered by taxid of level
    allgenes=OrderedDict()
    geneid=0
    label='root'
    if options.label is not None:
        label=options.label
    clusters,generefs,geneid,allgenes,hasStats= readClusterFile(options.fname,label,options.taxsep,generefs,geneid,allgenes)
    print('read {} clusters'.format(len(clusters)))
    createOrthoXML(options,clusters,idmap,generefs,hasStats)
else:
    if options.taxsep is None:
        print_error('taxid separator (--taxsep) is required when using tree as input')
        sys.exit(255)
    
    print('create a hierarchial orthoxml file based on given tree')
    tree,clusters,generefs,allgenes,ncl,hasStats = readClustersTree(options)
    print('read {} cluster files with in total {} clusters'.format(len(clusters),ncl))
    createHierarchialOrthoXML(options,allgenes,clusters,idmap,generefs,hasStats)
