"""

Invoice Machine API Wrapper
===========================

By: Martin Buhr

You can use this wrapper to access the inner workings of Invoice Machine's
API (http://invoicemachine.com/home) - feel free to alter this code or improve it.

For a brief intro visit the site at:
http://www.lonelycode.com/?p=294

Feedback is appreciated, the only conditions of use are:
- If you;re using the API, let me know
- If you make any major improvements that you think should be included, let me know!
- If you publish a better/faster/stronger version - let me know :-)

"""

INVOICE_MACHINE_URL = "http://machine_id.invoicemachine.com/api/"

class endpoints:
    """ Defines the endpoints for the API"""
    
    # Invoices
    invoice_new = "invoice.new"
    invoice_get = "invoice.get"
    invoice_getall = "invoice.getall"
    invoice_remove = "invoice.remove"
    
    # Clients
    client_new = "client.new"
    client_get = "client.get"
    client_getall = "client.getall"
    
    # Items
    item_new = "item.new"
    item_remove = "item.remove"
    
    

class api_connector:
    """ Acts asa  bridge to the API - will POST mesages and params
        to the API, must be initialised with token """
    
    def __init__(self, api_token=None):
        self.api_token = api_token
    
    def POST_url(self, endpoint, params={}):
        """ Generic handler for the GET requests, will auto-insert auth
            tokens if they exist """
            
        if self.api_token:
            params['api_token'] = self.api_token
        
        
        import urllib, urllib2, simplejson
        params = urllib.urlencode(params)
        endpoint = endpoint
        
        response = urllib2.urlopen(endpoint, params)
        ret = response.read()
        
        return ret
    
class apiObject:
    """ Generic API object, all other objects inherit
        from this base object, all data is stored in a
        list called 'data' """
        
    def __init__(self, xml=None, get=True, **kwargs):
        self.data={}
        self.get = get
        
        for key in kwargs:
            self[key] = kwargs[key]
        
        if xml:
            self = self.from_xml(xml)
    
    def __getitem__(self, key):
        try:
            return self.data[key]
        except:
            return None
    
    def __setitem__(self, key, item):
        self.data[key] = item
        
    def from_xml(self, xml):
        from xml.dom.minidom import parseString
        xmlobj = parseString(xml)
        
        if self.keyset:
            for key in self.keyset:
                try:
                    self[key] = xmlobj.getElementsByTagName(key)[0].childNodes[0].data
                except:
                    self[key] = None
        
        return self
        
    def get_params(self):
        params = {}
        
        for key in self.data:
            params[key] = self[key]
        
        if self.get:
            params['get'] = 'true'
        
        return params
    
class im_item(apiObject):
    """ Represents an item (or line) in the api,
        can be used to add items to the ssytem or
        add items to an invoice """
        
    def __init__(self, xml=None, get=True, **kwargs):
        self.kinds = ['hour',
                  'day',
                  'service',
                  'product']
        
        self.keyset = ['qty',
                       'kind',
                       'description',
                       'price',
                       'total',
                       'id']
        
        
        
        if 'kind' in kwargs:
            if kwargs['kind'] not in self.kinds:
                return False
            
        apiObject.__init__(self, xml, **kwargs)
        
        self.qty = None
        self.kind = None
        self.description = None
        self.price = None
        self.total = None
        self.id  = None
        
        if xml:
            try:
                self.qty = self['qty']
                self.kind = self ['kind']
                self.description = self['description']
                self.price = self['price']
                self.total = self['total']
                self.id = self['id']
            except:
                pass
    

class im_client(apiObject):
    """ Represents a client object in InvoiceMachine """
    
    def __init__(self, xml=None, get=True, **kwargs):
        self.keyset = ['name',
                       'email',
                       'company',
                       'address_line_01',
                       'address_line_02',
                       'address_line_03',
                       'city',
                       'country',
                       'id']
        apiObject.__init__(self, xml, **kwargs)
        
class im_invoice(apiObject):
    """ Represents an INvoice from InvoiceMachine, this is
        NOT used to create invoices - only to access them """
    
    # The API uses different parameters for creating invoices
    # anyone brave enough to try and consolidate that into this class
    # be my guest
    
    def __init__(self, xml=None, get=True, **kwargs):
        self.keyset = ['id',
                       'status',
                       'date',
                       'due_date',
                       'invoice_id',
                       'po',
                       'due',
                       'tax_rate',
                       'discount_rate',
                       'shipping_amount',
                       'currency_symbol',
                       'currency_code',
                       'notes',
                       'permalink',
                       'download_link',
                       'payment_link',
                       'client_id',
                       'client_name',
                       'client_email',
                       'client_company',
                       'lines',
                       'subtotal',
                       'tax',
                       'discount',
                       'total',
                       'paid',
                       'balance_due']
        
        self.lines = []
        
        apiObject.__init__(self, xml, **kwargs)
        
    def from_xml(self, xml):
        self.data['lines'] = []
        from xml.dom.minidom import parseString
        xmlobj = parseString(xml)
        
        if self.keyset:
            for key in self.keyset:
                if key == 'lines':
                    #deal with lines
                    lines = xmlobj.getElementsByTagName('line')
                    for line in lines:
                        bits = line.childNodes
                        x = "<line>"
                        for bit in bits:
                            x += bit.toxml()
                            
                        x += '</line>'
                        newline = im_item(xml=x)
                        self.data['lines'].append(newline)
                        self.lines.append(newline)
                
                else:
                    try:
                        self[key] = xmlobj.getElementsByTagName(key)[0].childNodes[0].data
                    except:
                        self[key] = None
    
class impy:
    """ InvoiceEngine main class - use this to perform functions within
        the IM webapp """
        
    def __init__(self, token, machine):
        self.token = token
        self.machine = machine
        self.url = INVOICE_MACHINE_URL.replace('machine_id', self.machine)
    
    def create_invoice(self, client, items, due=30, date=None, send=False):
        import datetime
        params = {}
        params['client'] = client
        params['get'] = 'true'
        if date:
            params['date'] = date
            
        if send:
            params['send'] = 'true'
        
        params['due'] = due
        
        c = 1
        for item in items:
            p = "line[%s]" % c
            
            pat = p + "[qty]"
            params[pat] = item['quantity']
            ##print pat
            
            pat = p + "[kind]" 
            params[pat] = item['kind']
            #print pat
            
            pat = p + "[description]" 
            params[pat] = item['description']
            #print pat
            
            pat = p + "[price]" 
            params[pat] = item['price']
            #print pat
            
            c += 1
        
        endpoint = self.url + endpoints.invoice_new
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)
        
        return ret
        
    def create_client(self, client_obj):
        endpoint = endpoint = self.url + endpoints.client_new
        
        params = client_obj.get_params()
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)
        
        return im_client(xml=ret)
    
    def get_client(self, id):
        params = {}
        endpoint = endpoint = self.url + endpoints.client_get
        
        params['id'] = id
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)

        client = im_client(xml=ret)
        
        return client
    
    def create_item(self, item):
        endpoint = endpoint = self.url + endpoints.item_new
        
        params = item.get_params()
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)
        
        newitem = item.from_xml(ret)
        return newitem
    
    def remove_item(self, itemid):
        params = {}
        endpoint = endpoint = self.url + endpoints.item_remove
        
        params['id'] = itemid
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)
        
        return ret
    
    def get_invoice(self, invoiceid):
        params = {}
        endpoint = self.url + endpoints.invoice_get
        
        params['id'] = invoiceid
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)
        
        invoice = im_invoice(xml=ret)
        return invoice
    
    def remove_invoice(self, invoiceid):
        params = {}
        endpoint = self.url + endpoints.invoice_remove
        
        params['id'] = invoiceid
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)
        
        return ret
    
    def get_clients(self):
        params = {}
        endpoint = self.url + endpoints.client_getall
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)
        
        from xml.dom.minidom import parseString
        xmlobj = parseString(ret)
        
        clients = xmlobj.getElementsByTagName('client')
        ret_clients = []
        for c in clients:
            newclient = im_client(xml=c.toxml())
            ret_clients.append(newclient)
        
        return ret_clients
    
    def get_invoices(self):
        params = {}
        endpoint = self.url + endpoints.invoice_getall
        #print endpoint
        
        connector = api_connector(self.token)
        ret = connector.POST_url(endpoint, params)
      
        try:
            from xml.dom.minidom import parseString
            xmlobj = parseString(ret)
            invoices = xmlobj.getElementsByTagName('invoice')
        except:
            invoices = None
            
        if invoices:
            ret_inv = []
            for i in invoices:
                newinv = im_invoice(xml=i.toxml())
                ret_inv.append(newinv)
            
            return ret_inv
        else:
            return []
