# Copyright 2009 by Tiago Antao <tiagoantao@gmail.com>.  All rights reserved.
# This code is part of the Biopython distribution and governed by its
# license.  Please see the LICENSE file that should have been included
# as part of this package.



"""
This module allows to control GenePop.

"""

import os
import re
import shutil
import subprocess
import sys
import tempfile

from Bio.Application import AbstractCommandline, _Argument, _Option

def _gp_float(tok):
    """Gets a float from a token, if it fails, returns the string.
    """
    try:
        return float(tok)
    except ValueError:
        return str(tok)

def _gp_int(tok):
    """Gets a int from a token, if it fails, returns the string.
    """
    try:
        return int(tok)
    except ValueError:
        return str(tok)
    
    
def _read_allele_freq_table(f):
    l = f.readline()
    while l.find(" --")==-1:
        if l == "":
            raise StopIteration
        l = f.readline()
    alleles = filter(lambda x: x != '', f.readline().rstrip().split(" "))
    alleles = map(lambda x: _gp_int(x), alleles)
    l = f.readline().rstrip()
    table = []
    while l != "":
        line = filter(lambda x: x != '', l.split(" "))
        try:
            table.append(
                (line[0],
                map(lambda x: _gp_float(x), line[1:-1]),
                _gp_int(line[-1])))
        except ValueError:
            table.append(
                (line[0],
                [None] * len(alleles),
                0))
        l = f.readline().rstrip()
    return alleles, table

def _read_table(f, funs):
    table = []
    l = f.readline().rstrip()
    while l.find("---")==-1:
        l = f.readline().rstrip()
    l = f.readline().rstrip()
    while l.find("===")==-1 and l.find("---")==-1 and l != "":
        toks = filter(lambda x: x != "", l.split(" ")) 
        line = []
        for i in range(len(toks)):
            try:
                line.append(funs[i](toks[i]))
            except ValueError:
                line.append(toks[i])  # Could not cast
        table.append(tuple(line))
        l = f.readline().rstrip()
    return table

def _read_triangle_matrix(f):
    matrix = []
    l = f.readline().rstrip()
    while l != "":
        matrix.append(
            map(lambda x: _gp_float(x),
                filter(lambda y: y != "", l.split(" "))))
        l = f.readline().rstrip()
    return matrix

def _read_headed_triangle_matrix(f):
    matrix = {}
    header = f.readline().rstrip()
    if header.find("---")>-1 or header.find("===")>-1:
        header = f.readline().rstrip()
    nlines = len(filter(lambda x:x != '', header.split(' '))) - 1
    for line_pop in range(nlines):
        l = f.readline().rstrip()
        vals = filter(lambda x:x != '', l.split(' ')[1:])
        clean_vals = []
        for val in vals:
            try:
                clean_vals.append(_gp_float(val))
            except ValueError:
                clean_vals.append(None)
        for col_pop in range(len(clean_vals)):
            matrix[(line_pop+1, col_pop)] = clean_vals[col_pop]
    return matrix

def _hw_func(stream, is_locus, has_fisher = False): 
    l = stream.readline()
    if is_locus:
        hook = "Locus "
    else:
        hook = " Pop : "
    while l != "":
        if l.startswith(hook):
            stream.readline()
            stream.readline()
            stream.readline()
            table = _read_table(stream,[str,_gp_float,_gp_float,_gp_float,_gp_float,_gp_int,str])
            #loci might mean pop if hook="Locus "
            loci = {}
            for entry in table:
                if len(entry) < 3:
                    loci[entry[0]] = None
                else:
                    locus, p, se, fis_wc, fis_rh, steps = entry[:-1]
                    if se == "-": se = None
                    loci[locus] = p, se, fis_wc, fis_rh, steps
            return loci
        l = stream.readline()
    #self.done = True
    raise StopIteration

class _FileIterator:
    """Iterator which crawls over a stream of lines with a function.

       The generator function is expected to yield a tuple, while
       consuming input
    """
    def __init__(self, func, stream, fname):
        self.func = func
        self.stream = stream
        self.fname = fname
        self.done = False

    def __iter__(self):
        if self.done:
            self.done = True
            raise StopIteration
        return self

    def next(self):
        return self.func(self)

    def __del__(self):
        self.stream.close()
        os.remove(self.fname)

class _GenePopCommandline(AbstractCommandline):
    """ Command Line Wrapper for GenePop.
    """
    def __init__(self, genepop_dir=None, cmd='Genepop', **kwargs):
        self.parameters = [
                _Argument(["command"],
                    ["INTEGER(.INTEGER)*"],
                    None,
                    True,
                    "GenePop option to be called"),
                _Argument(["mode"],
                    ["Dont touch this"],
                    None,
                    True,
                    "Should allways be batch"),
                _Argument(["input"],
                    ["input"],
                    None,
                    True,
                    "Input file"),
                _Argument(["Dememorization"],
                    ["input"],
                    None,
                    False,
                    "Dememorization step"),
                _Argument(["BatchNumber"],
                    ["input"],
                    None,
                    False,
                    "Number of MCMC batches"),
                _Argument(["BatchLength"],
                    ["input"],
                    None,
                    False,
                    "Length of MCMC chains"),
                _Argument(["HWtests"],
                    ["input"],
                    None,
                    False,
                    "Enumeration or MCMC"),
                _Argument(["IsolBDstatistic"],
                    ["input"],
                    None,
                    False,
                    "IBD statistic (a or e)"),
                _Argument(["MinimalDistance"],
                    ["input"],
                    None,
                    False,
                    "Minimal IBD distance"),
                _Argument(["GeographicScale"],
                    ["input"],
                    None,
                    False,
                    "Log or Linear"),
        ]
        AbstractCommandline.__init__(self, cmd, **kwargs)
        self.set_parameter("mode", "Mode=Batch")

    def set_menu(self, option_list):
        """Sets the menu option.

        Example set_menu([6,1]) = get all F statistics (menu 6.1)
        """
        self.set_parameter("command", "MenuOptions="+
                ".".join(map(lambda x:str(x),option_list)))

    def set_input(self, fname):
        """Sets the input file name.
        """
        self.set_parameter("input", "InputFile="+fname)

class GenePopController:
    def __init__(self, genepop_dir = None):
        """Initializes the controller.
        
        genepop_dir is the directory where GenePop is.

        The binary should be called Genepop (capital G)
        
        """
        self.controller = _GenePopCommandline(genepop_dir)

    def _remove_garbage(self, fname_out):
        try:
            if fname_out != None: os.remove(fname_out)
        except OSError:
            pass # safe
        try:
            os.remove("genepop.txt")
        except OSError:
            pass # safe
        try:
            os.remove("fichier.in")
        except OSError:
            pass # safe
        try:
            os.remove("cmdline.txt")
        except OSError:
            pass # safe

    def _get_opts(self, dememorization, batches, iterations, enum_test=None):
        opts = {}
        opts["Dememorization"]=dememorization
        opts["BatchNumber"]=batches
        opts["BatchLength"]=iterations
        if enum_test != None:
            if enum_test == True:
                opts["HWtests"]="Enumeration"
            else:
                opts["HWtests"]="MCMC"
        return opts

    def _run_genepop(self, extensions, option, fname, opts={}):
        for extension in extensions:
            self._remove_garbage(fname + extension)
        self.controller.set_menu(option)
        self.controller.set_input(fname)
        for opt in opts:
            self.controller.set_parameter(opt, opt+"="+str(opts[opt]))
        child = subprocess.Popen(str(self.controller),
                             stdin=subprocess.PIPE,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             shell=(sys.platform!="win32"))
        r_out, e_out = child.communicate()
        # capture error code:
        ret = child.returncode

        self._remove_garbage(None)
        if ret != 0: raise IOError("GenePop not found")
        return


    def _test_pop_hz_both(self, fname, type, ext, enum_test = True,
        dememorization = 10000, batches = 20, iterations = 5000):
        """Hardy-Weinberg test for heterozygote deficiency/excess.

           Returns a population iterator containg
               A dictionary[locus]=(P-val, SE, Fis-WC, Fis-RH, steps)
                 Some loci have a None if the info is not available
                 SE might be none (for enumerations)
        """
        opts = self._get_opts(dememorization, batches, iterations, enum_test)
        self._run_genepop([ext], [1, type], fname, opts)
        f = open(fname + ext)
        def hw_func(self):
            return _hw_func(self.stream, False)
        return _FileIterator(hw_func, f, fname + ext)

    def _test_global_hz_both(self, fname, type, ext, enum_test = True,
        dememorization = 10000, batches = 20, iterations = 5000):
        """Global Hardy-Weinberg test for heterozygote deficiency/excess.

           Returns a triple with:
             A list per population containg
               (pop_name, P-val, SE, switches)
                 Some pops have a None if the info is not available
                 SE might be none (for enumerations)
             A list per loci containg
               (locus_name, P-val, SE, switches)
                 Some loci have a None if the info is not available
                 SE might be none (for enumerations)
             Overall results (P-val, SE, switches)

        """
        opts = self._get_opts(dememorization, batches, iterations, enum_test)
        self._run_genepop([ext], [1, type], fname, opts)
        def hw_pop_func(self):
            return _read_table(self.stream, [str, _gp_float, _gp_float, _gp_float])
        f1 = open(fname + ext)
        l = f1.readline()
        while l.find("by population") == -1:
            l = f1.readline()
        pop_p = _read_table(f1, [str, _gp_float, _gp_float, _gp_float])
        f2 = open(fname + ext)
        l = f2.readline()
        while l.find("by locus") == -1:
            l = f2.readline()
        loc_p = _read_table(f2, [str, _gp_float, _gp_float, _gp_float])
        f = open(fname + ext)
        l = f.readline()
        while l.find("all locus") == -1:
            l = f.readline()
        f.readline()
        f.readline()
        f.readline()
        f.readline()
        l = f.readline().rstrip()
        p, se, switches = tuple(map(lambda x: _gp_float(x),
            filter(lambda y: y != "",l.split(" "))))
        f.close()
        return pop_p, loc_p, (p, se, switches)

    #1.1
    def test_pop_hz_deficiency(self, fname, enum_test = True,
        dememorization = 10000, batches = 20, iterations = 5000):
        """Hardy-Weinberg test for heterozygote deficiency.

           Returns a population iterator containg
               A dictionary[locus]=(P-val, SE, Fis-WC, Fis-RH, steps)
                 Some loci have a None if the info is not available
                 SE might be none (for enumerations)
        """
        return self._test_pop_hz_both(fname, 1, ".D", enum_test,
            dememorization, batches, iterations)

    #1.2
    def test_pop_hz_excess(self, fname, enum_test = True,
        dememorization = 10000, batches = 20, iterations = 5000):
        """Hardy-Weinberg test for heterozygote deficiency.

           Returns a population iterator containg
               A dictionary[locus]=(P-val, SE, Fis-WC, Fis-RH, steps)
                 Some loci have a None if the info is not available
                 SE might be none (for enumerations)
        """
        return self._test_pop_hz_both(fname, 2, ".E", enum_test,
            dememorization, batches, iterations)

    #1.3 P file
    def test_pop_hz_prob(self, fname, ext, enum_test = False,
        dememorization = 10000, batches = 20, iterations = 5000):
        """Hardy-Weinberg test based on probability.

           Returns 2 iterators and a final tuple:

          1. Returns a loci iterator containing
               b. A dictionary[pop_pos]=(P-val, SE, Fis-WC, Fis-RH, steps)
                 Some pops have a None if the info is not available
                 SE might be none (for enumerations)
               c. Result of Fisher's test (Chi2, deg freedom, prob)
          2. Returns a population iterator containg
               a. A dictionary[locus]=(P-val, SE, Fis-WC, Fis-RH, steps)
                 Some loci have a None if the info is not available
                 SE might be none (for enumerations)
               b. Result of Fisher's test (Chi2, deg freedom, prob)
          3. (Chi2, deg freedom, prob)
        """
        opts = self._get_opts(dememorization, batches, iterations, enum_test)
        self._run_genepop([ext], [1, 3], fname, opts)
        def hw_prob_loci_func(self): 
            return  _hw_func(self.stream, True, True)
        def hw_prob_pop_func(self): 
            return _hw_func(self.stream, False, True)
        shutil.copyfile(fname+".P", fname+".P2")
        f1 = open(fname + ".P")
        f2 = open(fname + ".P2")
        return _FileIterator(hw_prob_loci_func, f1, fname + ".P"), _FileIterator(hw_prob_pop_func, f2, fname + ".P2")

    #1.4
    def test_global_hz_deficiency(self, fname, enum_test = True,
        dememorization = 10000, batches = 20, iterations = 5000):
        """Global Hardy-Weinberg test for heterozygote deficiency.

           Returns a triple with:
             An list per population containg
               (pop_name, P-val, SE, switches)
                 Some pops have a None if the info is not available
                 SE might be none (for enumerations)
             An list per loci containg
               (locus_name, P-val, SE, switches)
                 Some loci have a None if the info is not available
                 SE might be none (for enumerations)
             Overall results (P-val, SE, switches)
        """
        return self._test_global_hz_both(fname, 4, ".DG", enum_test,
            dememorization, batches, iterations)


    #1.5
    def test_global_hz_excess(self, fname, enum_test = True,
        dememorization = 10000, batches = 20, iterations = 5000):
        """Global Hardy-Weinberg test for heterozygote excess.

           Returns a triple with:
             An list per population containg
               (pop_name, P-val, SE, switches)
                 Some pops have a None if the info is not available
                 SE might be none (for enumerations)
             An list per loci containg
               (locus_name, P-val, SE, switches)
                 Some loci have a None if the info is not available
                 SE might be none (for enumerations)
             Overall results (P-val, SE, switches)
        """
        return self._test_global_hz_both(fname, 5, ".EG", enum_test,
            dememorization, batches, iterations)

    #2.1
    def test_ld(self, fname,
        dememorization = 10000, batches = 20, iterations = 5000):
        opts = self._get_opts(dememorization, batches, iterations)
        self._run_genepop([".DIS"], [2, 1], fname, opts)
        def ld_pop_func(self):
            current_pop = None
            l = self.stream.readline().rstrip()
            if l == "":
                self.done = True
                raise StopIteration
            toks = filter(lambda x: x != "", l.split(" "))
            pop, locus1, locus2 = toks[0], toks[1], toks[2]
            if not hasattr(self, "start_locus1"):
                start_locus1, start_locus2 = locus1, locus2
                current_pop = -1
            if locus1 == start_locus1 and locus2 == start_locus2:
                current_pop += 1
            if toks[3] == "No":
                return current_pop, pop, (locus1, locus2), None
            p, se, switches = _gp_float(toks[3]), _gp_float(toks[4]), _gp_int(toks[5])
            return current_pop, pop, (locus1, locus2), (p, se, switches)
        def ld_func(self):
            l = self.stream.readline().rstrip()
            if l == "":
                self.done = True
                raise StopIteration
            toks = filter(lambda x: x != "", l.split(" "))
            locus1, locus2 = toks[0], toks[2]
            try:
                chi2, df, p = _gp_float(toks[3]), _gp_int(toks[4]), _gp_float(toks[5])
            except ValueError:
                return (locus1, locus2), None
            return (locus1, locus2), (chi2, df, p)
        f1 = open(fname + ".DIS")
        l = f1.readline()
        while l.find("----")==-1:
            l = f1.readline()
        shutil.copyfile(fname + ".DIS", fname + ".DI2")
        f2 = open(fname + ".DI2")
        l = f2.readline()
        while l.find("Locus pair")==-1:
            l = f2.readline()
        while l.find("----")==-1:
            l = f2.readline()
        return _FileIterator(ld_pop_func, f1, fname+".DIS"), _FileIterator(ld_func, f2, fname + ".DI2")

    #2.2
    def create_contingency_tables(self, fname):
        raise NotImplementedError

    #3.1 PR/GE files
    def test_genic_diff_all(self, fname,
        dememorization = 10000, batches = 20, iterations = 5000):
        raise NotImplementedError

    #3.2 PR2/GE2 files
    def test_genic_diff_pair(self, fname,
        dememorization = 10000, batches = 20, iterations = 5000):
        raise NotImplementedError

    #3.3 G files
    def test_genotypic_diff_all(self, fname,
        dememorization = 10000, batches = 20, iterations = 5000):
        raise NotImplementedError

    #3.4 2G2 files
    def test_genotypic_diff_pair(self, fname,
        dememorization = 10000, batches = 20, iterations = 5000):
        raise NotImplementedError

    #4
    def estimate_nm(self, fname):
        self._run_genepop(["PRI"], [4], fname)
        f = open(fname + ".PRI")
        lines = f.readlines() # Small file, it is ok
        f.close()
        for line in lines:
            m = re.search("Mean sample size: ([.0-9]+)", line)
            if m != None:
                mean_sample_size = _gp_float(m.group(1))
            m = re.search("Mean frequency of private alleles p\(1\)= ([.0-9]+)", line)
            if m != None:
                mean_priv_alleles = _gp_float(m.group(1))
            m = re.search("N=10: ([.0-9]+)", line)
            if m != None:
                mig10 = _gp_float(m.group(1))
            m = re.search("N=25: ([.0-9]+)", line)
            if m != None:
                 mig25 = _gp_float(m.group(1))
            m = re.search("N=50: ([.0-9]+)", line)
            if m != None:
                 mig50 = _gp_float(m.group(1))
            m = re.search("for size= ([.0-9]+)", line)
            if m != None:
                 mig_corrected = _gp_float(m.group(1))
        os.remove(fname + ".PRI")
        return mean_sample_size, mean_priv_alleles, mig10, mig25, mig50, mig_corrected

    #5.1
    def calc_allele_genotype_freqs(self, fname):
        """Calculates allele and genotype frequencies per locus and per sample.
        
        Parameters:
        fname - file name

        Returns tuple with 2 elements:
        Population iterator with
            population name
            Locus dictionary with key = locus name and content tuple as
              Genotype List with
                (Allele1, Allele2, observed, expected)
              (expected homozygotes, observed hm, 
              expected heterozygotes, observed ht)
              Allele frequency/Fis dictionary with allele as key and
                (count, frequency, Fis Weir & Cockerham)
              Totals as a pair
                count
                Fis Weir & Cockerham,
                Fis Robertson & Hill
        Locus iterator with
            Locus name
            allele list
            Population list with a triple
               population name
               list of allele frequencies in the same order as allele list above
               number of genes

        
        Will create a file called fname.INF
        """
        self._run_genepop(["INF"], [5,1], fname)
        #First pass, general information
        #num_loci = None
        #num_pops = None
        #f = open(fname + ".INF")
        #l = f.readline()
        #while (num_loci == None or num_pops == None) and l != '':
        #    m = re.search("Number of populations detected : ([0-9+])", l)
        #    if m != None:
        #        num_pops = _gp_int(m.group(1))
        #    m = re.search("Number of loci detected        : ([0-9+])", l)
        #    if m != None:
        #        num_loci = _gp_int(m.group(1))
        #    l = f.readline()
        #f.close()
        def pop_parser(self):
            if hasattr(self, "old_line"):
                l = self.old_line
                del self.old_line
            else:
                l = self.stream.readline()
            loci_content = {}
            while l<>'':
                l = l.rstrip()
                if l.find("Tables of allelic frequencies for each locus")>-1:
                    return self.curr_pop, loci_content
                match = re.match(".*Pop: (.+) Locus: (.+)", l)
                if match != None:
                    pop = match.group(1)
                    locus = match.group(2)
                    if not hasattr(self, "first_locus"):
                        self.first_locus = locus
                    if hasattr(self, "curr_pop"):
                        if self.first_locus == locus:
                            old_pop = self.curr_pop
                            #self.curr_pop = pop
                            self.old_line = l
                            del self.first_locus
                            del self.curr_pop
                            return old_pop, loci_content
                    self.curr_pop = pop
                else:
                    l = self.stream.readline()
                    continue
                geno_list = []
                l = self.stream.readline()
                if l.find("No data")>-1: continue

                while l.find("Genotypes  Obs.")==-1:
                    l = self.stream.readline()

                while l<>"\n":
                    m2 = re.match(" +([0-9]+) , ([0-9]+) *([0-9]+) *(.+)",l)
                    if m2 != None:
                        geno_list.append((_gp_int(m2.group(1)), _gp_int(m2.group(2)),
                            _gp_int(m2.group(3)), _gp_float(m2.group(4))))
                    else:
                        l = self.stream.readline()
                        continue
                    l = self.stream.readline()

                while l.find("Expected number of ho")==-1:
                    l = self.stream.readline()
                expHo =  _gp_float(l[38:])
                l = self.stream.readline()
                obsHo =  _gp_int(l[38:])
                l = self.stream.readline()
                expHe =  _gp_float(l[38:])
                l = self.stream.readline()
                obsHe =  _gp_int(l[38:])
                l = self.stream.readline()

                while l.find("Sample count")==-1:
                    l = self.stream.readline()
                l = self.stream.readline()
                freq_fis={}
                overall_fis = None
                while l.find("----")==-1:
                    vals = filter(lambda x: x!='',
                            l.rstrip().split(' '))
                    if vals[0]=="Tot":
                        overall_fis = _gp_int(vals[1]), \
                                _gp_float(vals[2]), _gp_float(vals[3])
                    else:
                        freq_fis[_gp_int(vals[0])] = _gp_int(vals[1]), \
                                _gp_float(vals[2]), _gp_float(vals[3])
                    l = self.stream.readline()
                loci_content[locus] = geno_list, \
                        (expHo, obsHo, expHe, obsHe), \
                        freq_fis, overall_fis
            self.done = True
            raise StopIteration
        def locus_parser(self):
            l = self.stream.readline()
            while l != "":
                l = l.rstrip()
                match = re.match(" Locus: (.+)", l)
                if match != None:
                    locus = match.group(1)
                    alleles, table = _read_allele_freq_table(self.stream)
                    return locus, alleles, table
                l = self.stream.readline()
            self.done = True
            raise StopIteration

        popf = open(fname + ".INF")
        shutil.copyfile(fname + ".INF", fname + ".IN2")
        locf = open(fname + ".IN2")
        pop_iter = _FileIterator(pop_parser, popf, fname + ".INF")
        locus_iter = _FileIterator(locus_parser, locf, fname + ".IN2")
        return (pop_iter, locus_iter)

    def _calc_diversities_fis(self, fname, ext):
        self._run_genepop([ext], [5,2], fname)
        f = open(fname + ext)
        l = f.readline()
        while l<>"":
            l = l.rstrip()
            if l.startswith("Statistics per sample over all loci with at least two individuals typed"):
                avg_fis = _read_table(f, [str, _gp_float, _gp_float, _gp_float])
                avg_Qintra = _read_table(f, [str, _gp_float])
            l = f.readline()
        f.close()
        def fis_func(self): 
            l = self.stream.readline()
            while l != "":
                l = l.rstrip()
                m = re.search("Locus: (.+)", l)
                if m != None:
                    locus = m.group(1)
                    self.stream.readline()
                    if self.stream.readline().find("No complete")>-1: return locus, None
                    self.stream.readline()
                    fis_table = _read_table(self.stream, [str, _gp_float, _gp_float, _gp_float])
                    self.stream.readline()
                    avg_qinter, avg_fis = tuple(map (lambda x: _gp_float(x),
                        filter(lambda y:y != "", self.stream.readline().split(" "))))
                    return locus, fis_table, avg_qinter, avg_fis
                l = self.stream.readline()
            self.done = True
            raise StopIteration
        dvf = open(fname + ext)
        return _FileIterator(fis_func, dvf, fname + ext), avg_fis, avg_Qintra

    #5.2
    def calc_diversities_fis_with_identity(self, fname):
        return self._calc_diversities_fis(fname, ".DIV")

    #5.3
    def calc_diversities_fis_with_size(self, fname):
        raise NotImplementedError

    #6.1 Less genotype frequencies
    def calc_fst_all(self, fname):
        """Executes GenePop and gets Fst/Fis/Fit (all populations)
        
        Parameters:
        fname - file name

        Returns:
        (multiLocusFis, multiLocusFst, multiLocus Fit),
        Iterator of tuples
          (Locus name, Fis, Fst, Fit, Qintra, Qinter)

        Will create a file called fname.FST .

        This does not return the genotype frequencies.
        
        """
        self._run_genepop([".FST"], [6,1], fname)
        f = open(fname + ".FST")
        l = f.readline()
        while l<>'':
            if l.startswith('           All:'):
                toks=filter(lambda x:x!="", l.rstrip().split(' '))
                try:
                    allFis = _gp_float(toks[1])
                except ValueError:
                    allFis = None
                try:
                    allFst = _gp_float(toks[2])
                except ValueError:
                    allFst = None
                try:
                    allFit = _gp_float(toks[3])
                except ValueError:
                    allFit = None
            l = f.readline()
        f.close()
        f = open(fname + ".FST")
        def proc(self): 
            if hasattr(self, "last_line"):
                l = self.last_line
                del self.last_line
            else:
                l = self.stream.readline()
            locus = None
            fis = None
            fst = None
            fit = None
            qintra = None
            qinter = None
            while l<>'':
                l = l.rstrip()
                if l.startswith('  Locus:'):
                    if locus != None:
                        self.last_line = l
                        return locus, fis, fst, fit, qintra, qinter
                    else:
                        locus = l.split(':')[1].lstrip()
                elif l.startswith('Fis^='):
                    fis = _gp_float(l.split(' ')[1])
                elif l.startswith('Fst^='):
                    fst = _gp_float(l.split(' ')[1])
                elif l.startswith('Fit^='):
                    fit = _gp_float(l.split(' ')[1])
                elif l.startswith('1-Qintra^='):
                    qintra = _gp_float(l.split(' ')[1])
                elif l.startswith('1-Qinter^='):
                    qinter = _gp_float(l.split(' ')[1])
                    return locus, fis, fst, fit, qintra, qinter
                l = self.stream.readline()
            if locus != None:
                return locus, fis, fst, fit, qintra, qinter
            self.stream.close()
            self.done = True
            raise StopIteration
        return (allFis, allFst, allFit), _FileIterator(proc , f, fname + ".FST")

    #6.2
    def calc_fst_pair(self, fname):
        self._run_genepop([".ST2", ".MIG"], [6,2], fname)
        f = open(fname + ".ST2")
        l = f.readline()
        while l<>"":
            l = l.rstrip()
            if l.startswith("Estimates for all loci"):
                avg_fst = _read_headed_triangle_matrix(f)
            l = f.readline()
        f.close()
        def loci_func(self): 
            l = self.stream.readline()
            while l != "":
                l = l.rstrip()
                m = re.search(" Locus: (.+)", l)
                if m != None:
                    locus = m.group(1)
                    matrix = _read_headed_triangle_matrix(self.stream)
                    return locus, matrix
                l = self.stream.readline()
            self.done = True
            raise StopIteration
        stf = open(fname + ".ST2")
        os.remove(fname + ".MIG")
        return _FileIterator(loci_func, stf, fname + ".ST2"), avg_fst

    #6.3
    def calc_rho_all(self, fname):
        raise NotImplementedError

    #6.4
    def calc_rho_pair(self, fname):
        raise NotImplementedError

    def _calc_ibd(self, fname, sub, stat="a", scale="Log", min_dist=0.00001):
        """Calculates isolation by distance statistics
        """
        self._run_genepop([".GRA", ".MIG", ".ISO"], [6,sub],
            fname, opts = {
            "MinimalDistance" : min_dist,
            "GeographicScale" : scale,
            "IsolBDstatistic" : stat,
            })
        f = open(fname + ".ISO")
        f.readline()
        f.readline()
        f.readline()
        f.readline()
        estimate = _read_triangle_matrix(f)
        f.readline()
        f.readline()
        distance = _read_triangle_matrix(f)
        f.readline()
        match = re.match("a = (.+), b = (.+)", f.readline().rstrip())
        a = _gp_float(match.group(1))
        b = _gp_float(match.group(2))
        f.readline()
        f.readline()
        match = re.match(" b=(.+)", f.readline().rstrip())
        bb = _gp_float(match.group(1))
        match = re.match(".*\[(.+)  ;  (.+)\]", f.readline().rstrip())
        bblow = _gp_float(match.group(1))
        bbhigh = _gp_float(match.group(2))
        f.close()
        os.remove(fname + ".MIG")
        os.remove(fname + ".GRA")
        os.remove(fname + ".ISO")
        return estimate, distance, (a, b), (bb, bblow, bbhigh)

    #6.5
    def calc_ibd_diplo(self, fname, stat="a", scale="Log", min_dist=0.00001):
        """Calculates isolation by distance statistics for diploid data.

           See _calc_ibd for parameter details.
           Note that each pop can only have a single individual and
           the individual name has to be the sample coordinates.
        """
        return self._calc_ibd(fname, 5, stat, scale, min_dist)

    #6.6
    def calc_ibd_haplo(self, fname, stat="a", scale="Log", min_dist=0.00001):
        """Calculates isolation by distance statistics for haploid data.

           See _calc_ibd for parameter details.
           Note that each pop can only have a single individual and
           the individual name has to be the sample coordinates.
        """
        return self._calc_ibd(fname, 6, stat, scale, min_dist)


