# coding: windows-1252
#
#---Bezeichnung: Modul: VertecCSVExporterClasses
#   Klassen:
#   CondExpression:
#   ObjectScript: N
#   ContainerScript: N
#   EventType: Kein
#   EventClass: 
#   EventMembers:
#   ExtendedRights: N
#---Modul welches den CSV Exporter enthält.

import locale
import vtcapp


class OclCsvRecord(object):
    """
    Record structure which represents one line in a CSV "file".
    The fields list is defined via the Exporter object that 
    instantiates this object.

    The OCL expression is evaluated at construction time, but the fields can also be 
    filled "manually" in Python when a more complex calculation is required:

    row.fieldname = value

    """

    def __init__(self, exporter, vertec_object=None):
        # we access member variables through __dict__ to avoid confusion
        # with the overwritten __getattr__ and __setattr__.
        self.__dict__['exporter'] = exporter
        self.__dict__['values'] = {}

        # initilize the field default values
        for key, default, field_type, ocl_expression in self.exporter.fields:
            self.values[key] = default

        # Evaluate the OCL expressions.
        self.__dict__['vertec_object'] = vertec_object
        self.eval_field_OCLs()

    def __getattr__(self, key):
        # __getattr__ is called when an attribute lookup has not found the attribute in the usual places.
        # That's why self.exporter still works!
        if key in self.values.keys():
            return self.values.get(key, None)

    def __setattr__(self, key, value):
        # If we find the key as fieldname, return the current value
        if key in self.values.keys():
            self.values[key] = value
        else:
            raise AttributeError('record has not attribute %s' % key)

    def field_type(self, searched_key):
        for key, default_value, field_type, ocl_expression in self.exporter.fields:
            if searched_key == key:
                return field_type


    def field_ocl(self, searched_key):
        for key, default_value, field_type, ocl_expression in self.exporter.fields:
            if searched_key == key:
                return ocl_expression

    def eval_field_OCLs(self):
        if self.vertec_object:
            for key, default_value, field_type, ocl_expression in self.exporter.fields:
                if ocl_expression:
                    self.values[key] =self.vertec_object.eval(ocl_expression)

    def field_value_as_string(self, key):
        # returns the value of a field as a unicode string.
        field_type = self.field_type(key)
        value = self.values.get(key, None)
        return_value = ""
        # not "if value:" since an integer can be 0 and still a valid value.
        if not value is None:
            if field_type == 'string':
                # is delegated to the exporter. 
                return_value = self.exporter.format_string(value)

            elif field_type == "float":
                return_value = '%.2f' % value

            elif field_type == "floatlocale":
                import locale
                locale.setlocale(locale.LC_ALL, '')
                return_value = locale.format('%.2f', value, True)

            elif field_type == "date":
                return_value = value.strftime('%d.%m.%Y')

            else:
                return_value = str(value)

        return unicode(return_value)

    def __str__(self):
        # returns the record as a (CSV formatted) string.
        stringlist = []
        for key, default_value, field_type, ocl_expression in self.exporter.fields:
            stringlist.append(self.field_value_as_string(key))
        return self.exporter.field_delimiter.join(stringlist)


class VertecCsvExporter(object):
    """Exporter classes for Vertec OCL enabled CSV text exports.
       See the comments in the Constructor for how to customize the class
       via the member variables. 

        A field list in the form:
        fields = [
            ('Project', None, 'string', 'code'),
            ('ProjectId', None, 'int', 'objid'),
            ('Currency', None, 'string', 'waehrung.asstring'),
        ]
        musst be passed when constructing the Exporter class.
    """

    def __init__(self, fields):
        self.fields = fields
        # if we write a file, we encode it as it is defined here. A user
        # of this class can override it with, e.g, "UTF-8" - any valid encoding definition.
        self.encoding ='UTF-8'
        self.rows = []
        self.field_delimiter = ';'
        self.string_delimiter = '"'
        # Excel, e.g., can't handle CRLF in CSV-strings.
        self.only_first_line_for_strings = True
        # If no file_name is given, the csv text is not saved but only
        # returned (in memory)
        self.file_name = ''
        self.field_names_in_first_line  = True
        self.row_delimiter = '\n'

    def add_row(self, vertec_object):
        # adds a new row and returns it.
        row = OclCsvRecord(self, vertec_object)
        self.rows.append(row)
        return row

    def populate_rows(self, listexpression, sqlwhere='', ocl_filter_expression=''):
        # populates the rows with either SQL or OCL.
        # the list can then be filtered by OCL (which is usefull especially if one
        # uses SQL in the first step)
        if sqlwhere:
            vertec_objects = vtcapp.getwithsql(listexpression, sqlwhere, '')
        else:
            vertec_objects = vtcapp.evalocl(listexpression)

        if ocl_filter_expression:
            vertec_objects = vertec_objects.evalocl(ocl_filter_expression)            

        for vertec_object in vertec_objects:
            self.add_row(vertec_object)

    def format_string(self, string_value):
        # formats a string to be compatible with CSV and the settings in this class. 
        # is implemented here since we (maybe) also need to format the fieldsname row.

        if string_value:
            # Escape the string delimiter if there is one.
            if self.string_delimiter:
                string_value = string_value.replace(self.string_delimiter, self.string_delimiter + self.string_delimiter)
            else:
                # Replace the field delimieter with spaces if there is no string delimiter.
                string_value = string_value.replace(self.field_delimiter, ' ')

            # return only first line if desired. 
            if self.only_first_line_for_strings:
                string_value = string_value.splitlines()[0]

        # add string delimiter.
        string_value = self.string_delimiter + string_value + self.string_delimiter

        return string_value

    def string_of_field_names(self):
        stringlist = []
        for key, default_value, field_type, ocl_expression in self.fields:
            stringlist.append(self.format_string(key))
        
        return unicode(self.field_delimiter.join(stringlist))

    def export_rows(self):
        # exports all rows, if desired, with the first line being
        # the fieldnames, formatted the same way.
        
        if self.field_names_in_first_line:
            csv_string = self.string_of_field_names() + self.row_delimiter
        else:
            csv_string = ""

        for row in self.rows: 
            csv_string += str(row) + self.row_delimiter

        # send the file to the client if a filename is provided. 
        if self.file_name:
            vtcapp.sendfile(csv_string.encode(self.encoding), self.file_name, True, False)                        
        
        return csv_string
