#!/usr/bin/python3
'''
Remaps id's in cluster/fasta/pairwise files.
Pipeline usage: POSTPROC:REMAP
'''

import os
import argparse
import sys

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

def getMapFiles( options ):
    allfiles = []
    anyok = False
    if options.maps is not None:
        allfiles = allfiles + options.maps
        anyok = True
    if options.mapfile is not None:
        anyok = True
        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:
        anyok = True
        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)
                #print('#added map file: {}'.format(fname))

    if not anyok:
        return None
    return allfiles


def readMap( fnames, colKey, colValue, splitat=None, minlen=3, select=0 ):
    if fnames is None:
        return None

    # make sure gene parsing options are OK
    if minlen<1:
        minlen=1
    if splitat is not None and len(splitat)==0:
        splitat=None
    if select<0:
        select=0
    
    # get the min nr of columns required
    maxcol = max(colKey,colValue)+1
    # get map
    labelMap = {}
    for f in fnames:
        if f is None:
            continue
        # skip missing files
        if not os.path.isfile(f):
            continue
        with open(f,mode='r') as fsh:
            # read map
            for rawline in fsh.readlines():
                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
                if splitat is None:
                    # no parsing
                    value=elems[colValue]
                else:
                    # split id
                    v=elems[colValue].split(splitat)
                    vclean=[]
                    for x in v:
                        # clean up each element by stripping white spaces
                        vx=x.strip()
                        # skip 'gi'
                        vxl=vx.lower()
                        if vxl=='gi':
                            continue
                        # skip if too short
                        if len(vx)<minlen:
                            continue
                        # save
                        vclean.append(vx)
                    
                    if len(vclean)==1:
                        value=vclean[0]
                    else:
                        if select<len(vclean):
                            value=vclean[select]
                        else:
                            value=vclean[0]
                labelMap[elems[colKey]]=value

    return labelMap

def guessfmt( datafile ):
    if datafile is None:
        return None
    datatype=None
    record=[]
    nrecs=0
    nfasta=0
    nclus=0
    ntodo=0
    npair=0
    with open(datafile,mode='r') as fsh:

        for rawline in fsh.readlines():
            line = rawline.strip().rstrip('\n')
            if len(line)==0: continue
            if line[0]=='#': continue

            # collect 2 lines
            if len(record)<2:
                record.append(line)
                continue

            # record is a fasta
            if record[0][0]=='>' and record[1][0]!='>':
                nfasta = nfasta+1
                continue

            nrecs=nrecs+1

            # split records...
            elems0 = record[0].split()
            elems1 = record[1].split()
            record=[]
            n0 = len(elems0)
            n1 = len(elems1)
            # both are of size 1 -> todo file
            if n0==1 and n1==1:
                ntodo = ntodo+1
                continue

            if n0>1 and n0==n1:
                if elems0[0].isdigit():
                    nclus = nclus+1
                    datatype = 1 # cluster file with numerical id
                    continue

                npair=npair+1
                datatype = 2 # pairwise...?
                continue
    if nfasta>0 and nclus+ntodo+npair==0:
        return 0 # fasta
    if nclus>0 and nfasta+ntodo+npair==0:
        return 1 # cluster
    if npair>0 and nclus+nfasta+ntodo==0:
        return 2 # pairwise
    if ntodo>0 and nclus+nfasta+npair==0:
        return 3 # todo

    return datatype

def get_mapped( themap, datastr, maptaxid, specsep ):
    inpdat=datastr
    gid=None
    if maptaxid:
        if specsep is None:
            return None
        sp=datastr.split(specsep)
        inpdat=sp[0]
        gid=sp[1]

    vm = None
    if inpdat in themap:
        vm=themap[inpdat]
        if gid is not None:
            vm = vm+specsep+gid
    return vm

def remap_fasta(options):
    ###fname,maptaxid,specsep,mapfiles,swap,keepall,forcefail,splitat=None,minlen=3,select=0):
    # set default separator
    specsep = options.taxsep
    if specsep is None or len(specsep)==0:
        specsep=':'

    # load as taxid map if requested
    specmap = None
    mapdata = None
    usemap = None
    mapfiles = getMapFiles( options )
    if options.swap:
        usemap=readMap( mapfiles,1,0,options.splitgene,options.splitminlen,options.splitselect )
    else:
        usemap=readMap( mapfiles,0,1,options.splitgene,options.splitminlen,options.splitselect )
    
    #--------------------------------------------------
    # flag  data type         format
    #--------------------------------------------------
    #  0    fasta file        >[label] (newline) (sequence)
    #  1    cluster file      [clid] [label]
    #  2    pair wise file    [labelA] [labelB] [data]
    #  3    todo file         [label]

    nunmapped=0
    nbad=0
    ntotal=0
    with open(options.fname,mode='r') as fsh:
        for rawline in fsh.readlines():
            line = rawline.strip().rstrip('\n')
            if len(line)==0: continue
            if line[0]=='#':
                print(line)
                continue
            elems=line.split()
            # ==> FASTA
            if elems[0][0]=='>':
                ntotal=ntotal+1
                if len(elems[0])<2:
                    nbad=nbad+1
                    continue
                # label line
                label=elems[0][1:]
                newlabel = get_mapped(usemap,label,options.maptaxid,specsep)
                if newlabel is not None:
                    if (options.keepmap):
                        print (('>%s %s')%(newlabel,label))
                    else:
                        print (('>%s')%(newlabel))
                else:
                    # unmapped label
                    if options.keep:
                        print (('>%s')%(label))
                    else:
                        nunmapped=nunmapped+1
                        print (('>%s ###UNMAPPED')%(label))
                        if options.forcefail:
                            print_error('unmapped gene ids found')
                            return None,None,None
            else:
                # fasta sequence line - no change
                print (('%s')%(line))


    return ntotal,nunmapped,nbad



def remap_pairwise(options):
    #datafile,maptaxid,specsep,mapfiles,swap,keepall,forcefail,splitat=None,minlen=3,select=0):
    '''
    remap pair wise data
    [labelA] [labelB] [data]
    '''
    mapfiles = getMapFiles( options )
    if options.swap:
        mapdata=readMap(mapfiles,1,0,options.splitgene,options.splitminlen,options.splitselect)
    else:
        mapdata=readMap(mapfiles,0,1,options.splitgene,options.splitminlen,options.splitselect)
        
    nunmapped=0
    nbad=0
    ntotal=0    
    with open(options.fname,mode='r') as fsh:
        #--------------------------------------------------
        # flag  data type         format
        #--------------------------------------------------
        #  0    fasta file        >[label] (newline) (sequence)
        #  1    cluster file      [clid] [label]
        #  2    pair wise file    [labelA] [labelB] [data]

        for rawline in fsh.readlines():
            line = rawline.strip().rstrip('\n')
            if len(line)==0: continue
            if line[0]=='#':
                print(line)
                continue
            elems=line.split()
            nelems=len(elems)
            # ==> pair wise data
            if nelems < 2:
                nbad=nbad+1
                continue

            ntotal=ntotal+1
            labelA=elems[0]
            labelB=elems[1]
            data=''
            if nelems > 2:
                sep=' '
                data=sep.join(elems[2:])

            newA=get_mapped(mapdata,labelA,options.maptaxid,options.taxsep)
            newB=get_mapped(mapdata,labelB,options.maptaxid,options.taxsep)
            mappedA=True
            mappedB=True
            mappedAB=True
            if newA is None:
                mappedA=False
            if newB is None:
                mappedB=False

            mappedAB = mappedA and mappedB
            if options.keep or mappedAB:
                print (('%s %s %s')%(newA,newB,data))
            else:
                nunmapped=nunmapped+1
                print (('%s %s %s ###UNMAPPED')%(labelA,labelB,data))
                if forcefail:
                    print_error('unmapped gene ids found')
                    return None,None,None

    return ntotal,nunmapped,nbad

def remap_column( options, columnOverride = None, clidAppend = None ):
    '''
    remap a specific column
    if column is not given, options.column is used
    '''

    column = options.column
    if columnOverride is not None:
        column = columnOverride
    
    mapfiles = getMapFiles( options )

    if options.swap:
        mapdata=readMap(mapfiles,1,0,None,1,0)
    else:
        mapdata=readMap(mapfiles,0,1,None,1,0)
        
    nunmapped=0
    nbad=0 # always 0 in this case
    ntotal=0
    with open(options.fname,mode='r') as fsh:
        for rawline in fsh.readlines():
            line = rawline.strip().rstrip('\n')
            if len(line)==0: continue
            if line[0]=='#':
                print(line)
                continue

            elems=line.split()
            if column >= len(elems):
                continue
            
            label = elems[column]
            ntotal = ntotal+1
            newline = line
            ismapped = True
            if mapdata is not None:
                # if label is found in map, change it
                if label in mapdata:
                    elems[column] = mapdata[label]
                else:
                    ismapped = False
                    nunmapped=nunmapped+1
                    if options.forcefail:
                        print_error('unmapped id found')
                        return None,None,None


            if clidAppend is not None:
                elems[0] = elems[0]+clidAppend

            if ismapped:
                print( *elems, sep=options.outsep )
            else:
                print( *elems, "###UNMAPPED", sep=options.outsep )

    if ntotal==nunmapped:
        print_error('nothing mapped - consider the option --swap')
        
    return ntotal,nunmapped,nbad

def remap_column_pref( options, remove=False ):
    #datafile,mapfiles,swap,keepall,forcefail,column,prefix,remove=False,splitat=None,minlen=3,select=0):

    mapfiles = getMapFiles( options )
    if options.swap:
        mapdata=readMap(mapfiles,1,0,None,1,0)
    else:
        mapdata=readMap(mapfiles,0,1,None,1,0)
        
    nunmapped=0
    nbad=0 # always 0 in this case
    ntotal=0
    hasprefix = options.prefix is not None
    with open(datafile,mode='r') as fsh:
        for rawline in fsh.readlines():
            line = rawline.strip().rstrip('\n')
            if len(line)==0: continue
            if line[0]=='#':
                print(line)
                continue

            elems=line.split()
            if options.column >= len(elems):
                continue
            data = elems[options.column]
            # check for prefix
            addprefix = False
            if hasprefix and data.startswith(options.prefix):
                data=data[len(options.prefix):]
                addprefix = not remove
            ntotal=ntotal+1

            newline = line
            ismapped=False
            if data in mapdata:
                ismapped=True
                elems[options.column] = mapdata[data]
                if addprefix:
                    elems[options.column] = options.prefix+elems[options.column]
                    
                newline = ' '.join(elems)

            if not ismapped:
                nunmapped=nunmapped+1

            if options.keep or ismapped:
                print (newline)
            else:
                print (('%s ###UNMAPPED')%(newline))
                if options.forcefail:
                    print_error('unmapped id found')
                    return None,None,None

    return ntotal,nunmapped,nbad

def remap_todo( options ):
    ###datafile,mapfiles,swap,keepall,forcefail,splitat=None,minlen=3,select=0):
    return remap_column(options)

#datafile,mapfiles,swap,keepall,forcefail,0,splitat=None,minlen=3,select=0)
"""
    if swap:
        mapdata=readMap(mapfiles,1,0,None,1,0)
    else:
        mapdata=readMap(mapfiles,0,1,None,1,0)
        
    nunmapped=0
    nbad=0 # always 0 in this case
    ntotal=0    
    with open(datafile,mode='r') as fsh:
        #--------------------------------------------------
        # flag  data type         format
        #--------------------------------------------------
        #  0    fasta file        >[label] (newline) (sequence)
        #  1    cluster file      [clid] [label]
        #  2    pair wise file    [labelA] [labelB] [data]
        #  3    todo file         [label]

        for rawline in fsh.readlines():
            line = rawline.strip().rstrip('\n')
            if len(line)==0: continue
            if line[0]=='#': continue

            ntotal=ntotal+1

            newid=line
            ismapped=False
            if line in mapdata:
                newid=mapdata[line]
                ismapped=True

            if not ismapped:
                nunmapped=nunmapped+1

            if keepall or ismapped:
                print (newid)
            else:
                print (('%s ###UNMAPPED')%(newid))
                if forcefail:
                    print_error('unmapped id found')
                    return None,None,None

    return ntotal,nunmapped,nbad
"""

def parseMapFiles(mapfiles):
    rval={}
    keys=[]
    if mapfiles is None:
        return None
    for f in mapfiles:
        if not os.path.isfile(f):
            #print('warning: file is not found %s - skipping'%(f))
            continue
        bname=os.path.basename(f)
        pos = bname.find('.fs.maptxt')
        lbl = bname[:pos]
        # warning this assumes the last dot implies!!
        ####lbl='.'.join(lbl.split('.')[:-1])
        rval[lbl]=f
        keys.append(lbl)
    return rval,keys

# remap cluster, keys are not of the format <taxid>:<geneid>
def remap_cluster( options ):
    clidAppend = None if options.oglevel is None else 'at{}'.format(options.oglevel)
    return remap_column( options, 1, clidAppend )

def remap_cluster_OLD( options ):
    if (options.oglevel is None):
        return remap_column( options,1 )
    
    # gene id columns
    colKey=0
    colValue=1
    if options.swap:
        x=colValue
        colValue=colKey
        colKey=x

    # set default species separator
    specsep = options.taxsep
    if specsep is None or len(specsep)==0:
        specsep=':'

    mapfiles = getMapFiles( options )

    print('GOT MAPFILES {}'.format(mapfiles))
    
    # load as taxid map if requested
    specmap  = None
    fgenemap = None
    keys = []
    if options.maptaxid:
        # map files are taxid maps
        specmap=readMap( mapfiles, colKey, colValue, options.splitgene, options.splitminlen, options.splitselect )
    else:
        # read gene map file
        print('CALL parseMapFiles()')
        fgenemap,keys = parseMapFiles(mapfiles)

    # read clid map file
    # columns for key and value
    ogcolKey=0
    ogcolValue=2
    if options.ogswap:
        x=ogcolValue
        ogcolValue=ogcolKey
        ogcolKey=x


    ogmapdata = None
    if options.ogmap is not None:
        ogmapdata=readMap([options.ogmap],ogcolKey,ogcolValue)


    # read cluster and sort according to gene column
    nunmapped=0
    nbad=0
    data=[]
    stats={}
    with open(options.fname,mode='r') as fsh: 
        splitdata=[]
        # read all lines
        for rawline in fsh.readlines():
            line = rawline.strip().rstrip('\n')
            if len(line)==0: continue
            if line[0]=='#':
                if line.startswith('#STATS'):
                    elems=line[7:].split(',')
                    idstr=elems[0]
                    stats[clid] = idstr.split(':')[-1]
                
                continue

            # split line, all columns as of column 2 is in one element
            splitline=line.split()
            if len(splitline)>2:
                splitdata.append(splitline)
            else:
                nbad=nbad+1

        # sort according to second column - the geneid
        data=sorted(splitdata,key=lambda line: line[1])

    # quit if empty
    if len(data)==0:
        return 0,0,nbad

    # now loop over data and find maps
    currentid=''
    currentmap=None
    outdata=[]
    ntotal=len(data)
    nerr=0
    clidIsInt=False
    for elems in data:
        clid=elems[0]
        clidIsInt = clid.isdigit()
        fullid=elems[1]
        rest=' '.join(elems[2:])
        newid=fullid
        post=''
        if specmap is not None:
            spid=fullid.split(specsep)
            taxid=spid[0]
            geneid=spid[1]
            if taxid in specmap:
                newtaxid=specmap[taxid]
                newid = newtaxid+specsep+geneid
            else:
                nunmapped=nunmapped+1
                if options.keepall:
                    post='###UNMAPPED TAXID'
                    newid=fullid
        else:
            if len(keys)==1:
                if currentmap is None:
                    fmap = fgenemap[keys[0]]
                    currentmap=readMap( [ fmap ], colKey, colValue, options.splitgene,options.splitminlen,options.splitselect )
            else:
                taxid=fullid.split(specsep)[0]
                # check if new taxid
                if not currentid==taxid:
                    currentid=taxid
                    currentmap={}
                    if taxid in fgenemap:
                        fmap=fgenemap[taxid] # file name of gene map for taxid
                        ###                print('#read map for %s'%taxid)
                        currentmap=readMap( [ fmap ], colKey, colValue, options.splitgene,options.splitminlen,options.splitselect )
                    else:
                        print('#MISSING map for %s'%taxid)

            newid=None
            if fullid in currentmap:
                post=''
                newid=currentmap[fullid]
            else:
                nunmapped=nunmapped+1
                if options.keep:
                    post='###UNMAPPED GENEID'
                    newid=fullid

        newclid=clid
        if ogmapdata:
            newclid=None
            if clid in ogmapdata:
                newclid=ogmapdata[clid]
                
        elif options.oglevel is not None:
            newclid = '{}at{}'.format(clid,options.oglevel)
            
        if newclid is None:
            nunmapped=nunmapped+1
            newclid='%s'%clid
            post='###UNMAPPED CLID'

        if nunmapped>0 and options.forcefail:
            print_error('unmapped genes and/or cluster ids found')
            return None,None,None
        
        if newid is not None:
            outstr='%s %s %s %s'%(newclid,newid,rest,post)
            outdata.append([clid,outstr])

    # output sorted according to cluster ID
    lastid=""
    if clidIsInt:
        for D in sorted(outdata,key=lambda line: int(line[0])):
            clid=D[0]
            if not clid==lastid:
                if clid in stats:
                    print(stats[clid])
                lastid=clid
            print(D[1])
    else:
        for D in sorted(outdata,key=lambda line: line[0]):
            clid=D[0]
            if not clid==lastid:
                if clid in stats:
                    print(stats[clid])
                lastid=clid

            print(D[1])
    
    return ntotal,nunmapped,nbad


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

# code to handle escaped characters in arg parser input (outsep)
import codecs
def unescaped_str(arg_str):
    return codecs.decode(str(arg_str), 'unicode_escape')

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

parser.add_argument('-f', '--file',    dest='fname',     help='data file',    metavar='FILE', required=True)
parser.add_argument('-m', '--map',     dest='maps',      help='gene (or taxid if taxsep)  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 --mapfile)',  metavar='FILE', required=False, default=None)
parser.add_argument('-d', '--mapdir',  dest='mapdir',    help='directory where map files are found (with --labels)',  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('-M', '--ogmap',   dest='ogmap',     help='cluster id map file <string,numerical>', metavar='FILE', required=False, default=None)
parser.add_argument('-S', '--ogswap',  dest='ogswap',    help='swap mapping order (cluster ids)', action='store_true', default=False)
parser.add_argument('-O', '--oglevel', dest='oglevel',   help='map cluster ids to <clid>at<level> where label can be the taxid of the level', default=None)

parser.add_argument('-K', '--keepmap', dest='keepmap',   help='add the original label to fasta header', 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', '--type',    dest='datatype',  help='data type (0 - fasta, 1 - clusters, 2 - pairwise data, 3 - todo file, 4 - by column, 5 - automatic)', default=5, type=int)
parser.add_argument('-c', '--column',  dest='column',    help='column containing the label to be mapped (starts at 0)', default=None, type=int)
parser.add_argument('-P', '--prefix',  dest='prefix',    help='taxid prefix - removed when mapping and added afterwards', 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('-o', '--outsep',  dest='outsep',    help='output column separator', type=unescaped_str, required=False, default=' ')
parser.add_argument('-i', '--maptaxid',    dest='maptaxid',      help='map taxid - requires --taxsep', action='store_true', default=False)

parser.add_argument('-a', '--splitgene',   dest='splitgene',    help='split gene id wrt this character (\'|\' is often used in gene ids)', required=False, default=None)
parser.add_argument('-l', '--splitselect', dest='splitselect',  help='which one to select from splitgene option (index starting at 0)', default=1, type=int)
parser.add_argument('-e', '--splitminlen',   dest='splitminlen',help='minimum length of gene id', required=False, default=1)

options = parser.parse_args()

datatype=options.datatype
txprefix=options.prefix

if options.labels is not None and options.mapdir is None:
    print('error: --mapdir is required by --labels',file=sys.stderr)
    sys.exit(255)
    
if options.column is not None:
    if options.ogmap is not None:
        print('error: both --ogmap and --column are set',file=sys.stderr)
        sys.exit(255)
    if options.datatype!=4 and options.datatype!=5:
        print('error: --column set with incompatible data type (must be 4 or 5)',file=sys.stderr)
        sys,exit(255)
        
if options.datatype==4 or options.datatype==5:
    if options.column is None:
        print('error: for data type 4 or 5, option --column must be given',file=sys.stderr)
        sys,exit(255)

if (options.ogmap is not None) and (options.oglevel is not None):
    print('error: only one of --ogmap and --oglevel may be given',file=sys.stderr)
    sys,exit(255)
    
# force data type to cluster if og map is given
if (options.ogmap is not None) or (options.oglevel is not None):
    datatype=1
    txprefix=None

# force data type to column if column is given
if options.column is not None:
    datatype = 4

# guess data type (0-3, not 4)
if datatype == 5:
    txprefix=None
    datatype = guessfmt( options.fname )
    if datatype is None:
        print('error: unable to identify input data',file=sys.stderr)
        sys.exit(255)
        
datastr=['fasta','cluster','pairs','todo']
print('# Got data type: %s'%datastr[datatype])
    
# if data is cluster, require either maps or ogmaps, otherwise just maps
if datatype == 1:
    if (options.maps is None) and (options.ogmap is None) and (options.mapfile is None) and (options.labels is None) and (options.oglevel is None):
        print('error: remap of this file requires one of the options --labels, --mapfile, --map, --ogmap or --oglevel',file=sys.stderr)
        sys.exit(255)
else:
    if options.maps is None and options.mapfile is None and options.labels is None:
        print('error: remap of this file requires option --labels, --map or --mapfile',file=sys.stderr)
        sys.exit(255)

# if data is not TODO file, require taxsep if maptaxid
if datatype != 3:    
    if options.maptaxid and (options.taxsep is None):
        print('error: --maptaxid requires option --taxsep',file=sys.stderr)
        sys.exit(255)

# open fasta file and read data
ntotal=0
nunmapped=0
nbad=0
if datatype == 0:
    ntotal,nunmapped,nbad=remap_fasta( options )
    #options.fname, options.maptaxid, options.taxsep, options.maps, options.swap, options.keep, options.forcefail, options.splitgene, options.splitminlen, options.splitselect )
elif datatype == 1:
    ntotal,nunmapped,nbad=remap_cluster( options )
    #options.fname, options.maptaxid, options.taxsep, options.maps, options.ogmap, options.swap, options.ogswap, options.keep, options.forcefail, options.splitgene, options.splitminlen, options.splitselect )
elif datatype == 2:
    ntotal,nunmapped,nbad=remap_pairwise( options )
    #options.fname, options.maptaxid, options.taxsep, options.maps, options.swap, options.keep, options.forcefail, options.splitgene, options.splitminlen, options.splitselect )
elif datatype == 3:
    ntotal,nunmapped,nbad=remap_todo( options.fname, options.maps, options.swap, options.keep, options.forcefail )
elif datatype == 4:
    if txprefix is None:
        ntotal,nunmapped,nbad=remap_column( options )
    else:
        ntotal,nunmapped,nbad=remap_column_pref( options )
else:
    print('error: unknown data type',file=sys.stderr)

if ntotal is None:
    sys.exit(255)

if nunmapped>0 or nbad>0:
    if options.forcefail:
        print(('# N(total)     = %d')%ntotal,file=sys.stderr)
        print(('# N(unmapped)  = %d')%nunmapped,file=sys.stderr)
        print(('# N(bad)       = %d')%nbad,file=sys.stderr)
    else:
        print(('# N(total)     = %d')%ntotal)
        print(('# N(unmapped)  = %d')%nunmapped)
        print(('# N(bad)       = %d')%nbad)
