#    SnapPea.py
#
#    The Python interface to the SnapPea kernel consists of two parts.
#
#        SnapPea.py (this file) defines a set of objects (Triangulation,
#            AbelianGroup, etc.) purely in Python.
#
#        SnapPeaC.c (in a different file) implements SnapPea.py's methods
#            as a set of wrappers to the standard SnapPea kernel functions,
#            which are written in C.

#  Changes by MC and NMD in 2002/8 are Triangulation.__init__ and
#  .get_gluing_data  and at the end of the file.  Note that __all__ is set
#  at the very end, which controls what happens when you do "from
#  SnapPea import *"

import SnapPeaC
import os
import string
import types
import tempfile

try:
    import plink
except:
    pass

False = 0
True  = 1

#  Two global functions added by MC 2002/9 to support slicing.
#
#  Python2.2 is inconsistent about how it normalizes slices.
def normalize_slice(s, length):
    if s == None:
        return slice(0, length, 1) 
    start, stop, step = s.start, s.stop, s.step
    if not s.stop or s.stop > length:
        stop = length
    elif s.stop < 0:
        stop = s.stop + length
    else:
        stop = s.stop
    if not s.start:
        start = 0
    elif s.start < 0:
        start = s.start + length
    else:
        start = s.start
    if s.step == None:
        step = 1
    else:
        step = s.step
    return slice(start, stop, step)

def slice_slice(old, new):
    start = old.start + new.start*old.step
    stop = old.start + new.stop*old.step
    if new.step:
        step = old.step*new.step
    else:
        step = old.step
    return slice(start, stop, step)

#    Keep track of when memory is released.
def VerifyMyMallocUsage():
    return SnapPeaC.verify_my_malloc_usage()


# Below class changed 2002/8/20 by NMD so that call to
# SnapPeaC.get_cusped_census_manifold passes correct
# path which is deduced from the "package" manifolds.

import manifolds

class Triangulation:
    """
    Python class representing a SnapPea triangulation.

    Triangulation('file_name')
        reads a file from disk.

    Triangulation(num_tet (=5,6,7), orientable (=0(false),1(true)), index)
        reads a cusped census manifold.

    Triangulation((long) (Trianguation *))
        accepts a pointer to an existing SnapPea Triangulation.
    """
    def __init__(self, spec, orientable = 1, index = 0):
        if spec in range(5,8):
            self.triangulation = SnapPeaC.get_cusped_census_manifold(manifolds.__path__[0]+os.sep, spec, orientable, index)
        elif type(spec) == type(''):
            self.triangulation = SnapPeaC.get_triangulation(spec)
        else:
            self.triangulation = spec

# End of NMD changes

    def __del__(self):
        #    Let the SnapPea kernel free its private representation
        #    of a Triangulation.
        SnapPeaC.free_triangulation(self.triangulation)
    
    def __repr__(self):

        s = '\n'
        s = s + 'name:      %s' % SnapPeaC.get_triangulation_name(self.triangulation) + '\n'
        s = s + 'volume:    %f' % SnapPeaC.volume(self.triangulation)[0]                 + '\n'
        s = s + 'homology:  %s' % self.homology()                                     + '\n'
        s = s + 'Dehn fillings:\n'
        for i in range(SnapPeaC.get_num_cusps(self.triangulation)):
            s = s + '%3i  ' % i
            if SnapPeaC.get_cusp_is_complete(self.triangulation, i):
                s = s + '    complete'
            else:
                s = s + '%f ' % SnapPeaC.get_cusp_m(self.triangulation, i)
                if SnapPeaC.get_cusp_is_orientable(self.triangulation, i):
                    s = s + '%f ' % SnapPeaC.get_cusp_l(self.triangulation, i)
            s = s + '\n'
        
        return s

    def save(self, file_name, change_name=1):
        if len(file_name) > 0 and change_name:
           SnapPeaC.set_triangulation_name(self.triangulation, file_name)
        SnapPeaC.save_triangulation(self.triangulation, file_name)
    
    def clone(self):
        return Triangulation(SnapPeaC.copy_triangulation(self.triangulation))
    
    def get_name(self):
        return SnapPeaC.get_triangulation_name(self.triangulation)
    
    def set_name(self, name):
        SnapPeaC.set_triangulation_name(self.triangulation, name)
        
    def get_triangulation_is_orientable(self):
        return SnapPeaC.get_triangulation_is_orientable(self.triangulation)
    
    def get_solution_type(self):
        return SnapPeaC.get_solution_type(self.triangulation)
    
    def get_num_tetrahedra(self):
        return SnapPeaC.get_num_tetrahedra(self.triangulation)
    
    def get_num_cusps(self):
        return SnapPeaC.get_num_cusps(self.triangulation)
    
    def get_cusp_is_orientable(self, i):
        return SnapPeaC.get_cusp_is_orientable(self.triangulation, i)
    
    def get_cusp_is_complete(self, i):
        return SnapPeaC.get_cusp_is_complete(self.triangulation, i)
    
    def get_cusp_m(self, i):
        return SnapPeaC.get_cusp_m(self.triangulation, i)
    
    def get_cusp_l(self, i):
        return SnapPeaC.get_cusp_l(self.triangulation, i)

    def get_cusp_shape(self, i, precision=0):
        val =  SnapPeaC.get_current_cusp_shape(self.triangulation, i)
        if precision:
            return val
        return val[0]

    def get_cusp_shapes(self, precision=0):
        return [self.get_cusp_shape(i, precision)  for i in range(self.get_num_cusps())]

    def get_cusp_modulus(self, i, precision=0):
        val =  SnapPeaC.get_current_cusp_modulus(self.triangulation, i)
        if precision:
            return val
        return val[0]

    def get_cusp_moduli(self, precision=0):
        return [self.get_cusp_modulus(i, precision)  for i in range(self.get_num_cusps())]

    def get_holonomies(self, cusp_index, meridian_precision=0, longitude_precision=0,
                       recompute=0):
        return SnapPeaC.get_holonomies(self.triangulation, cusp_index,
                                       meridian_precision, longitude_precision,
                                       recompute)
                       
    def set_cusp(self, index, m = 0, l = 0, recompute = 1):
        """
        Assign Dehn filling coefficients to a cusp of a Triangulation.
        
        set_cusp(i)
            makes cusp i complete.
        
        set_cusp(i, m, l)
            does (m,l) Dehn filling on cusp i.
            (As a special case, set_cusp(i, 0, 0) is equivalent
            to set_cusp(i), and makes the cusp complete.)
        
        set_cusp(i, m, l, 0)
            sets the cusp coefficients, but doesn't attempt
            to find the hyperbolic structure.
            (set_cusp(i, m, l, 1) is equivalent to set_cusp(i, m, l).)
        """
        if index in range(SnapPeaC.get_num_cusps(self.triangulation)):
            SnapPeaC.set_cusp_info(self.triangulation, index, m, l, recompute)
        else:
            return 'cusps are numbered 0 through %i\n' % SnapPeaC.get_num_cusps(self.triangulation)
    
    def cusp_is_fillable(self, index):
        return SnapPeaC.cusp_is_fillable(self.triangulation, index)
    
    def remove_Dehn_fillings(self):
        return SnapPeaC.remove_Dehn_fillings(self.triangulation)
    
    def tet_shapes(self, use_fixed_coordinates):
        return SnapPeaC.tet_shapes(self.triangulation, use_fixed_coordinates)
    
    def volume(self, precision=0):
         val = SnapPeaC.volume(self.triangulation)
         if precision:
             return val
         return val[0]

    
    def homology(self):
        """
        Compute the first homology group of a Triangulation

        If the homology couldn't be computed (perhaps because some
        Dehn filling coefficients aren't integers) return None.
        Otherwise return the corresponding AbelianGroup.

        Note:  An empty coefficient list is not None,
        and will generate an AbelianGroup with
        no torsion coefficients.
        """
        theTorsionCoefficients = SnapPeaC.homology(self.triangulation)
        if theTorsionCoefficients == None:
            return None
        else:
            return AbelianGroup(theTorsionCoefficients)
    
    def fundamental_group(    self,
                            simplify_flag = 1,
                            fillings_affect_generators_flag = 1,
                            minimize_num_generators_flag = 1):
        return FundamentalGroup(SnapPeaC.fundamental_group(
                                    self.triangulation,
                                    simplify_flag,
                                    fillings_affect_generators_flag,
                                    minimize_num_generators_flag))

    def fill_cusp(self, index):
        if index not in range(self.get_num_cusps()):
            return 'cusps are numbered 0 through %i\n' % self.get_num_cusps()
        elif self.get_num_cusps() == 1:
            print 'The triangulation must retain at least one cusp.'
        elif not self.cusp_is_fillable(index):
            print 'To permanently fill a cusp, the Dehn filling coefficients must be relatively prime integers.'
        else:
            theFilledTriangulation = SnapPeaC.fill_cusp(self.triangulation, index)
            if (theFilledTriangulation != self.triangulation):
                SnapPeaC.free_triangulation(self.triangulation)
                self.triangulation = theFilledTriangulation

    def make_finite_triangulation(self):
        for i in range(self.get_num_cusps()):
            if self.get_cusp_is_complete(i):
                raise ValueError, "Not all cusps are filled."
        return Triangulation(SnapPeaC.make_finite_triangulation(self.triangulation))
    
    def get_drillable_curves(self, max_segments = 6):
        return SnapPeaC.get_drillable_curves(self.triangulation, max_segments)
    
    def drill_curve(self, index, max_segments = 6):
        theDrilledTriangulation = SnapPeaC.drill_curve(self.triangulation, index, max_segments)
        if (theDrilledTriangulation != 0):
            SnapPeaC.free_triangulation(self.triangulation)
            self.triangulation = theDrilledTriangulation
    
    def get_normal_surfaces(self):
        return SnapPeaC.get_normal_surfaces(self.triangulation)
    
    def split_on_normal_surface(self, index):
        #    SnapPeaC produces bare SnapPea Triangulations.
        #    Convert them to Python Triangulation objects.
        theBareTriangulations = SnapPeaC.split_along_normal_surface(self.triangulation, index)
        theTriangulationObjects = []
        for theBareTriangulation in theBareTriangulations:
            theTriangulationObjects.append(Triangulation(theBareTriangulation))
        return theTriangulationObjects
    
    def core_geodesic(self, index):
        return SnapPeaC.core_geodesic(self.triangulation, index)
    
    def shortest_curves_become_meridians(self):
        SnapPeaC.shortest_curves_become_meridians(self.triangulation)
    
    def current_fillings_become_meridians(self):
        SnapPeaC.current_fillings_become_meridians(self.triangulation)

    def change_peripheral_curves(self,index, a,b,c,d):
        return SnapPeaC.change_peripheral_curves(self.triangulation, index, a,b,c,d)
    
    def simplify(self):
        SnapPeaC.basic_simplification(self.triangulation)
    
    def randomize(self):
        SnapPeaC.randomize_triangulation(self.triangulation)
    
    def reflect(self):
        SnapPeaC.reorient(self.triangulation)
    
    def canonize(self):
        SnapPeaC.proto_canonize(self.triangulation)
    
    def is_canonical_triangulation(self):
        return SnapPeaC.is_canonical_triangulation(self.triangulation)

    def is_isometric(self, other):
        return SnapPeaC.are_isometric(self.triangulation, other.triangulation)
    
    def symmetry_group(self):

        theRawResults = SnapPeaC.symmetry_group(self.triangulation)

        thePackagedResults = {}
        
        if theRawResults[0] == 0:
            thePackagedResults['manifold'] = None
        else:
            thePackagedResults['manifold'] = SymmetryGroup(theRawResults[0], theRawResults[3])
        
        if theRawResults[1] == 0:
            thePackagedResults['link'] = None
        else:
            thePackagedResults['link'] = SymmetryGroup(theRawResults[1], theRawResults[3])
        
        if theRawResults[2] == 0:
            thePackagedResults['symmetric triangulation'] = None
        else:
            thePackagedResults['symmetric triangulation'] = Triangulation(theRawResults[2])

        return thePackagedResults
    
    def Dirichlet(self, centroid_at_origin=1, maximize_inj_radius=0,
                displacement=(0,0,0)):
        return DirichletDomain(SnapPeaC.Dirichlet(    self.triangulation,
                                                    centroid_at_origin,
                                                    maximize_inj_radius,
                                                    displacement[0],
                                                    displacement[1],
                                                    displacement[2]))

    
    def get_gluing_data(self, fill=None):
        return SnapPeaC.get_gluing_data(self.triangulation, fill)

    def get_gluing_equations(self):
        """Format of get_gluing_equations:
        a b c  d e f  ... = 2 pi i
        
        means

        a*log(z0) + b*log(1/(1-z0)) + c*log((z0-1)/z) + d*log(z1) +... = 2 pi i
             
        for edge equations, and (same) = 1 for cusp equations

        In terms of the tetrahedra, a is the invariant of the edge
        (2,3), b the invariant of the edge (0,2) and c is the
        invariant of the edge (1,2).  See kernel_code/edge_classes.c
        for a detailed account of the convention."""
        return SnapPeaC.get_gluing_equations(self.triangulation)

    def get_cusp_equation(self, num_cusp, m, l):
        return SnapPeaC.get_cusp_equation(self.triangulation, num_cusp, m, l)

    def get_cusp_equations(self):
        return [ (self.get_cusp_equation(i, 1, 0), self.get_cusp_equation(i, 0, 1))
                 for i in range(self.get_num_cusps())]

    def same_triangulation(self, other):
        return SnapPeaC.same_triangulation(self.triangulation, other.triangulation)

    def install_shortest_bases(self):
        SnapPeaC.install_shortest_bases(self.triangulation)

    def install_current_curve_bases(self):
        SnapPeaC.install_current_curve_bases(self.triangulation)

    def hack_double_cover(self):
        return Triangulation(SnapPeaC.hack_double_cover(self.triangulation))

    def hack_create_all_covers(self, degree):
        return [ Triangulation(x) for x in SnapPeaC.hack_create_all_covers(self.triangulation, degree)]
    
    def choose_generators(self, compute_corners, centroid_at_origin):
        SnapPeaC.choose_generators(self.triangulation, compute_corners, centroid_at_origin)

    def hack_hide_hyperbolic_structure(self):
        SnapPeaC.hack_hide_hyperbolic_structure(self.triangulation)

    def find_complete_hyperbolic_structure(self):
        SnapPeaC.find_complete_hyperbolic_structure(self.triangulation)

    def create_cover(self, permutation_images):
        """This function creates a cover of the manifold, which is
        specified by a list of permutations corresponding to each of
        the generators of the simplified presentation of pi_1.  Each
        permutation is stored simply as a list L where L[i] is the
        image of i under the permutation.  Here, the domain and range
        of a permutation is just range(n), though most computer
        algebra programs use range(1,n+1)."""

        return Triangulation(SnapPeaC.create_cover(self.triangulation,  permutation_images))

    def set_tet_shapes(self, shapes):
        SnapPeaC.set_tet_shapes(self.triangulation, shapes)

    def get_cusp_neighborhood_info(self, cusp_index = 0):

        """For the largest possible embedded cusp neighborhood about
        the given geodesic, returns the volume and the translations of
        the meridian and the longitude."""
        
        assert cusp_index < self.get_num_cusps() 
        return SnapPeaC.get_cusp_neighborhood_info(self.triangulation, cusp_index)

    def get_peripheral_curve_intersections(self, cusp=0):

        """

        Returns intersection number information about the peripheral
        curves at the given cusp, given as a tuple:

        (meridian info, longitude info)

        where each curve is given by a list of

        (tetrahedra number, vectex number, edge/face number, weight)

        """

        ans = []
        for curve_type in [0,1]:
            curve = []
            for tet in range(self.get_num_tetrahedra()):
                for vertex in range(4):
                    for face in range(4):
                        if vertex != face:
                            weight, curr_cusp = SnapPeaC.get_peripheral_curve_datum(
                                self.triangulation, tet, curve_type, vertex, face)
                            if curr_cusp == cusp and weight != 0:
                                curve.append( (tet, vertex, face, weight) )
            ans.append(curve)
        return ans
        
    
            
        
def sendable_to_triangulation(sendable_string):
    return Triangulation(SnapPeaC.sendable_to_triangulation(sendable_string))

class CuspedCensus_list:
    """
    Subscriptable class containing Triangulations from the SnapPea cusped census.
    """
    def __init__(self, num_tetrahedra = 5, orientable = 1, slice = None):
    
        self.num_tetrahedra    = num_tetrahedra
        self.orientable        = orientable

        if   num_tetrahedra == 5:
            self.length = 415
        elif num_tetrahedra == 6:
            if self.orientable:
                self.length = 962
            else:
                self.length = 259
        elif num_tetrahedra == 7:
            if self.orientable:
                self.length = 3552
            else:
                self.length = 887
        else:
            self.length = 0
        self.slice = normalize_slice(slice, self.length)
    
    def __repr__(self):
        if   self.num_tetrahedra == 5:
            return 'census of all cusped hyperbolic 3-manifolds triangulable with 5 or fewer ideal tetrahedra'
        elif self.num_tetrahedra == 6:
            if self.orientable:
                return 'census of all orientable cusped hyperbolic 3-manifolds triangulable with 6 (but not fewer) ideal tetrahedra'
            else:
                return 'census of all nonorientable cusped hyperbolic 3-manifolds triangulable with 6 (but not fewer) ideal tetrahedra'
        elif self.num_tetrahedra == 7:
            if self.orientable:
                return 'census of all orientable cusped hyperbolic 3-manifolds triangulable with 7 (but not fewer) ideal tetrahedra'
            else:
                return 'census of all nonorientable cusped hyperbolic 3-manifolds triangulable with 7 (but not fewer) ideal tetrahedra'
        else:
            return 'empty census'
    
    def __len__(self):
        return  max(0, (self.slice.stop - self.slice.start + self.slice.step - 1)/
                        self.slice.step)
    
    def __getitem__(self, i):
        if type(i) == types.SliceType:
            s = slice_slice(self.slice, normalize_slice(i,self.__len__()))
            return CuspedCensus_list(self.num_tetrahedra, self.orientable, s)
        j = self.slice.start + i*self.slice.step
        if not ( 0 <= j < self.slice.stop ):
            raise IndexError, "Requested manifold is out of range."
        return Triangulation(self.num_tetrahedra, self.orientable, j)


class CuspedCensus5tet_list:
    """
    Subscriptable class containing Triangulations from the SnapPea cusped census of 5 or fewer tets.
    """

    orientable_indices = [3, 4, 6, 7, 9, 10, 11, 15, 16, 17, 19, 22, 23, 26, 27, 29, 30, 32,
                        33, 34, 35, 36, 37, 38, 39, 40, 43, 44, 45, 46, 47, 49, 52, 53, 54,
                        55, 58, 59, 60, 61, 62, 64, 66, 67, 69, 70, 71, 72, 73, 74, 76, 77,
                        78, 79, 80, 81, 82, 83, 84, 85, 87, 89, 90, 93, 94, 95, 96, 98, 99,
                        100, 102, 103, 104, 105, 106, 108, 110, 111, 112, 113, 115, 116, 117,
                        118, 119, 120, 121, 122, 123, 125, 129, 130, 135, 136, 137, 139, 140,
                        141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 154, 155, 157,
                        159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172,
                        173, 175, 178, 179, 180, 181, 182, 183, 184, 185, 186, 188, 189, 190,
                        191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 206,
                        207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220,
                        221, 222, 223, 224, 225, 227, 228, 229, 230, 231, 232, 233, 234, 235,
                        236, 238, 239, 240, 241, 242, 243, 244, 246, 247, 248, 249, 250, 251,
                        252, 253, 255, 256, 257, 258, 259, 260, 261, 262, 263, 266, 267, 269,
                        270, 271, 272, 273, 275, 276, 277, 278, 279, 280, 281, 282, 284, 285,
                        286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 299, 300,
                        302, 303, 304, 305, 306, 307, 310, 312, 316, 318, 319, 320, 322, 326,
                        328, 329, 335, 336, 337, 338, 339, 340, 342, 343, 345, 346, 349, 350,
                        351, 352, 356, 357, 358, 359, 360, 361, 363, 364, 366, 367, 368, 369,
                        370, 371, 372, 373, 374, 376, 378, 385, 388, 389, 390, 391, 392, 393,
                        395, 397, 400, 401, 402, 403, 410, 412]

    nonorientable_indices = [0, 1, 2, 5, 8, 12, 13, 14, 18, 20, 21,
                             24, 25, 28, 31, 41, 42, 48, 50, 51, 56, 57, 63, 65, 68, 75, 86,
                             88, 91, 92, 97, 101, 107, 109, 114, 124, 126, 127, 128, 131, 132,
                             133, 134, 138, 152, 153, 156, 158, 174, 176, 177, 187, 204, 205,
                             226, 237, 245, 254, 264, 265, 268, 274, 283, 298, 301, 308, 309,
                             311, 313, 314, 315, 317, 321, 323, 324, 325, 327, 330, 331, 332,
                             333, 334, 341, 344, 347, 348, 353, 354, 355, 362, 365, 375, 377,
                             379, 380, 381, 382, 383, 384, 386, 387, 394, 396, 398, 399, 404,
                             405, 406, 407, 408, 409, 411, 413, 414]


    def __init__(self, orientable = 1, slice = None):
    
        self.orientable = orientable

        if   orientable:
            self.length = 301
        else:
            self.length  = 114
        self.slice = normalize_slice(slice, self.length)
    
    def __repr__(self):
        if self.orientable:
            return 'census of all orientable cusped hyperbolic 3-manifolds triangulable with 5 or fewer ideal tetrahedra'
        else:
            return 'census of all nonorientable cusped hyperbolic 3-manifolds triangulable with 5 or fewer ideal tetrahedra'
    
    def __len__(self):
        return  max(0, (self.slice.stop - self.slice.start + self.slice.step - 1)/
                        self.slice.step)
    
    def __getitem__(self, i):
        if type(i) == types.SliceType:
            s = slice_slice(self.slice, normalize_slice(i,self.__len__()))
            return CuspedCensus5tet_list( self.orientable, s)
        j = self.slice.start + i*self.slice.step
        if not ( 0 <= j < self.slice.stop ):
            raise IndexError, "Requested manifold is out of range."
        if self.orientable:
            k = self.orientable_indices[j]
        else:
            k = self.nonorientable_indices[j]
        return Triangulation(5, self.orientable, k)


class AbelianGroup:
    
    def __init__(self, coefficients):
        self.coefficients = coefficients
    
    def __repr__(self):

        coef = self.coefficients
        n = len(coef)

        if (n == 0):
            return 'trivial'
        s = ''
        for i in range(n):
            s = s + 'Z'
            if coef[i] != 0:
                s = s + ('/%i' % coef[i])
            if i != n - 1:
                s = s + ' + '
        return s
    
    def __len__(self):
        return len(self.coefficients)
    
    def __getitem__(self, i):
        return self.coefficients[i]
    
    def Betti_number(self):
        return self.coefficients.count(0)

    def order(self):
        if self.Betti_number() > 0:
            return "infinite"
        ans = 1
        for c in self.coefficients:
            ans = ans*c
        return ans


class FundamentalGroup:

    def __init__(self, fundamental_group):
        #    fundamental_group is an integer that is really
        #    a pointer to a SnapPea kernel FundamentalGroup.
        self.fundamental_group = fundamental_group

    def __del__(self):
        #    Let the SnapPea kernel free its private representation
        #    of a FundamentalGroup.
        SnapPeaC.free_group_presentation(self.fundamental_group)

    def __repr__(self):
        return (    'generators:  ' + self.generators_string() + '\n'
                  + 'relations:\n'
                  + self.relations_string() )
    
    def num_generators(self):
        return SnapPeaC.fg_get_num_generators(self.fundamental_group)
    
    def relations(self):
        return SnapPeaC.fg_get_relations(self.fundamental_group)

    def representation(self, word):
        return SnapPeaC.fg_representation(self.fundamental_group, word)

    def peripheral_curves(self):
        return SnapPeaC.fg_peripheral_curves(self.fundamental_group)
        
    def generators_string(self):
        theAlphabet = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'
        if self.num_generators() > 0:
            return theAlphabet[0 : 2*self.num_generators() - 1]
        else:
            return 'none'
        
    def relations_string(self):
        theString = ''
        for theWord in self.relations():
            theString = theString + theWord + '\n'
        return theString

    def magma_string(self):
        def process_letter(x):
            if x.islower():
                return x
            else:
                return x.lower() + "^-1"

        def process_relator(word):
            return "*".join(map(process_letter, word))
        
        return "Group<" + self.generators_string() + "|" + ", ".join(map(process_relator, self.relations())) + ">"

    def gap_string(self):
        def process_letter(x):
            if x.islower():
                return x
            else:
                return x.lower() + "^-1"

        def process_relator(word):
            return "*".join(map(process_letter, word))

        relations = ", ".join(map(process_relator, self.relations()))
        assignments = "".join(["%s := F.%d; " % (string.letters[i], i + 1) for i in range(self.num_generators())])
        return "CallFuncList(function() local F, %s; F := FreeGroup(%s); %s  return F/[%s]; end,[])"  % (self.generators_string(), ", ".join(['"' + x + '"' for x in string.letters[:self.num_generators()]]), assignments, relations)


class SymmetryGroup:

    def __init__(self, symmetry_group, is_full_group, owns_symmetry_group=True):
        #    symmetry_group is an integer that is really
        #    a pointer to a SnapPea kernel SymmetryGroup.
        self.symmetry_group            = symmetry_group
        self.is_full_group            = is_full_group
        self.owns_symmetry_group    = owns_symmetry_group

    def __del__(self):
        #    Let the SnapPea kernel free its private representation
        #    of a SymmetryGroup.
        if self.owns_symmetry_group:
            SnapPeaC.free_symmetry_group(self.symmetry_group)

    def __repr__(self):

        if self.is_full_group:
            thePretext = ''
        else:
            thePretext = 'at least '

        if self.is_abelian():
            theText = self.abelian_description().__repr__()
        elif self.is_dihedral():
            theText = 'D%d'%(self.order()/2)
        elif self.is_polyhedral():
            theText = self.polyhedral_description()['name']
        elif self.is_S5():
            theText = 'S5'
        elif self.is_direct_product():
            theText =     self.get_factor(0).__repr__()    \
                      + ' X '                            \
                      + self.get_factor(1).__repr__()
        else:
            theText = 'nonabelian group of order %d'%self.order()
        
        return thePretext + theText
    
    def order(self):
        return SnapPeaC.symmetry_group_order(self.symmetry_group)
    
    def is_abelian(self):
        return SnapPeaC.symmetry_group_is_abelian(self.symmetry_group)

    def abelian_description(self):
        return AbelianGroup(SnapPeaC.symmetry_group_abelian_description(self.symmetry_group))
    
    def is_dihedral(self):
        return SnapPeaC.symmetry_group_is_dihedral(self.symmetry_group)
    
    def is_polyhedral(self):
        return SnapPeaC.symmetry_group_is_polyhedral(self.symmetry_group)
    
    def polyhedral_description(self):
        return SnapPeaC.symmetry_group_polyhedral_description(self.symmetry_group)
    
    def is_S5(self):
        return SnapPeaC.symmetry_group_is_S5(self.symmetry_group)
    
    def is_direct_product(self):
        return SnapPeaC.symmetry_group_is_direct_product(self.symmetry_group)
    
    def get_factor(self, i):
        return SymmetryGroup(    SnapPeaC.symmetry_group_factor(self.symmetry_group, i),
                                True,
                                False)
    
    def is_amphicheiral(self):
        return SnapPeaC.symmetry_group_is_amphicheiral(self.symmetry_group)
    
    def is_invertible_knot(self):
        return SnapPeaC.symmetry_group_invertible_knot(self.symmetry_group)
        
    
    #    Please don't confuse abelian_description() (above)
    #    with abelianization() (below).  The former describes
    #    a group which already happens to be abelian, while
    #    the latter gives the quotient of the group by its
    #    commutator subgroup.

    def commutator_subgroup(self):
        return SymmetryGroup(    SnapPeaC.symmetry_group_commutator_subgroup(self.symmetry_group),
                                self.is_full_group,
                                True)

    def abelianization(self):
    
        if self.is_full_group:
        
            #    Compute the abelianization as a pointer to a SnapPea kernel
            #    internal SymmetryGroup data structure.
            theSnapPeaGroup = SnapPeaC.symmetry_group_abelianization(self.symmetry_group)
            
            #    Create the corresponding Python AbelianGroup object.
            theAbelianization = AbelianGroup(SnapPeaC.symmetry_group_abelian_description(theSnapPeaGroup))

            #    Free the SnapPea kernel's data structure.
            SnapPeaC.free_symmetry_group(theSnapPeaGroup)
            
            #    Return the Python object.
            return theAbelianization
            
        else:
            return None

    def center(self):
        if self.is_full_group:
            return SymmetryGroup(    SnapPeaC.symmetry_group_center(self.symmetry_group),
                                    True,
                                    True)
        else:
            return None
    
    def presentation(self):
        if self.is_full_group:
            return SnapPeaC.symmetry_group_presentation(self.symmetry_group)
        else:
            return None
    
    def presentation_text(self):
        if self.is_full_group:
            theAlphabet = 'a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z'
            thePresentation = self.presentation()
            theText = '{'
            theText = theText + theAlphabet[0 : 2*thePresentation['number of generators'] - 1]
            theText = theText + ' |'
            theLowerAlphabet = 'abcdefghijklmnopqrstuvwxyz'
            theUpperAlphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
            theFirstRelationFlag = 1
            for theRelation in thePresentation['relations']:
                if theFirstRelationFlag == 0:
                    theText = theText + ',' 
                else:
                    theFirstRelationFlag = 0
                for theFactor in theRelation:
                    if theFactor[1] < 0:
                        theLetter = theUpperAlphabet[theFactor[0]]
                        thePower  = - theFactor[1]
                    else:
                        theLetter = theLowerAlphabet[theFactor[0]]
                        thePower =   theFactor[1]
                    if thePower > 1:
                        theText = theText + ' %c^%i'%(theLetter, thePower)
                    elif thePower == 1:
                        theText = theText + ' %c'%(theLetter)
                    else:
                        raise RuntimeError, 'zero exponent in symmetry group presentation'
            theText = theText + ' }'
            return theText
        else:
            return None


class DirichletDomain:

    def __init__(self, Dirichlet_domain):
        #    Dirichlet_domain is an integer that is really
        #    a pointer to a SnapPea kernel WEPolyhedron.
        self.Dirichlet_domain = Dirichlet_domain

    def __del__(self):
        #    Let the SnapPea kernel free its private representation
        #    of a WEPolyhedron.
        SnapPeaC.free_Dirichlet_domain(self.Dirichlet_domain)

    def __repr__(self):
        return 'Dirichlet domain v=? e=? f=?'
    
    def off(self):
    
        theText = 'OFF %d %d 0\n\n'%(self.v(), self.f())
        
        theVertices = self.vertices()
        for i in range(self.v()):
            theText = theText + '%11.8f %11.8f %11.8f\n'%theVertices[i]
        theText = theText + '\n'
        
        theFaces = self.faces()
        theFaceColors = self.face_colors()
        for i in range(self.f()):
            theText = theText + '%2d'%len(theFaces[i])
            for j in range(len(theFaces[i])):
                theText = theText + ' %2d'%theFaces[i][j]
            theText = theText + '  %5.3f %5.3f %5.3f 1.000\n'%theFaceColors[i]
        
        return theText
    
    def v(self):
        return SnapPeaC.Dirichlet_num_vertices(self.Dirichlet_domain)
    
    def e(self):
        return SnapPeaC.Dirichlet_num_edges(self.Dirichlet_domain)
    
    def f(self):
        return SnapPeaC.Dirichlet_num_faces(self.Dirichlet_domain)

    def vertices(self):
        return SnapPeaC.Dirichlet_vertices(self.Dirichlet_domain)

    def faces(self):
        return SnapPeaC.Dirichlet_faces(self.Dirichlet_domain)

    def face_colors(self):
        return SnapPeaC.Dirichlet_face_colors(self.Dirichlet_domain)
    
    def face_pairings(self):
        return SnapPeaC.Dirichlet_face_pairings(self.Dirichlet_domain)


#--------------------------------------------------------------------
#  Begin: Additional Code added 2002/8 by MC and NMD:
#--------------------------------------------------------------------

import gzip, struct, os, types, re, string

closed_census_directory = os.path.join(manifolds.__path__[0], 'ClosedCensusData')
link_directory = os.path.join(manifolds.__path__[0], 'ChristyLinks')
table_directory = os.path.join(manifolds.__path__[0], 'HTWKnots')


class ClosedCensus_list:
    """
    Subscriptable class containing Triangulations from the SnapPea closed census.
    """
    
    def __init__(self, orientable = 1, slice = None):
        self.orientable = orientable
        if self.orientable:
            filename = 'ClosedOrientableDistinct.txt'
        else:
            filename = 'ClosedNonorientableDistinct.txt'
        self.census_file = open(closed_census_directory + os.sep + filename)
        self.linelength  = len(self.census_file.readline())
        self.census_file.seek(0,2)
        self.length = int( (self.census_file.tell() + 1) / self.linelength )
        self.slice = normalize_slice(slice, self.length)

    def __del__(self):
        self.census_file.close()

    def __repr__(self):
        if self.orientable:
            return 'Census of low-volume closed orientable hyperbolic manifolds.' 
        else:
            return 'Census of low-volume closed non-orientable hyperbolic manifolds.' 

    def __len__(self):
        return  max(0, (self.slice.stop - self.slice.start + self.slice.step - 1)/
                        self.slice.step)

    def __getitem__(self, i):
        if type(i) == types.SliceType:
            s = slice_slice(self.slice, normalize_slice(i, self.__len__()))
            return ClosedCensus_list(self.orientable, s)
        j = self.slice.start + i*self.slice.step
        if not ( 0 <= j < self.slice.stop ):
             raise IndexError, "Requested manifold is out of range."
        self.census_file.seek(j*self.linelength)
        volume, number_of_tets, index, m, l = string.split(self.census_file.readline())
        manifold = Triangulation(int(number_of_tets), 1, int(index))
        manifold.set_cusp(0,int(m),int(l))
        manifold.set_name(manifold.get_name() + "(%d,%d)" % (int(m), int(l)))
        return manifold    

# ---------- Support for the Hoste-Thistlethwaite knot table ----------
#
# We store the Hoste-Thistlethwaite tables in two gzipped files:
# alternating.gz and nonalternating.gz.  An alternating knot is stored
# in an array of 8 bytes.  To recover the Dowker-Thistlethwaite
# encoding of an alternating knot with k crossings, double the first k
# nybbles of the array, after replacing 0's by 16's.  A non-alternating
# knot is stored in an array of 10 bytes.  For a k-crossing knot the
# first k nybbles describe the Dowker-Thistlethwaite encoding of the
# corresponding alternating knot. This is padded with a zero nybble if
# k is odd.  The next two bytes describe the signs for the DT encoding
# of the nonalternating knot, with the high order bit of the first
# byte representing the sign of the first crossing. For knots with
# less than 16 crossings each record is padded with 0's.  But these
# are adequately handled by gzip, with the result that the two gzipped
# files are somewhat smaller than the carefully packed files that are
# supplied with the knotscape program.


# These dictionaries are used in accessing the tables.  The key is the
# number of crossings, the value is the number of knots with that many
# crossings.

Alternating_numbers = { 3:1, 4:1, 5:2, 6:3, 7:7, 8:18, 9:41, 10:123, 11:367,
                        12:1288, 13:4878, 14:19536, 15:85263, 16:379799 }

Nonalternating_numbers = { 8:3, 9:8, 10:42, 11:185, 12:888, 13:5110,
                           14:27436, 15:168030, 16:1008906 }

Alternating_offsets = {}
offset = 0
for i in range(3,17):
    Alternating_offsets[i] = offset
    offset +=  Alternating_numbers[i]
Num_Alternating = offset

Nonalternating_offsets = {}
offset = 0
for i in range(8,17):
    Nonalternating_offsets[i] = offset
    offset += Nonalternating_numbers[i]
Num_Nonalternating = offset

# These are the gzipped files holding the tables.
Alternating_table = gzip.open(os.path.join(table_directory, 'alternating.gz') )
Nonalternating_table = gzip.open(os.path.join(table_directory, 'nonalternating.gz') )

def extract_HT_knot(record, crossings, alternation):
    DT=[]
    size = (1+crossings)/2
    for byte in record[:size]:
        first_nybble = (byte & 0xf0) >> 4
        if first_nybble == 0: first_nybble = 16
        DT.append(2*first_nybble)
        second_nybble = byte & 0x0f
        if second_nybble == 0: second_nybble = 16
        DT.append(2*second_nybble)
    if alternation == 'n':
        signs = record[-2]<<8 | record[-1]
        mask = 0x8000
        for i in range(crossings):
            if (signs & (mask >> i)) == 0:
                DT[i] = -DT[i]
    return DT[:crossings]

def get_HT_knot_DT(crossings, alternation, index):
    size = (1 + crossings)/2
    index -= 1
    if ( alternation == 'a'
         and crossings in Alternating_numbers.keys()
         and 0 <= index < Alternating_numbers[crossings] ):
        offset = 8*(Alternating_offsets[crossings] +  index)
        Alternating_table.seek(offset)
        data = Alternating_table.read(size)
        record = struct.unpack('%dB'%size, data)
    elif ( alternation == 'n'
         and crossings in Nonalternating_numbers.keys()
         and 0 <= index < Nonalternating_numbers[crossings] ):
        offset = 10*(Nonalternating_offsets[crossings] +  index)
        Nonalternating_table.seek(offset)
        data = Nonalternating_table.read(size+2)
        record = struct.unpack('%dB'%(size+2), data)
    else:
        print """
        You have entered a Hoste-Thistlethwaite knot name with an
        inappropriate index or number of crossings."""
        raise ValueError

    DT = extract_HT_knot(record, crossings, alternation)
    return DT

def get_HT_knot(crossings, alternation, index):
    DT = get_HT_knot_DT(crossings, alternation, index)
    name = "%d" % crossings + alternation + "%d" % index
    manifold = Triangulation(SnapPeaC.get_triangulation_from_DT(DT))
    manifold.set_name(name)
    return manifold

def get_HT_knot_by_index(alternation, index):
    DT=[]
    crossings = 16
    if alternation == 'a':
        for i in range(3,17):
            if Alternating_offsets[i] > index:
                crossings = i-1
                break
        Alternating_table.seek(8*index)
        size = (1 + crossings)/2
        data = Alternating_table.read(size)
        record = struct.unpack('%dB'%size, data)
        index_within_crossings = index - Alternating_offsets[crossings]
    if alternation == 'n':
        for i in range(8,17):
            if Nonalternating_offsets[i] > index:
                crossings = i-1
                break
        Nonalternating_table.seek(10*index)
        size = (1 + crossings)/2
        data = Nonalternating_table.read(size+2)
        record = struct.unpack('%dB'%(size+2), data)
        index_within_crossings = index - Nonalternating_offsets[crossings]


    DT = extract_HT_knot(record, crossings, alternation)
    name = "%d" % crossings + alternation + "%d" % (index_within_crossings + 1)
    manifold = Triangulation(SnapPeaC.get_triangulation_from_DT(DT))
    manifold.set_name(name)
    return manifold

# An HT_knot_list is a subscriptable object containing Triangulations
# of exteriors of knots in the Hoste-Thistlethwaite tables.

class HT_knot_list:
    def __init__(self, alternation, slice = None):
        if alternation == 'a':
            self.length = Num_Alternating
            self.alternation = 'a'
        elif alternation == 'n':
            self.length = Num_Nonalternating
            self.alternation = 'n'
        else:
            self.length = 0
            self.alternation = None
        self.slice = normalize_slice(slice, self.length)

    def __repr__(self):
        if self.alternation == 'a':
            return """
    Exteriors of alternating knots from the Hoste-Thistlethwaite table."""
        elif self.alternation == 'n':
            return """
    Exteriors of non-alternating knots from the Hoste-Thistlethwaite table."""
        else:
            return """
    Improperly initialized knot list."""

    def __len__(self):
        return  max(0, (self.slice.stop - self.slice.start + self.slice.step - 1)/
                        self.slice.step)
        
    def __getitem__(self, i):
        if type(i) == types.SliceType:
            s = slice_slice(self.slice, normalize_slice(i, self.__len__()))
            return HT_knot_list(self.alternation, s)
        j = self.slice.start + i*self.slice.step
        if not ( 0 <= j < self.slice.stop ):
             raise IndexError, "Requested knot exterior is out of range."
        return get_HT_knot_by_index(self.alternation, j)


# Census of links/knots using the classical numbering system of
# Tait/Conway/Rolfsen/Christy.  Mostly useful just for links as the HT
# list of knots is much more extensive.

class LinkCensus_list:
    """
    Subscriptable class containing Triangulations from link exteriors.
    """

    # num_links[component][crossings] is the number of links with
    # specified number of components and crossings.
    num_links= [ [], [0, 0, 0, 1, 1, 2, 3, 7, 21, 49, 166, 552],
                 [0, 0, 0, 0, 1, 1, 3, 8, 16, 61, 183],
                 [0, 0, 0, 0, 0, 0, 3, 1, 10, 21, 74],
                 [0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 24],
                 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]]

    max_crossings = 11

    def __init__(self, components, slice = None):
         if not (1 <= components < len(self.num_links) ):
            raise IndexError, "No data on links with %s many components." % components

         self.components = components

         length = 0
         for n in self.num_links[components]:
            length = length + n
         self.length = length

         self.slice = normalize_slice(slice, self.length)
    
    def __repr__(self):
        return 'Christy census of link complements in S^3 with %s components' % self.components
    
            
    def __len__(self):
        return  max(0, (self.slice.stop - self.slice.start + self.slice.step - 1)/
                        self.slice.step)
    
    def __getitem__(self, i):
        if type(i) == types.SliceType:
            s = slice_slice(self.slice, normalize_slice(i,self.__len__()))
            return LinkCensus_list(self.components, s)
        j = self.slice.start + i*self.slice.step
        if not ( 0 <= j < self.slice.stop ):
            raise IndexError, "Requested manifold is out of range."

        so_far = 0
        for k in range(self.max_crossings + 1):
            n =  self.num_links[self.components][k]
            so_far = so_far + n
            if so_far > j:
                l = j - so_far + n + 1
                if self.components > 1:
                    name = "%d^%d_%d" % (k, self.components, l)
                    M =  get_manifold(name)
                    M.set_name(name)
                    return M
                else:
                    name = "%d_%d" % (k,  l)
                    M =  get_manifold(name)
                    M.set_name(name)
                    return M


split_filling_info = re.compile("(.*?)((?:\([0-9 .+-]+,[0-9 .+-]+\))+)")
is_census_manifold = re.compile("([msvxy])([0-9]+)$")
is_torus_bundle = re.compile("b([+-no])([+-])([lLrR]+)$")
is_knot_complement = re.compile("(?P<crossings>[0-9]+)_(?P<index>[0-9]+)$")
is_link_complement1 = re.compile("(?P<crossings>[0-9]+)[\^](?P<components>[0-9]+)[_](?P<index>[0-9]+)$")
is_link_complement2 = re.compile("(?P<crossings>[0-9]+)[_](?P<index>[0-9]+)[\^](?P<components>[0-9]+)$")
is_link_complement3 = re.compile("[lL]([0-9]+)")
is_HT_knot = re.compile('(?P<crossings>[0-9]+)(?P<alternation>[an])(?P<index>[0-9]+)')

spec_dict = {'m': (5, 1),
             's': (6, 1),
             'v': (7, 1),
             'x': (6, 0),
             'y': (7, 0)}

def get_manifold(name):
    """Loads a manifold specified by name, according 
to the following conventions:  

   1. Numbers in parens at the end mean do Dehn filling on the loaded
   manifold, e.g. m125(1,2)(4,5) means do (1,2) filling on the first
   cusp and (4,5) filling on the second cusp.

   2. Names of the form m123, s123, v123, and so on refer to the
   SnapPea Census manifolds.

   3. Names of the form 4_1, 04_1, 4_01, 5^2_6, 6_4^7, etc, refer to
   complements of links in Rolfsen's table.  Similary L20935.  l104001, etc.

   4. Names of the form b++LLR, b+-llR, bo-RRL, bn+LRLR load the
   correponding torus bundle.

   5. Names of the form 11a17 or 12n345 refer to complements of knots in
   the Hoste-Thisthlethwaite tables.

  If one of the above rules does _not_ apply, it looks for a file
  with the specified name in the current directory and looks in the
  path given by the user variable SNAPPEA_MANIFOLD_DIRECTORY.
"""
    # get filling info, if any
    m = split_filling_info.match(name)
    if m:
        real_name = m.group(1)
        fillings = re.subn("\)\(", "),(", m.group(2))[0]
        fillings = eval( "[" + fillings + "]" )
    else:
        real_name = name
        fillings = ()

    triangulation = None

    # Check for a census manifold
    m = is_census_manifold.match(real_name)
    if m:
         spec, orientable = spec_dict[m.group(1)]
         triangulation = Triangulation(  spec, orientable, int(m.group(2)) )
        
    if triangulation == None:
     # Check for a puntured torus bundle 
        m = is_torus_bundle.match(real_name)
        if m:
            LRstring = m.group(3).upper()
            negative_determinant = negative_trace = 0

            if m.group(1) == '-' or m.group(1) == 'n':
                negative_determinant = 1
            
            if m.group(2) == '+':
                negative_trace = 0
            else:
                negative_trace = 1

            triangulation =  Triangulation(SnapPeaC.easy_triangulate_punctured_torus_bundle(
                negative_determinant, negative_trace, LRstring))
    
    if triangulation == None:
    # Check for a link
        filename = None
        m = is_knot_complement.match(real_name)
        if m:
            filename = "L1%.2d%.3d" % (int(m.group("crossings")),
                                       int(m.group("index")))
        m = is_link_complement1.match(real_name)
        if m:
            filename = "L%.1d%.2d%.3d" % (int(m.group("components")),
                                          int(m.group("crossings")),
                                          int(m.group("index")))
        m = is_link_complement2.match(real_name)
        if m:
            filename = "L%.1d%.2d%.3d" % (int(m.group("components")),
                                          int(m.group("crossings")),
                                          int(m.group("index")))
        m = is_link_complement3.match(real_name)
        if m:
            filename = "L" + m.group(1)

        if filename:
            try:
                 triangulation = Triangulation(SnapPeaC.get_triangulation(
                     os.path.join(link_directory, filename) ))
            except:
                raise IOError, "Requested link complement " + real_name + " not found."

    if triangulation == None:
    # Check for a Hoste-Thistlethwaite knot.
        m = is_HT_knot.match(real_name)
        if m:
            triangulation = get_HT_knot(int(m.group("crossings")),
                             m.group("alternation"),
                             int(m.group("index")))

    if triangulation == None:
    # If all else fails, try to load a manifold from a file.
        try:
            locations = [os.curdir, os.environ["SNAPPEA_MANIFOLD_DIRECTORY"]]
        except KeyError:
            locations = [os.curdir]
        found = 0
        for location in locations:
            filename = os.path.join(location,real_name)
            if os.path.isfile(filename):
                triangulation = Triangulation(filename)
                break;
            
    if triangulation == None:
    # Give up if we failed to locate a manifold.
        raise IOError, "Requested manifold %s not found." % real_name
        
    # Otherwise do the dehn filling.
    if len(fillings) > 0:
        num_cusps = triangulation.get_num_cusps() 
        if len(fillings) > num_cusps:
            raise ValueError, "More fillings requested than manifold has cusps"
        for i in range(len(fillings)):
            m, l = fillings[i]
            triangulation.set_cusp(i, m, l)
        s = real_name
        for filling in fillings:
            s = s + "(%s,%s)" % filling
        triangulation.set_name(s)

    return triangulation


def get_link_complement(file_name=None):
    if file_name is None:
        try:
            dialog = plink.LinkEditor(no_arcs=True)
            dialog.window.mainloop()
        except:
            raise RuntimeError, "Please install PLink to use this feature"
        klp = dialog.SnapPea_KLPProjection() 
        if klp is not None:
            return Triangulation(SnapPeaC.get_triangulation_from_PythonKLP(*klp))
        else:
            return None
    if not os.path.isfile(file_name):
        raise IOError, "Requested link file %s does not exist." % file_name
    return Triangulation(SnapPeaC.triangulate_link_complement_from_file(file_name))

def fibered_manifold_from_braid(braid_word, num_strands=None):
    """
    
    This function returns the fibered 3-manifold associated to
    braid_word, with is a list of integers.  An integer i in braid_word
    corresponds to the standard generator sigma_i of the braid group,
    using the convention that sigma_(-i) = (sigma_i)^(-1).  The number
    of strands is calculated automatically from the word, but can be
    specified explicitly if needed.  
    
    The fibered 3-manifold can be described as the closure of the braid
    with the addition of an unknotted component which is the boundary
    of a disc having one set of end points of the braid.  The
    additional component is the last cusp.
    
    (In other words, think of the braid as givening an element of the
    mapping class group of the num_strands-punctured disc.  This
    function returns the corresponding mapping torus.)
    """
    
    if not num_strands:
        num_strands = max(map(abs, braid_word)) + 1
    T = Triangulation(SnapPeaC.fibered_manifold_associated_to_braid(num_strands, braid_word))
    T.set_name("")
    return T


#----------begin Misc. functions

# computes the gcd.  taken from snappea

def gcd(a, b):

    a = abs(a)
    b = abs(b)
    
    if a == 0:
        if b == 0: raise ValueError, "gcd(0,0) undefined."
        else: return b

    while 1:
        b = b % a
        if (b == 0): return a
        a = a % b
        if (a == 0): return b

# returns a list of all coprime pairs (a, b) where a and b are less
# than n and returns only one of (a, b) and (-a, -b).  (Generically,
# returns the pair with a > 0.)

def coprime_pairs(n):
    pairs = []
    for i in range(1, n+1):
        for x in range(1, i):
            if(gcd(x, i) == 1):
                pairs.append( (x, i) )
                pairs.append( (x, -i) )
        if i == 1: pairs.append( (0, 1) )
        for y in range(-i, i + 1):
            if(gcd(i, y) == 1):
                pairs.append( (i, y) )
    return pairs

#

#----------------------------------------------------------------------
#
# Code for interacting with SnapPeaGUI under OS X
#
#---------------------------------------------------------------------

# Mockup of OS X SnapPea and SnapPeaPython interaction. Since the
# current OS X SnapPea doesn't accept many AppleEvents, we
# use TeXShop instead, since it has good AppleEvent support.  

SnapPeaX_name = "SnapPea"

def execute_applescript( data ):
    script_name = tempfile.mktemp() + ".scpt"
    open(script_name, "w").write(data)
    script = os.popen("osascript " + script_name)
    ans = script.read()
    os.remove(script_name)
    return ans 

def activate_SnapPeaX():
    script = """\
    tell application "%s"
           activate
    end tell""" % SnapPeaX_name
    execute_applescript(script)

# Commands for interacting with SnapPeaX

def copy_to_SnapPeaX(manifold):
    file_name = tempfile.mktemp() + ".tri"
    manifold.save(file_name, 0)
    script = """\
    set f to POSIX file "%s"
    tell application "%s"
          open f
    end tell""" % (file_name, SnapPeaX_name)
    execute_applescript(script)
    activate_SnapPeaX()
             
def get_from_SnapPeaX():
    file_name = tempfile.mktemp() + ".tri"
    script = """\
    on suffix(s, i)
       set len to length of s
       if len < i then
            return s
       else
             return text -i thru -1 of s
       end if
     end suffix

     set f to POSIX file "%s"
     tell application "%s"
          set winName to (name of window 1)
      end tell
      set tail to suffix(winName, 11)
      if tail = "Link Editor" then
          return 0
      end if
      tell application "%s"
           try
              set doc to (document of window 1)
              save doc in f
                  return 1
              on error
                  return 0
              end try
      end tell"""  % (file_name, SnapPeaX_name, SnapPeaX_name)
    ans = int(execute_applescript(script))
    if not ans:
        return None
    manifold =  Triangulation(file_name)
    os.remove(file_name)
    return manifold

def get_all_from_SnapPeaX():
    num_docs = int(execute_applescript(
        'tell application "%s" to count of the documents' % SnapPeaX_name))
    file_names = [tempfile.mktemp() + ".tri" for i in range(num_docs)]
    script = """\
    set savenames to {%s}
    set usednames to {}

    tell application "SnapPea"
           repeat with win in windows
                 set winname to (name of win)
                     set tail to (text -4 thru -1 of winname)
                           if tail = "Main" then
                                set savename to item 1 of savenames
                                set savenames to rest of savenames
                                save (document of win) in POSIX file savename
                                set usednames to usednames & savename
                            end if
	   end repeat
    end tell
    usednames""" % repr(file_names)[1:-1].replace("'", '"')

    used_names = [name.strip() for name in execute_applescript(script).split(",")]
    manifolds =  [Triangulation(file_name) for file_name in used_names]
    [os.remove(file_name) for file_name in used_names]
    return manifolds

# So that SnapPeaX can raise the correct Terminal Window, we signal
# which window this is by changing the title.  

def connect_to_SnapPeaX():
    script = """\
    tell application "Terminal"
    set custom title of front window to "SnapPeaPython"
    end tell
    
    tell application "%s" to activate
    tell application "Terminal" to activate 
    """ % SnapPeaX_name
    execute_applescript(script)

    
def detach_from_SnapPeaX():
    script = """\
    tell application "Terminal"
         set custom title of front window to ""
    end tell"""
    execute_applescript(script)


#AppleScript for raising the correct terminal window
"""\
tell application "Terminal"
	set windowCount to (count of the windows)
	if windowCount is greater than 0 then
		repeat with w from 1 to windowCount
			if custom title of window 1 is "SnapPeaPython" then
				set frontmost of window 1 to true
				activate
				tell application "System Events" to keystroke "  = SnapPea.get_from_SnapPeaX()"
				tell application "System Events" to keystroke "a" using control down
				return
			else
				set frontmost of window 1 to false
			end if
		end repeat
	end if
end tell
"""

# append to Triangulation class

Triangulation.copy_to_SnapPeaX = copy_to_SnapPeaX

#---------------------------------------------

ClosedOrientable = ClosedCensus_list()
ClosedNonorientable = ClosedCensus_list(orientable=0)
Cusped5tet = CuspedCensus_list()
Cusped5tetOrientable = CuspedCensus5tet_list(1)
Cusped5tetNonorientable = CuspedCensus5tet_list(0)
Cusped6tetOrientable = CuspedCensus_list(6)
Cusped6tetNonorientable = CuspedCensus_list(6, orientable=0)
Cusped7tetOrientable = CuspedCensus_list(7)
Cusped7tetNonorientable = CuspedCensus_list(7, orientable=0)
AlternatingKnotExteriors = HT_knot_list('a')
NonalternatingKnotExteriors = HT_knot_list('n')
LinkExteriors2Comp = LinkCensus_list(2)
LinkExteriors3Comp = LinkCensus_list(3)
LinkExteriors4Comp = LinkCensus_list(4)
LinkExteriors5Comp = LinkCensus_list(5)

__all__ = ("Triangulation",
           "ClosedOrientable",
           "ClosedNonorientable",
           "Cusped5tet",
           "Cusped5tetOrientable",
           "Cusped5tetNonorientable",
           "Cusped6tetOrientable",
           "Cusped6tetNonorientable",
           "Cusped7tetOrientable",
           "Cusped7tetNonorientable",
           "LinkExteriors2Comp",
           "LinkExteriors3Comp",
           "LinkExteriors4Comp",
           "LinkExteriors5Comp",
           "AlternatingKnotExteriors", "NonalternatingKnotExteriors",
           "get_manifold", "sendable_to_triangulation", "get_link_complement",
           "get_from_SnapPeaX", "get_all_from_SnapPeaX", "detach_from_SnapPeaX"
           )


