# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
#    This program 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.
#
#    This program 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 this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import gpgme
from errors import errorcheck
import errors, aux
from util import GpgmeWrapper

class Context(GpgmeWrapper):
    """From the GPGME C documentation:

    * All cryptographic operations in GPGME are performed within a
    * context, which contains the internal state of the operation as well as
    * configuration parameters.  By using several contexts you can run
    * several cryptographic operations in parallel, with different
    * configuration.

    Thus, this is the place that you will usually start."""

    def _getctype(self):
        return 'GpgmeCtx'
    
    def __init__(self):
        tmp = gpgme.new_GpgmeCtx_p()
        errorcheck(gpgme.gpgme_new(tmp))
        self.context = gpgme.GpgmeCtx_p_value(tmp)
        self.wrapped = self.context
        gpgme.delete_GpgmeCtx_p(tmp)
        self.last_passcb = None
        self.last_progresscb = None

    def __del__(self):
        self._free_passcb()
        self._free_progresscb()
        gpgme.gpgme_release(self.context)

    def _free_passcb(self):
        if self.last_passcb != None:
            gpgme.pygpgme_clear_generic_cb(self.last_passcb)
            gpgme.delete_PyObject_p_p(self.last_passcb)
            self.last_passcb = None

    def _free_progresscb(self):
        if self.last_progresscb != None:
            gpgme.pygpgme_clear_generic_cb(self.last_progresscb)
            gpgme.delete_PyObject_p_p(self.last_progresscb)
            self.last_progresscb = None

    def op_keylist_start(self, pattern = None, secret_only = 0):
        """Start an iteration over a GPG keyring by using
        the gpgme_op_keylist_start() function.

        You can then use GpgmeCtx.op_keylist_next() to retrieve
        values.  Or, you can use the return value of op_keylist_start --
        an iterator -- to loop over them.  For instance:

        for i in c.op_keylist_start():
            print i

        NOTE: unless you use op_keylist_end() OR you consume all items
        from op_keylist_start, your Context will raise a Busy
        exception if you try to perform any other operation!
        """
        errorcheck(gpgme.gpgme_op_keylist_start(self.context,
                                                  pattern, secret_only))
        return aux.ContextKeysIterator(self)

    def op_keylist_next(self):
        """Returns the next key in the list created
        by a call to op_keylist_start().  The object returned
        is of type Key."""
        ptr = gpgme.new_GpgmeKey_p()
        errorcheck(gpgme.gpgme_op_keylist_next(self.context, ptr))
        key = gpgme.GpgmeKey_p_value(ptr)
        gpgme.delete_GpgmeKey_p(ptr)
        return aux.Key(key)

    def _getnameprepend(self):
        return 'gpgme_'

    def _errorcheck(self, name):
        return name.startswith('gpgme_op')

    def set_passphrase_cb(self, func, hook):
        """Sets the passphrase callback to the function specified by func.

        When the system needs a passphrase, it will call func with two args:
        desc, a string describing the passphrase it needs; and hook,
        the data passed in as hook here.

        The hook argument is mandatory for obscure technical reasons
        (Python hands the C code a 'cell' instead of a tuple if I made
        this optional.)  It is suggested that you pass a None value for
        hook if your called functions do not require a specific hook value.

        Please see the GPGME manual for more information.
        """
        self._free_passcb()
        self.last_passcb = gpgme.new_PyObject_p_p()
        hookdata = (func, hook)
        gpgme.pygpgme_set_passphrase_cb(self.wrapped, hookdata, self.last_passcb)

    def set_progress_cb(self, func, hook):
        """Sets the progress meter callback to the function specified by

        This function will be called to provide an interactive update of
        the system's progress.

        The hook argument is mandatory for obscure technical reasons
        (Python hands the C code a 'cell' instead of a tuple if I made
        this optional.)  It is suggested that you pass a None value for
        hook if your called functions do not require a specific hook value.

        Please see the GPGME manual for more information."""
        print "PROGRESSCBSET"
        self._free_progresscb()
        self.last_progresscb = gpgme.new_PyObject_p_p()
        hookdata = (func, hook)
        gpgme.pygpgme_set_progress_cb(self.wrapped, hookdata, self.last_progresscb)
    
class Data(GpgmeWrapper):
    """From the GPGME C manual:

* A lot of data has to be exchanged between the user and the crypto
* engine, like plaintext messages, ciphertext, signatures and information
* about the keys.  The technical details about exchanging the data
* information are completely abstracted by GPGME.  The user provides and
* receives the data via `GpgmeData' objects, regardless of the
* communication protocol between GPGME and the crypto engine in use.

        This Data class is the implementation of the GpgmeData objects.

        Please see the information about __init__ for instantiation."""

    def _getctype(self):
        return 'GpgmeData'
    
    def _getnameprepend(self):
        return 'gpgme_data_'

    def _errorcheck(self, name):
        if name == 'gpgme_data_get_type' or \
               name == 'gpgme_data_get_encoding':
            return 0
        return 1
    
    def _newptr(self):
        assert self.wrapped == None, \
               "Attempt to re-initialize an existing object %s" % self.wrapped
        assert self.ptr == None, \
               "Attempt to overwrite existing ptr object"
        self.ptr = gpgme.new_GpgmeData_p()

    def _ptrval(self):
        return gpgme.GpgmeData_p_value(self.ptr)

    def _ptrdel(self):
        gpgme.delete_GpgmeData_p(self.ptr)
        del self.ptr

    def __init__(self, string = None, file = None, offset = None,
                 length = None, cb = None):
        """Initialize a new GpgmeData object.

        If no args are specified, make it an empty object.

        If string alone is specified, initialize it with the data
        contained there.

        If file, offset, and length are all specified, file must
        be either a filename or a file-like object, and the object
        will be initialized by reading the specified chunk from the file.

        If cb is specified, it MUST be a tuple of:

        (func, hook)

        where func is a callback function taking two arguments (count,
        hook) and returning a string of read data, or None on EOF.
        This will supply the read() method for the system.

        If file is specified without any other arguments, then
        it must be a filename, and the object will be initialized from
        that file.

        Any other use will result in undefined or erroneous behavior."""
        self.wrapped = None
        self.ptr = None
        self.last_readcb = None

        if cb != None:
            apply(self.new_with_read_cb, cb)
        elif string != None:
            self.new_from_mem(string)
        elif file != None and offset != None and length != None:
            self.new_from_filepart(file, offset, length)
        elif file != None:
            self.new_from_file(file)
        else:
            self.new()

    def new(self):
        self._newptr()
        errorcheck(gpgme.gpgme_data_new(self.ptr))
        self.wrapped = self._ptrval()
        self._ptrdel()

    def new_from_mem(self, string):
        self._newptr()
        errorcheck(gpgme.gpgme_data_new_from_mem(self.ptr, string,
                                                   len(string), 1))
        self.wrapped = self._ptrval()
        self._ptrdel()

    def new_from_file(self, filename, copy = 1):
        self._newptr()
        errorcheck(gpgme.gpgme_data_new_from_file(self.ptr, filename, copy))
        self.wrapped = self._ptrval()
        self._ptrdel()

    def new_with_read_cb(self, func, hook):
        self._newptr()
        self._free_readcb()
        self.last_readcb = gpgme.new_PyObject_p_p()
        hookdata = (func, hook)
        gpgme.pygpgme_data_new_with_read_cb(self.ptr, hookdata,
                                            self.last_readcb)
        self.wrapped = self._ptrval()
        self._ptrdel()

    def new_from_filepart(self, file, offset, length):
        """This wraps the GPGME gpgme_data_new_from_filepart() function.
        The argument "file" may be:

        1. a string specifying a file name, or
        3. a a file-like object. supporting the fileno() call and the mode
           attribute."""

        self._newptr()
        filename = None
        fp = None

        if type(file) == type("x"):
            filename = file
        else:
            fp = gpgme.fdopen(file.fileno(), file.mode)
            if fp == None:
                raise ValueError, "Failed to open file from %s arg %s" % \
                      (str(type(file)), str(file))

        errorcheck(gpgme.gpgme_data_new_from_filepart(self.ptr, filename, fp,
                                                        offset, length))
        self.wrapped = self._ptrval()
        self._ptrdel()

    def write(self, buffer):
        errorcheck(gpgme.gpgme_data_write(self.wrapped, buffer, len(buffer)))

    def read(self, size = -1):
        """Read at most size bytes, returned as a string.
        
        If the size argument is negative or omitted, read until EOF is reached.

        Returns the data read, or the empty string if there was no data
        to read before EOF was reached."""
        
        if size == 0:
            return ''

        if size > 0:
            try:
                result = gpgme.gpgme_data_read_helper(self.wrapped, size)
                errorcheck(result[0])
                return result[1]
            except errors.EOF:
                return ''
        else:
            retval = ''
            while 1:
                try:
                    result = gpgme.gpgme_data_read_helper(self.wrapped, 10240)
                    errorcheck(result[0])
                    retval += result[1]
                except errors.EOF:
                    return retval

    def __del__(self):
        if self.wrapped != None:
            gpgme.gpgme_data_release(self.wrapped)
        self._free_readcb()

    def _free_readcb(self):
        if self.last_readcb != None:
            gpgme.pygpgme_clear_generic_cb(self.last_readcb)
            gpgme.delete_PyObject_p_p(self.last_readcb)
            self.last_readcb = None

class Recipients(GpgmeWrapper):
    """This object is used to identify a set of keys that are:

    a) used as the recipients of an encryption process; or
    b) used for a list of keys to export

    """
    
    def _getctype(self):
        return 'GpgmeRecipients'
    
    def __init__(self):
        ptr = gpgme.new_GpgmeRecipients_p()
        errorcheck(gpgme.gpgme_recipients_new(ptr))
        self.wrapped = gpgme.GpgmeRecipients_p_value(ptr)
        gpgme.delete_GpgmeRecipients_p(ptr)

    def __del__(self):
        gpgme.gpgme_recipients_release(self.wrapped)

    def _getnameprepend(self):
        return 'gpgme_recipients_'

    def _errorcheck(self, name):
        if name == 'gpgme_recipients_count' or \
           name == 'gpgme_recipients_enum_read':
            return 0
        return 1

    def __iter__(self):
        return aux.RecipientsIterator(self)
    
    def add(self, name, validity = None):
        """Adds a new recipient to the list.

        If validity is specified, sets the trust value to it.
        (equivolent to self.add_name_with_validity())

        If validity is not specified, uses the system default.
        (equivolent to self.add_name())
        """

        if validity != None:
            self.add_name_with_validity(name, validity)
        else:
            self.add_name(validity)

    #def remove(self, name):
    #    """Removes a recipient from the list."""
    #
    #    namelist = [x for x in self]
    #    if not name in namelist:
    #        raise KeyError, "Can't remove recipient '%s': not in list" % name

