# -*- coding: iso-8859-1 -*-
"""
    Form.py - MoinMoin macro to display database based forms
    
    @copyright: 2008 Wolfgang Fischer
    @license: GNU GPL, see COPYING for details.

    Information on prerequisites, installation and usage can be found at
        http://moinmo.in/MacroMarket/Form
        
    2008-07-29 RudolfReuter, Version 0.9alpha because of moin-1.7.1 changes 
      Attention: Python >= 2.5 needed for SQLite access
      debug help for tracing, insert the following line
          import pdb; pdb.set_trace() #debug
      changes in moin-1.7.1:
      1. MoinMoin/wikidicts.py - Function get_dict was dropped
      2. MoinMoin/wikiutil.py - Function renderText, 4th parameter dropped
      Added ".type" check in first Form element. 2008-07-31
    2008-12-28 RudolfReuter, Version 0.10alpha because of moin-1.8.1 changes 
      added dict check xxx/FormsDict for Linux version at #Get form dict
    2010-01-28 RudolfReuter, adopted to 1.9.1 (Werkzeug), version 0.11alpha
    2014-06-11 TestTest, adopted to 1.9.7, version 0.12alpha
"""
Dependencies = ['external']  # Can't be cached as it depends on external data

def execute(macro, args):
    args = [ arg.strip() for arg in args.split(',') ]
    request = macro.request
    if not hasattr(request, 'form_macro_form'):
        request.form_macro_form = Form(request)
    return request.form_macro_form.render(args)


class Form:
    import datetime
    from MoinMoin import wikiutil

    # fake _ function to get gettext recognize those texts:
    _ = lambda x: x
    ui_text = {
        # TODO: Button labels should be defined here
        }
    del _

    datetime_types = [ datetime.datetime, datetime.date, datetime.time ]


    def __init__(self, request):
        self.request = request
        self.formatter = request.formatter
        self.post_args = request.values
        self.date_format = request.user.date_fmt or request.cfg.date_fmt
        self.datetime_format = request.user.datetime_fmt or request.cfg.datetime_fmt
        today = self.datetime.date.today()
        self.variables = {
            'PAGE': request.page.page_name,
            'NOW' : self.datetime.datetime.now(),
            'TODAY' : today,
            'TODAY_DATETIME' : self.datetime.datetime.fromordinal(today.toordinal()),
            'ME': request.user.name,
        }
        self.forms = {}
        self.dict_name_stack = []
        self.last_form_key = ''
        
        from MoinMoin import version
        self.is_moin_1_7 = version.release.startswith('1.7')
        self.is_moin_1_8 = version.release.startswith('1.8')
        self.is_moin_1_9 = version.release.startswith('1.9')

        # Extract information on post action
        self.action = u''
        self.action_form = u''
        self.action_index = 0
        for k in self.post_args.keys():
            if k.startswith('_Action_') and len(k) > 18:
                self.action = k[8:11]
                self.action_index = int(k[12:16])
                self.action_form = k[17:]



    # =============================
    # Render form elements
    # =============================
    
    def render(self, args):
        if not args:
            return self.error_message('Missing arguments')
        element = args[0].lower()
        if element == 'start':
            return self.render_start()
        elif element == 'end':
            return self.render_end()
        request = self.request
        post_args = self.post_args

        if len(args) > 1:  # key specified
            key = self.key = args[1]
        else:
            key = u''
        self.dict_name, self.form_name, self.field_name = self.parse_element_key(key, element)
        dict_name = self.dict_name
        form_name = self.form_name
        self.form_key = form_key = u'%s.%s' % (dict_name, form_name)
        self.last_form_key = self.form_key
        if form_key not in self.forms:  # First element of this form
            form = self.form = self.forms[form_key] = {}
            form['form_name'] = form_name
            form['dict_name'] = dict_name
            # Get form dict
            if self.is_moin_1_7 or self.is_moin_1_8:
                if not request.dicts.has_dict(dict_name): # fix for Linux, RudolfReuter 2008-12-28
                    request.dicts.adddict(request, dict_name)

            if self.is_moin_1_7 or self.is_moin_1_8:
                form_dict_obj = request.dicts.dict(dict_name) 
            elif self.is_moin_1_9:
                form_dict_obj = request.dicts[dict_name] 
            #if not hasattr(form_dict_obj, 'get_dict'): # 'get_dict' was dropped in moin-1.7.1
            #    form['dict'] = { }
            #    return self.error_message('The form dictionary "%s" does not exist or is not valid' % dict_name)
            if not form_dict_obj.has_key('.type'):
                form['dict'] = { }
                return self.error_message('The form dictionary "%s" does not exist or is not valid' % dict_name)
            #form['dict'] = form_dict_obj.get_dict()
            form['dict'] = form_dict_obj
            form['addonly'] = self.get_stripped_from_form_dict('addonly') == u'1'
            # Connect to data source
            error = self.connect()
            if error:
                return self.error_message(error)

            # Fetch data
            error = self.fetch_data()
            if error:
                return self.error_message(error)

            if form_key == self.action_form:
                # Do form action
                self.do_action()
                # Fetch updated data
                self.fetch_data()
            # Close connection
            self.connection.close()
            
            records = form['records']
            form['add_mode'] = (post_args.has_key('_Action_New_0000_%s' % form_key) or not records) and self.get_stripped_from_form_dict('insert')
            # Set index of current record
            self.set_record_index()
            # Add record values to variables dict
            # if form_key not in post_args.get('form', []):
            index = form['index']
            if 0 < index <= len(records):
                record = records[index-1]
            else:
                record = value = None
            for (field, field_data) in form['fields'].iteritems():
                field_type = field_data[1]
                if record:
                    value = record[field_data[0]]
                    if field_type not in self.datetime_types and field_type:
                        value = field_type(value)
                self.variables['%s.%s' % (form_key, field)] = value
           
        self.form = self.forms[form_key]
        
        if element == 'form':
            return self.render_form()
        elif element == 'buttons':
            return self.render_buttons()
        elif element == 'navigation':
            return self.render_navigation()
        elif element == 'filter':
            return self.render_filter()
        elif element == 'next':
            return self.render_next()
        else:
            return self.render_field(element, args)

        
    def render_form(self):
        # TODO: handle recursion
        # TODO: handle form body doesn't exist
        form = self.form
        request = self.request
        form_text = [ self.get_stripped_from_form_dict('header').replace('\\n', '\n') % self.variables ]
        form_body = self.get_stripped_from_form_dict('body').replace('\\n', '\n') % self.variables
        if self.get_stripped_from_form_dict('all'):
            records = form['records']
            count = [len(records), len(records) + 1][self.get_stripped_from_form_dict('insert') != u'']
            for i in range(count):
                form_text.append(form_body)
        else:
            form_text.append(form_body)
        form_text.append(self.get_stripped_from_form_dict('footer').replace('\\n', '\n') % self.variables)
        # Load the parser
        Parser = self.wikiutil.searchAndImportPlugin(request.cfg, "parser", 'wiki')
        self.dict_name_stack.append(self.dict_name)
        # change in moin-1.7.1 MoinMoin/wikiutil.py line 2476, 4th parameter dropped
        #result = self.wikiutil.renderText(request, Parser, u''.join(form_text), line_anchors=False)
        result = self.wikiutil.renderText(request, Parser, u''.join(form_text))
        self.dict_name_stack.pop()
        return result
        
        
    def render_start(self):
        return self.formatter.rawHTML('<span><form action="" method="POST" enctype="multipart/form-data">')

        
    def render_end(self):
        return self.formatter.rawHTML('</form></span>')
  
    
    def render_next(self):
        form = self.form
        form['index'] += 1
        form['add_mode'] = form['index'] > len(form['records'])
        return u''


    def render_field(self, element, args):
        form = self.form
        field_name = self.field_name
        field_key = '%s.%s' % (self.form_key, field_name)
        value = self.wikiutil.escape(self.get_field_value(self.form_key, field_name, field_key) or '', 1)
        
        if element == 'value':
            return self.formatter.rawHTML(u'%s<input type="hidden" name="%s" value="%s">' % (value, field_key, value))

        elif element == 'textarea':
            if len(args) > 2:
                cols = args[2]
            else:
                cols = u''
            if len(args) > 3:
                rows = args[3]
            else:
                rows = u''
            return self.formatter.rawHTML(u'<textarea name="%s" cols="%s" rows="%s">%s</textarea>' % (field_key, cols, rows, value))

        elif element == 'hidden':
            return self.formatter.rawHTML(u'<input type="hidden" name="%s" value="%s">' % (field_key, value))

        else:
            if len(args) > 2:
                size = args[2]
            else:
                size = u''
            return self.formatter.rawHTML(u'<input type="text" name="%s" size="%s" value="%s">' % (field_key, size, value))


    def get_field_value(self, form_key, field_name, field_key, for_action = 0):
        if field_name.startswith('@'):
            value = self.post_args.get(field_key, None)
        elif not for_action:
            form = self.forms[form_key]
            index = form.get('index', 0)
            records = form.get('records', [])
            if index > len(records):
                value = None
            else:
                value = records[index-1][form['fields'][field_name][0]]
        else:
            if form_key == self.action_form:
                index = self.action_index
            else:
                index = 0 
            value = self.post_args.getlist(field_key)[index]
            form = self.forms[form_key]
            field_type = form['fields'][field_name][1]
            if field_type:
                value = field_type(value)
        if value == '':
            value = None
        return value


    def render_navigation(self):
        form = self.form
        form_key = self.form_key
        return self.formatter.rawHTML("""
                    <input type="submit" name="First_%s" value="|<">
                    <input type="submit" name="Previous_%s" value="<">
                    <input type="text" name="GoTo_%s" size="3" value="%s">
                    <input type="submit" name="Next_%s" value=">">
                    <input type="submit" name="Last_%s" value=">|"> of %s
                    """ % (form_key, form_key, form_key, str(form['index']), form_key, form_key, len(form['records'])))


    def render_filter(self):
        formatter = self.formatter
        return formatter.rawHTML('<input type="submit" name="_Action_App_0000" value="Filter"><input type="reset">')


    def render_buttons(self):
        formatter = self.formatter
        if self.get_stripped_from_form_dict('all'):
            action_index = self.form['index'] - 1
        else:
            action_index = 0
        action_key = ('0000%i_' % action_index)[-5:]
        action_key += self.form_key
        result = []
#        if self.form['unbound']:
#            result.append(formatter.rawHTML('<input type="submit" name="_Action_App_%s" value="Filter"><input type="reset">' % (action_key)))
        if self.form['add_mode']:
            result.append(formatter.rawHTML('<input type="submit" name="_Action_Add_%s" value="Add"><input type="submit" name="_Action_Can_%s" value="Cancel">' % (action_key, action_key)))
        else:
            if self.get_stripped_from_form_dict('update'):
                result.append(formatter.rawHTML('<input type="submit" name="_Action_Sav_%s" value="Save">' % action_key))
            if self.get_stripped_from_form_dict('delete'):
                result.append(formatter.rawHTML('<input type="submit" name="_Action_Del_%s" value="Delete">' % action_key))
            if self.get_stripped_from_form_dict('insert') and not self.get_stripped_from_form_dict('all'):
                result.append(formatter.rawHTML('<input type="submit" name="_Action_New_%s" value="New">' % action_key))
        return u''.join(result)



    # =============================
    # DataBase communication
    # =============================

    def fetch_data(self):
        select_query = self.get_stripped_from_form_dict('select')
        if select_query:
            parameters = self.get_query_parameters('select')
        else:
            return 'The form "%s" does not contain a select query' % self.form_key
        cursor = self.connection.cursor()
        cursor.execute(select_query, parameters)
        description = cursor.description
        form = self.form
        form['fields'] = dict([(description[i][0], [i, description[i][1]]) for i in range(len(description))])
        form['records'] = [cursor.fetchall(), []][form['addonly']]
        cursor.close()
        return None


    def set_record_index(self):
        form = self.form
        form_key = self.form_key
        post_args = self.post_args
        records = form['records']
        # Calculate index of current record (first record is 1)
        if form['add_mode']:
            index = len(records) + 1
        elif 'GoTo_%s' % form_key in post_args:
            index = int(post_args['GoTo_%s' % form_key][0])
            if 'First_%s' % form_key in post_args:
                index = 1
            elif 'Previous_%s' % form_key in post_args:
                index -= 1
            elif 'Next_%s' % form_key in post_args:
                index +=1
            elif 'Last_%s' % form_key in post_args:
                index = len(records)
            if index < 1:
                index = 1
            elif index > len(records):
                index = len(records)
        else:
            goto_fields = self.get_stripped_from_form_dict('goto_fields')
            if goto_fields:
                fields = form['fields']
                goto_fields = [ field.strip() for field in goto_fields.split(',') ]
                variables = self.variables
                def get_value(value, type, variables):
                    value = value.strip().strip('"\'')
                    if value in variables:
                        return variables[value]
                    else:
                        value = value % variables
                    if type == self.datetime.datetime:
                        return self.datetime.datetime.strptime(value, self.datetime_format)
                    elif type == self.datetime.date:
                        return self.datetime.datetime.strptime(value, self.date_format)
                    return type(value)
                goto_values_list = self.get_stripped_from_form_dict('goto_values').split(',')
                goto_values = [ (get_value(goto_values_list[i], fields[goto_fields[i]][1], variables), fields[goto_fields[i]][0]) for i in range(len(goto_values_list)) ]
                index = 0
                for record_index in range(len(records)):
                    equal = True
                    for value in goto_values:
                        equal = value[0] == records[record_index][value[1]]
                        if not equal:
                            break
                    if equal:
                        index = record_index + 1
                        break
                if not index:  # No matching record found
                    index = 1  # Set to first record (default)
            else:
                index = 1  # Set to first record (default)
        form['index'] = index


    def do_action(self):
        cursor = self.connection.cursor()
        action = self.action
        action_query_map = {'Add' : 'insert', 'Sav' : 'update', 'Del' : 'delete'}
        if action in action_query_map:
            query_type =action_query_map[action] 
            query = self.get_stripped_from_form_dict(query_type) 
            if query:
                parameters = self.get_query_parameters(query_type, for_action = 1)
                count = cursor.execute(query, parameters)
        self.connection.commit()
        cursor.close()

        
    def connect(self):
        dict_name, self.connection_name = self.parse_connection_key(self.get_stripped_from_form_dict('connection'))
        if self.is_moin_1_7 or self.is_moin_1_8:
            connection_dict_obj = self.request.dicts.dict(dict_name)
        elif self.is_moin_1_9:
            connection_dict_obj = self.request.dicts[dict_name] 
        #if hasattr(connection_dict_obj, 'get_dict'): # 'get_dict' was dropped in moin-1.7.1
        self.connection_dict = connection_dict = connection_dict_obj
        #else:
            #return 'The connection dictionary "%s" does not exist or is not valid' % dict_name

        connection_name_prefix = '%s.' % self.connection_name
        connection_type = self.get_stripped_from_connection_dict('type').lower()

        if connection_type == 'sqlite':
            import sqlite3
            connection_string = self.get_stripped_from_connection_dict('file')
            if connection_string.startswith('attachment:'):
                from MoinMoin.action import AttachFile
                pagename, filename = AttachFile.absoluteName(connection_string[11:], dict_name)
                connection_string = AttachFile.getFilename(self.request, pagename, filename)
            self.connection = sqlite3.connect(connection_string)

        elif connection_type == 'odbc':
            import pyodbc
            connection_string = self.get_stripped_from_connection_dict('connection_string')
            self.connection = pyodbc.connect(connection_string)

        elif connection_type == 'mysql':
            import MySQLdb
            port = connection_dict.get('port', '3306').strip('`')
            self.connection = MySQLdb.connect(host = self.get_stripped_from_connection_dict('server'), port = port, user = self.get_stripped_from_connection_dict('uid'), passwd = self.get_stripped_from_connection_dict('pwd'),db = self.get_stripped_from_connection_dict('database'))

        elif connection_type == 'oracle':
            import cx_Oracle
            if 'port' in parameters:
                port = parameters['%sport' % connection_name_prefix]
            else:
                port = 1521
            self.connection = cx_Oracle.connect(self.get_stripped_from_connection_dict('uid'), self.get_stripped_from_connection_dict('pwd'), cx_Oracle.makedsn(self.get_stripped_from_connection_dict('server'), port, self.get_stripped_from_connection_dict('database')))

        else:
            return 'Connection type "%s" specified in "%s" is unknown' % (connection_type, dict_name)

        return None



    # =============================
    # Handling Form Dictionaries
    # =============================

    def parse_element_key(self, key, element):
        key_parts = key.split('.')
        if element in ['form', 'buttons', 'navigation', 'filter', 'next']:
            key_parts.extend([u'', u'', u''])
        else:
            if len(key_parts) == 2:
                key_parts.insert(0, u'')
            elif len(key_parts) < 2:
                pass  # TODO: raise error - field not specified correctly
        dict_name, form_name, field_name = key_parts[:3]
        if not (form_name or dict_name):
            if self.last_form_key:
                form_key = self.last_form_key
                form = self.forms[form_key]
                dict_name, form_name = form['dict_name'], form['form_name']
            else:
                pass  # TODO: raise error - unspecified form
        elif not dict_name:
            if self.dict_name_stack:
                dict_name = self.dict_name_stack[-1]
            else:
                dict_name = self.request.page.page_name
        return dict_name, form_name, field_name

        
    def parse_connection_key(self, key):
        key_parts = key.split('.')
        key_parts.extend([u'', u''])
        dict_name, connection_name = key_parts[:2]
        if not dict_name:
            dict_name = self.dict_name
        return dict_name, connection_name


    def get_query_parameters(self, key, for_action = 0):
        # In this order:
        # 1. variables values
        # 2. form values (if not current)
        # 3. post values
        form_key = self.form_key
        variables = self.variables 
        result = []
        parameters = self.get_stripped_from_form_dict('%s_parameters' % key)
        if parameters:
            for par in parameters.split(','):
                par = par.strip()
                if variables.has_key(par):
                    value = variables[par]
                else:
                    key_parts = par.strip().split('.')
                    if len(key_parts) == 2:
                        key_parts.insert(0, u'')
                    elif len(key_parts) < 2:
                        pass  # TODO: raise error - parameter not specified correctly
                    dict_name, form_name, field_name = key_parts[:3]
                    if not (form_name or dict_name):
                        dict_name, form_name = self.dict_name, self.form_name
                    elif not dict_name:
                        dict_name = self.dict_name
                    parameter_form_key = u'%s.%s' % (dict_name, form_name)
                    parameter_key = u'%s.%s' % (parameter_form_key, field_name)
                    if self.forms.has_key(parameter_form_key):
                        value = self.get_field_value(parameter_form_key, field_name, parameter_key, for_action = for_action)
                result.append(value)
        return result

            
        
    def get_stripped_from_form_dict(self, key):
        return self.form['dict'].get('%s.%s' % (self.form_name, key), u'').strip('`')

        
    def get_stripped_from_connection_dict(self, key):
        return self.connection_dict.get('%s.%s' % (self.connection_name, key), u'').strip('`')

        

    # =============================
    # Error Message
    # =============================
    
    def error_message(self, message):
        formatter = self.formatter
        result = [ '', formatter.preformatted(True) ]
        message = self.wikiutil.escape(message, 1)
        result.append(formatter.rawHTML("Form macro error: %s" % message))
        result.append(formatter.preformatted(False))
        return u'\n'.join(result)

