from . import schema
from . import RemcallError
from .naming import CSharpNameConverter


class CSharphCodeGenerator:

    indent_chars = '\t'
    type_names = {
        schema.void: 'void',
        schema.boolean: 'bool',
        schema.int8: 'SByte',
        schema.uint8: 'byte',
        schema.int16: 'Int16',
        schema.uint16: 'UInt16',
        schema.int32: 'Int32',
        schema.uint32: 'UInt32',
        schema.int64: 'Int64',
        schema.uint64: 'UInt64',
        schema.float32: 'float',
        schema.float64: 'double',
        schema.string: 'string',
        schema.date: 'DateTime',
        schema.time: 'Time',
        schema.datetime: 'DateTime'
    }

    def __init__(self, schema, namespace='Remcall.Generated',
                 name_converter=CSharpNameConverter()):
        self.schema = schema
        self.namespace = namespace
        self.name_converter = name_converter
        self._indent = 0
        self.outfile = None

    def writeline(self, s):
        print(self.indent_chars * self._indent + s, file=self.outfile)

    def linebreak(self):
        print(file=self.outfile)

    def indent(self):
        self._indent += 1

    def dedent(self):
        self._indent -= 1
        if self._indent < 0:
            raise RemcallError('Trying to create negative indent')

    def scalartypename(self, typ):
        assert not isinstance(typ, schema.Array)
        if isinstance(typ, schema.Primitive):
            return self.type_names[typ]
        else:
            return self.name_converter.type_name(typ)

    def typename(self, typ):
        return '{}[]'.format(self.scalartypename(typ.typ)) \
               if isinstance(typ, schema.Array) \
               else self.scalartypename(typ)

    def write_schema(self, fp):
        self.outfile = fp
        self.writeline('// Code generated by remcall, do not edit!')
        self.writeline('using System;')
        self.linebreak()
        self.writeline('namespace {} {{'.format(self.namespace))
        self.linebreak()
        self.indent()
        for enum in self.schema.enums_sorted:
            self.write_enum(enum)
            self.linebreak()
        for record in self.schema.records_sorted:
            self.write_record(record)
            self.linebreak()
        for interface in self.schema.interfaces_sorted:
            self.write_interface(interface)
            self.linebreak()
        self.dedent()
        self.writeline('}')

    def write_enum(self, enum):
        enum_name = self.scalartypename(enum)
        enum_values = [self.name_converter.enum_field_name(v)
                       for v in enum.values]
        self.writeline(
            'enum {} {{ {} }}'.format(enum_name, ', '.join(enum_values)))

    def write_record(self, record):
        record_name = self.scalartypename(record)
        fields = ['{} {};'
                  .format(self.typename(typ),
                          self.name_converter.record_field_name(name))
                  for typ, name in record.fields]
        self.writeline('struct {} {{'.format(record_name))
        self.indent()
        for field in fields:
            self.writeline(field)
        self.dedent()
        self.writeline('}')

    def write_interface(self, interface):
        interface_name = self.scalartypename(interface)
        self.writeline('interface {} {{'.format(interface_name))
        self.indent()
        for method in interface.methods_sorted:
            self.write_method(method)
        self.dedent()
        self.writeline('}')

    def write_method(self, method):
        method_name = self.name_converter.method_name(method.name)
        args = ['{!s} {}'.format(self.typename(typ),
                                 self.name_converter.parameter_name(name))
                for typ, name in method.arguments]
        ret = self.typename(method.return_type)
        self.writeline('{} {}({});'.format(ret, method_name, ', '.join(args)))
