text: # coding: utf-8
  
  # TODO must use Header.__new__ rather than Basic.__new__
  
  from sympy.utilities.iterables import iterable
  from sympy.core import Symbol
  from sympy import sympify, Tuple
  
  from .core import Basic
  from .core import Variable
  from .core import ValuedArgument
  from .core import FunctionDef, Interface
  from .core import ClassDef
  from .core import DottedName, DottedVariable
  from .datatypes import datatype, DataTypeFactory, UnionType
  from .macros import Macro, MacroShape, construct_macro
  from .core import local_sympify
  
  class Header(Basic):
      pass
  
  class MetaVariable(Header):
      """Represents the MetaVariable."""
  
      def __new__(cls, name, value):
          if not isinstance(name, str):
              raise TypeError('name must be of type str')
  
          # TODO check value
  
          return Basic.__new__(cls, name, value)
  
      @property
      def name(self):
          return self._args[0]
  
      @property
      def value(self):
          return self._args[1]
  
  # TODO rename dtypes to arguments
  class VariableHeader(Header):
      """Represents a variable header in the code.
  
      name: str
          variable name
  
      dtypes: dict
          a dictionary for typing
  
      Examples
  
      """
  
      # TODO dtypes should be a dictionary (useful in syntax)
      def __new__(cls, name, dtypes):
          if not(isinstance(dtypes, dict)):
              raise TypeError("Expecting dtypes to be a dict.")
  
          return Basic.__new__(cls, name, dtypes)
  
      @property
      def name(self):
          return self._args[0]
  
      @property
      def dtypes(self):
          return self._args[1]
  
  # TODO rename dtypes to arguments
  
      def __getnewargs__(self):
          """used for Pickling self."""
          # TODO improve after renaming the args property
          args = (self._args[0],)
          return args
  
  class FunctionHeader(Header):
      """Represents function/subroutine header in the code.
  
      func: str
          function/subroutine name
  
      dtypes: tuple/list
          a list of datatypes. an element of this list can be str/DataType of a
          tuple (str/DataType, attr, allocatable)
  
      results: tuple/list
          a list of datatypes. an element of this list can be str/DataType of a
          tuple (str/DataType, attr, allocatable)
  
      kind: str
          'function' or 'procedure'. default value: 'function'
  
      is_static: bool
          True if we want to pass arrays in f2py mode. every argument of type
          array will be preceeded by its shape, the later will appear in the
          argument declaration. default value: False
  
      Examples
  
      >>> from pyccel.ast.core import FunctionHeader
      >>> FunctionHeader('f', ['double'])
      FunctionHeader(f, [(NativeDouble(), [])])
      """
  
      # TODO dtypes should be a dictionary (useful in syntax)
      def __new__(cls, func, dtypes,
                  results=None,
                  kind='function',
                  is_static=False):
          func = str(func)
          if not(iterable(dtypes)):
              raise TypeError("Expecting dtypes to be iterable.")
  
          if results:
              if not(iterable(results)):
                  raise TypeError("Expecting results to be iterable.")
  
          if not isinstance(kind, str):
              raise TypeError("Expecting a string for kind.")
  
          if not (kind in ['function', 'procedure']):
              raise ValueError("kind must be one among {'function', 'procedure'}")
  
          if not isinstance(is_static, bool):
              raise TypeError('is_static must be a boolean')
  
          return Basic.__new__(cls, func, dtypes, results, kind, is_static)
  
      @property
      def func(self):
          return self._args[0]
  
      @property
      def dtypes(self):
          return self._args[1]
  
      @property
      def results(self):
          return self._args[2]
  
      @property
      def kind(self):
          return self._args[3]
  
      @property
      def is_static(self):
          return self._args[4]
  
      def create_definition(self):
          """Returns a FunctionDef with empy body."""
          # TODO factorize what can be factorized
          from itertools import product
  
          name = str(self.func)
  
          body      = []
          cls_name  = None
          hide      = False
          kind      = self.kind
          is_static = self.is_static
          imports   = []
          funcs = []
          dtypes = []
         
          for i in self.dtypes:
              if isinstance(i, UnionType):
                  dtypes += [i.args]
              elif isinstance(i, dict):
                  dtypes += [[i]]
              else:
                  raise TypeError('element must be of type UnionType or dict')
          for args_ in product(*dtypes):
              args = []
              for i, d in enumerate(args_):
                  dtype    = d['datatype']
                  allocatable = d['allocatable']
                  is_pointer = d['is_pointer']
                  precision = d['precision']
                  rank = d['rank']
                  if rank>0 and allocatable:#case of ndarray
                      if dtype in ['int', 'double', 'float', 'complex']:
                          allocatable = True
                          dtype = 'ndarray'+dtype
                  order = None
                  if rank >1:
                      order = d['order']
  
                  shape  = None
                  if isinstance(dtype, str):
                      try:
                          dtype = datatype(dtype)
                      except:
                          #TODO check if it's a class type before
                          if isinstance(dtype, str):
                              dtype =  DataTypeFactory(str(dtype), ("_name"))()
                              is_pointer = True
                  arg_name = 'arg_{0}'.format(str(i))
                  arg = Variable(dtype, arg_name,
                                 allocatable=allocatable, is_pointer=is_pointer,
                                 rank=rank, shape=shape ,order = order, precision = precision)
                  args.append(arg)
  
              # ... factorize the following 2 blocks
              results = []
              for i,d_var in enumerate(self.results):
                  dtype = d_var.pop('datatype')
                  var = Variable(dtype, 'res_{}'.format(i), **d_var)
                  results.append(var)
                  # we put back dtype otherwise macro will crash when it tries to
                  # call create_definition
                  d_var['datatype'] = dtype
  
              func= FunctionDef(name, args, results, body,
                               local_vars=[],
                               global_vars=[],
                               cls_name=cls_name,
                               hide=hide,
                               kind=kind,
                               is_static=is_static,
                               imports=imports,
                               is_header=True)
              funcs += [func]
  
          return funcs
  
      def to_static(self):
          """returns a static function header. needed for f2py"""
          return FunctionHeader(self.func,
                                self.dtypes,
                                self.results,
                                self.kind,
                                True)
  
      def vectorize(self,index):
          """ add a dimension to one of the arguments specified by it's position"""
          types = self.dtypes
          types[index]['rank'] += 1
          types[index]['allocatable'] = True
          return FunctionHeader(self.func,
                                types,
                                self.results,
                                self.kind,
                                self.is_static)
  
      def __getnewargs__(self):
          """used for Pickling self."""
          args = (self.func,
                  self.dtypes,
                  self.results,
                  self.kind,
                  self.is_static,)
          return args
  
  
  # TODO to be improved => use FunctionHeader
  class MethodHeader(FunctionHeader):
      """Represents method header in the code.
  
      name: iterable
          method name as a list/tuple
  
      dtypes: tuple/list
          a list of datatypes. an element of this list can be str/DataType of a
          tuple (str/DataType, attr)
  
      results: tuple/list
          a list of datatypes. an element of this list can be str/DataType of a
          tuple (str/DataType, attr)
  
      kind: str
          'function' or 'procedure'. default value: 'function'
  
      is_static: bool
          True if we want to pass arrays in f2py mode. every argument of type
          array will be preceeded by its shape, the later will appear in the
          argument declaration. default value: False
  
      Examples
  
      >>> from pyccel.ast.core import MethodHeader
      >>> m = MethodHeader(('point', 'rotate'), ['double'])
      >>> m
      MethodHeader((point, rotate), [(NativeDouble(), [])], [])
      >>> m.name
      'point.rotate'
      """
  
      def __new__(cls, name, dtypes, results=None, kind='function', is_static=False):
          if not isinstance(name, (list, tuple)):
              raise TypeError("Expecting a list/tuple of strings.")
  
          if not(iterable(dtypes)):
              raise TypeError("Expecting dtypes to be iterable.")
  
          for d in dtypes:
              if not isinstance(d, UnionType) and not isinstance(d, dict):
                  raise TypeError("Wrong element in dtypes.")
  
          for d in results:
              if not isinstance(d, UnionType):
                  raise TypeError("Wrong element in dtypes.")
  
  
          if not isinstance(kind, str):
              raise TypeError("Expecting a string for kind.")
  
          if not (kind in ['function', 'procedure']):
              raise ValueError("kind must be one among {'function', 'procedure'}")
  
          if not isinstance(is_static, bool):
              raise TypeError('is_static must be a boolean')
  
          return Basic.__new__(cls, name, dtypes, results, kind, is_static)
  
      @property
      def name(self):
          _name = self._args[0]
          if isinstance(_name, str):
              return _name
          else:
              return '.'.join(str(n) for n in _name)
  
      @property
      def dtypes(self):
          return self._args[1]
  
      @property
      def results(self):
          return self._args[2]
  
      @property
      def kind(self):
          return self._args[3]
  
      @property
      def is_static(self):
          return self._args[4]
  
  class ClassHeader(Header):
      """Represents class header in the code.
  
      name: str
          class name
  
      options: str, list, tuple
          a list of options
  
      Examples
  
      >>> from pyccel.ast.core import ClassHeader
      >>> ClassHeader('Matrix', ('abstract', 'public'))
      ClassHeader(Matrix, (abstract, public))
      """
  
      def __new__(cls, name, options):
          if not(iterable(options)):
              raise TypeError("Expecting options to be iterable.")
  
          return Basic.__new__(cls, name, options)
  
      @property
      def name(self):
          return self._args[0]
  
      @property
      def options(self):
          return self._args[1]
  
  
  # TODO must extend Header rather than Basic
  class InterfaceHeader(Basic):
  
      def __new__(cls, name, funcs):
          if not isinstance(name,str):
              raise TypeError('name should of type str')
          if not all([isinstance(i, str) for i in funcs]):
              raise TypeError('functions name must be of type str')
          return Basic.__new__(cls, name, funcs)
  
  
      @property
      def name(self):
          return self._args[0]
  
      @property
      def funcs(self):
          return self._args[1]
  
  class MacroFunction(Header):
      """."""
  
      def __new__(cls, name, args, master, master_args, results=None):
          if not isinstance(name, (str, Symbol)):
              raise TypeError('name must be of type str or Symbol')
  
          # master can be a string or FunctionDef
          if not isinstance(master, (str, FunctionDef, Interface)):
              raise ValueError('Expecting a master name of FunctionDef')
  
          # we sympify everything since a macro is operating on symbols
          if not(args is None):
              args = [sympify(a, locals=local_sympify) for a in args]
  
          if not(master_args is None):
              master_args = [sympify(a, locals=local_sympify) for a in master_args]
  
          if not(results is None):
              results = [sympify(a, locals=local_sympify) for a in results]
  
          return Basic.__new__(cls, name, args, master, master_args, results)
  
      @property
      def name(self):
          return self._args[0]
  
      @property
      def arguments(self):
          return self._args[1]
  
      @property
      def master(self):
          return self._args[2]
  
      @property
      def master_arguments(self):
          return self._args[3]
  
      @property
      def results(self):
          return self._args[4]
  
      # TODO: must be moved to annotation, once we add AliasVariables
      #       this is needed if we have to create a pointer or allocate a new
      #       variable to store the result
      def apply(self, args, results=None):
          """returns the appropriate arguments."""
          d_arguments = {}
          if len(args) > 0:
  
              sorted_args = []
              unsorted_args = []
              j = -1
              for ind, i in enumerate(args):
                  if not isinstance(i, ValuedArgument):
                      sorted_args.append(i)
                  else:
                      j=ind
                      break
              if j>0:
                  unsorted_args = args[j:]
                  for i in unsorted_args:
                      if not isinstance(i, ValuedArgument):
                          raise ValueError('variable not allowed after an optional argument')
  
              for i in self.arguments[len(sorted_args):]:
                  if not isinstance(i, ValuedArgument):
                      raise ValueError('variable not allowed after an optional argument')
         
               
  
              for arg,val in zip(self.arguments[:len(sorted_args)],sorted_args):
                  if not isinstance(arg, Tuple):
                      d_arguments[arg.name] = val
                  else:
                      if not isinstance(val, (list, Tuple,tuple)):
                          val = [val]
                      #TODO improve add more checks and generalize
                      if len(val)>len(arg):
                          raise ValueError('length mismatch of argument and its value ')
                      elif len(val)<len(arg):
                          for val_ in arg[len(val):]:
                              if isinstance(val_, ValuedArgument):
                                  val +=Tuple(val_.value,)
                              else:
                                  val +=Tuple(val_)
  
                      for arg_,val_ in zip(arg,val):
                          d_arguments[arg_.name] = val_
  
              d_unsorted_args = {}
              for arg in self.arguments[len(sorted_args):]:
                  d_unsorted_args[arg.name] = arg.value
              
              for arg in unsorted_args:
                  if arg.name in d_unsorted_args.keys():
                      d_unsorted_args[arg.name] = arg.value
                  else:
                      raise ValueError('Unknown valued argument')
              d_arguments.update(d_unsorted_args)
              for i in d_arguments.keys():
                  arg = d_arguments[i]
                  if isinstance(arg, Macro):
                      d_arguments[i] = construct_macro(arg.name,
                                        d_arguments[arg.argument.name])
                      if isinstance(arg, MacroShape):
                          d_arguments[i]._index = arg.index
              
                  
          d_results = {}
          if not(results is None) and not(self.results is None):
              for (r_macro, r) in zip(self.results, results):
                  # TODO improve name for other Nodes
                  d_results[r_macro.name] = r
          # ...
  
          # ... initialize new args with None
          newargs = [None]*len(self.master_arguments)
          # ...
          argument_keys = d_arguments.keys()
          result_keys = d_results.keys()
          for i,arg in enumerate(self.master_arguments):
              
              if isinstance(arg, Symbol):
                  if arg.name in argument_keys:
                      new = d_arguments[arg.name]
                      if isinstance(new, Symbol) and new.name in result_keys:
                          new = d_results[new.name]
  
                  elif arg.name in result_keys:
                      new = d_results[arg.name]
                  else:
                      new = arg
                 #TODO uncomment later
                 #     raise ValueError('Unknown variable name')
              elif isinstance(arg, Macro):
                  if arg.argument.name in argument_keys:
                      new = d_arguments[arg.argument.name]
                      if isinstance(new, Symbol) and new.name in result_keys:
                          new = d_results[new.name]
                  elif arg.argument.name in result_keys:
                      new = d_results[arg.argument.name]
                  else:
                      raise ValueError('Unkonwn variable name')
   
                  new = construct_macro(arg.name, new)
                  if isinstance(arg, MacroShape):
                          new._index = arg.index
  
              newargs[i] = new
          return newargs
  
  class MacroVariable(Header):
      """."""
  
      def __new__(cls, name,  master):
          if not isinstance(name, (str, Symbol, DottedName)):
              raise TypeError('name must be of type str or DottedName')
  
      
          if not isinstance(master, (str, Variable, DottedVariable)):
              raise ValueError('Expecting a master name of Variable')
  
          
          return Basic.__new__(cls, name, master)
  
  
      @property
      def name(self):
          return self._args[0]
  
      @property
      def master(self):
          return self._args[1]
  
  
  
  