#
# Copyright (c) 2002, 2003 Art Haas
#
# This file is part of PythonCAD.
# 
# PythonCAD is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# PythonCAD is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with PythonCAD; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#
# linetype class
#

import types
from sys import maxint

import globals

class Linetype(object):
    """A class representing linetypes.

This class provides the basis for solid and dashed lines.
Dashed lines are more "interesting" in the various ways
the dashes can be drawn.

A Linetype object has two attributes:

name: A string describing this linetype
dlist: A list of integers used for drawing dashed lines

A solid line has a dlist attribute of None.

A Linetype object has the following methods:

getName(): Return the Linetype name
getList(): Return the Linetype dashlist
clone(): Make a copy of a Linetype object.
    """
    def __init__(self, name, dashlist=None):
        """Initstantiate a Linetype object.

Linetype(name, dashlist)

The name should be a string representing what this linetype
is called. The dashlist can be None (solid lines) or a list of
integers which will be used to determine the on/off bits
of a dashed line. The list must have at least two integers.
        """
        _n = name
        if not isinstance(_n, types.StringTypes):
            raise TypeError, "Invalid Linetype name: " + `_n`
        if not isinstance(_n, unicode):
            _n = unicode(name)
        _l = dashlist
        if _l is not None:
            if not isinstance(_l, list):
                raise TypeError, "Second argument must be a list or None."
            _length = len(_l)
            if _length < 2:
                raise ValueError, "Dash list must hold at least two integers."
            _temp = []
            for _i in range(_length):
                _item = _l[_i]
                if not isinstance(_item, int):
                    _item = int(_l[_i])
                if _item < 0:
                    raise ValueError, "Invalid list value: %d" % _item
                _temp.append(_item)
            _l = _temp
        self.__name = _n
        if _l is None:
            self.__list = None
        else:
            self.__list = _l[:]

    def __str__(self):
        if self.__list is None:
            return "Solid Line: '%s'" % self.__name
        else:
            str = "Dashed Line: '%s': " % self.__name
            return str + `self.__list`
        
    def __eq__(self, obj):
        """Compare one Linetype to another for equality.

The comparison examines the dash list - if both lists
are None, or the same length and with identical values,
the function returns True. Otherwise the function returns
False.
        """
        if not isinstance(obj, Linetype):
            raise TypeError, "Invalid Linetype comparison: " + `obj`
        _val = False
        _objlist = obj.getList()
        if self.__list is None and _objlist is None:
            _val = True
        elif self.__list is not None and _objlist is not None:
            if self.__list == _objlist:
                _val = True
        return _val

    def __ne__(self, obj):
        """Compare one Linetype to another for non-equality.

The comparison examines the dash list - if both lists
are None, or the same length and with identical values,
the function returns False. Otherwise the function returns
True.
        """
        if not isinstance(obj, Linetype):
            raise TypeError, "Invalid Linetype comparison: " + `obj`
        _val = True
        _objlist = obj.getList()
        if self.__list is None and _objlist is None:
            _val = False
        elif self.__list is not None and _objlist is not None:
            if self.__list == _objlist:
                _val = False
        return _val

    def __hash__(self):
        """Provide a hash function for Linetypes.

Defining this method allows Linetype objects to be stored
as dictionary keys.
        """
        _dashlist = self.__list
        _val = 0 # hash value for solid lines
        if _dashlist is not None:
            _val = hash_dashlist(_dashlist)
        return _val
    
    def getName(self):
        """Get the name of the Linetype.

getName()        
        """
        return self.__name

    name = property(getName, None, None, "Linetype name.")

    def getList(self):
        """Get the list used for determining the dashed line pattern.

getList()
This function returns None for solid Linetypes.
        """
        _list = None
        if self.__list is not None:
            _list = self.__list[:]
        return _list

    list = property(getList, None, None, "Linetype dash list.")

    def clone(self):
        """Make a copy of a Linetype

clone()        
        """
        _name = self.__name[:]
        _dashlist = None
        if self.__list is not None:
            _dashlist = self.__list[:]
        return Linetype(_name, _dashlist)

#
# LinetypeDict Class
#
# The LinetypeDict is built from the dict object. Using instances
# of this class will guarantee than only Linetype objects will be
# stored in the instance
#

class LinetypeDict(dict):
    def __init__(self):
        dict.__init__(self)
        
    def __setitem__(self, key, value):
        if not isinstance(key, Linetype):
            raise TypeError, "LinetypeDict keys must be Linetype objects: " + `key`
        if not isinstance(value, Linetype):
            raise TypeError, "LinetypeDict values must be Linetype objects: " + `value`
        dict.__setitem__(self, key, value)

#
# the hash function for linetype dashlists
#

def hash_dashlist(dashlist):
    """The hashing function for linetype dashlists

hash_dashlist(dashlist)

Argument "dashlist" should be a list containing two or
more integer values.
    """
    if not isinstance(dashlist, list):
        raise TypeError, "Invalid list: " + `dashlist`
    if len(dashlist) < 2:
        raise ValueError, "Invalid list length: " + str(dashlist)
    _dlist = []
    for _obj in dashlist:
        if not isinstance(_obj, int):
            raise TypeError, "Invalid list item: " + `_obj`
        if _obj < 0:
            raise ValueError, "Invalid list value: %d" % _obj
        _dlist.append(_obj)
    _val = (0xffffff & _dlist[0]) << 6
    for _i in _dlist:
        _val =  c_mul(160073, _val) ^ ((_i << 5) | _i) # made this up
    _val = _val ^ len(_dlist)
    if _val == -1:
        _val = -2
    return _val

#
# mulitplication used for hashing
#
# an eval-based routine came from http://effbot.org/zone/python-hash.htm,
# but the move to Python 2.3 printed warnings when executing that code
# due to changes in large hex values, so the code was re-written to
# avoid the eval-loop and hackish long->str->int conversions ...
#

def c_mul(a, b):
    _lval = (long(a * b) & 0xffffffffL) - maxint
    return int(_lval)

#
# find an existing linetype in the global linetype dictionary
#

def get_linetype_by_dashes(dashlist):
    if dashlist is None:
        _key = 0
    elif isinstance(dashlist, list):
        _key = hash_dashlist(dashlist)
    else:
        raise TypeError, "Invalid dashlist: " + `dashlist`
    return globals.linetypes.get(_key)
        
def get_linetype_by_name(name):
    if not isinstance(name, types.StringTypes):
        raise TypeError, "Invalid linetype name: " + str(name)
    _name = name
    if not isinstance(_name, unicode):
        _name = unicode(name)
    for _lt in globals.linetypes:
        if _lt.getName() == _name:
            return _lt
    return None
