# -*- coding: utf-8 -*-
"""
w2lapp.add.py: add an entry

web2ldap - a web-based LDAP Client,
see http://www.web2ldap.de for details

(c) by Michael Stroeder <michael@stroeder.com>

This module is distributed under the terms of the
GPL (GNU GENERAL PUBLIC LICENSE) Version 2
(see http://www.gnu.org/copyleft/gpl.html)
"""

import ldap, ldap.modlist,pyweblib.forms, \
       w2lapp.cnf,w2lapp.core,w2lapp.gui,w2lapp.addmodifyform,w2lapp.modify

from ldap.dn import escape_dn_chars
from ldaputil.base import is_dn,ParentDN,SplitRDN

from ldaputil.controls import PostReadControl


# Attribute types always ignored for add requests
ADD_IGNORE_ATTR_TYPES = [
  'governingStructureRule',
  'hasSubordinates',
  'structuralObjectClass',
  'subschemaSubentry',
]


def ModlistTable(schema,modlist):
  """
  Return a string containing a HTML table showing attr type/value pairs
  """
  s = []
  s.append('<table summary="Modify list">')
  for attr_type,attr_value in modlist:
    if w2lapp.schema.no_humanreadable_attr(schema,attr_type):
      tablestr = '%s bytes of binary data' % (
        ' + '.join(
          [ str(len(x)) for x in attr_value ]
        )
      )
    else:
      tablestr = '<br>'.join([
        pyweblib.forms.escapeHTML(repr(v))
        for v in attr_value
      ])
    s.append('<tr><td>%s</td><td>%s</td></tr>' % (
      pyweblib.forms.escapeHTML(attr_type),tablestr
    )
  )
  s.append('</table>')
  return '\n'.join(s) # ModlistTable()


def SearchMissingParentEntries(ls,new_dn):
  parent_dn = ParentDN(new_dn)
  missing_parentdns = []
  while parent_dn.lower():
    try:
      ls.l.search_s(
        parent_dn.encode('utf-8'),ldap.SCOPE_BASE,
        '(objectClass=*)',['objectClass']
      )
    except ldap.NO_SUCH_OBJECT:
      missing_parentdns.append(parent_dn)
    parent_dn = ParentDN(parent_dn)
  return missing_parentdns


########################################################################
# Add new entry
########################################################################

def w2l_Add(sid,outf,command,form,ls,dn):

  sub_schema = ls.retrieveSubSchema(dn,w2lapp.cnf.GetParam(ls,'_schema',None))

  entry,invalid_attrs,in_binattrs = w2lapp.modify.GetEntryfromInputForm(form,ls,dn,sub_schema)

  if 'input_formtype' in form.inputFieldNames:
    input_addattrtypes = form.getInputValue('input_addattrtype',[])
    for input_addattrtype in input_addattrtypes:
      if input_addattrtype:
        entry[input_addattrtype] = entry.get(input_addattrtype,[])+['']
    w2lapp.addmodifyform.w2l_AddForm(
      sid,outf,'addform',form,ls,dn,
      Msg='',
      entry=entry,
      skip_oc_input=1
    )
    return

  if invalid_attrs:
    w2lapp.addmodifyform.w2l_AddForm(
      sid,outf,'addform',form,ls,dn,
      Msg='Syntax check failed for the following attributes: %s' % (
        ', '.join([ form.utf2display(unicode(v)) for v in invalid_attrs ])
      ),
      entry=entry,
      skip_oc_input=1
    )
    return

  # Final check if object classes were defined in entry data
  if not entry.has_key('objectClass'):
    raise w2lapp.core.ErrorExit(u'No object class(es) defined.')

  # Try to get the RDN from the user's input.
  rdn = form.getInputValue('add_rdn',[''])[0].encode(ls.charset)
  add_basedn = form.getInputValue('add_basedn',[dn])[0].encode(ls.charset)

  # If rdn does not contain a complete RDN try to determine
  # the attribute type for forming the RDN.
  try:
    rdn_list = [ tuple(rdn_comp.split('=',1)) for rdn_comp in ldap.explode_rdn(rdn) ]
  except ldap.DECODING_ERROR:
    w2lapp.addmodifyform.w2l_AddForm(
      sid,outf,'addform',form,ls,dn,
      Msg='<p>Wrong format of RDN string.</p>',
      rdn_default=rdn,
      entry=entry,
      skip_oc_input=1
    )
    return

  # Automagically derive the RDN from the entry
  for i in range(len(rdn_list)):
    rdn_attr_type,rdn_attr_value = rdn_list[i]
    # Normalize old LDAPv2 RDN form
    if rdn_attr_type.lower().startswith('oid.'):
      rdn_attr_type = rdn_attr_type[4:]
    if rdn_attr_value:
      rdn_list[i] = rdn_attr_type,rdn_attr_value
      if not entry.has_key(rdn_attr_type):
        # Add the RDN attribute to the entry if not already present
        entry[rdn_attr_type] = [rdn_attr_value]
    else:
      if entry.has_key(rdn_attr_type) and len(entry[rdn_attr_type])==1:
        rdn_list[i] = rdn_attr_type,entry[rdn_attr_type][0]
      else:
        w2lapp.addmodifyform.w2l_AddForm(
          sid,outf,'addform',form,ls,dn,
          Msg='Attribute <var>%s</var> required for RDN not in entry data.' % (
            form.utf2display(unicode(rdn_attr_type))
          ),
          rdn_default=rdn,
          entry=entry,
          skip_oc_input=1
        )
        return

  # Join the list of RDN components to one RDN string
  rdn = '+'.join([
    '='.join((atype,escape_dn_chars(avalue or '')))
    for atype,avalue in rdn_list
  ])

  # Generate list of modifications
  modlist = ldap.modlist.addModlist(entry,ignore_attr_types=ADD_IGNORE_ATTR_TYPES)

  if not modlist:
    raise w2lapp.core.ErrorExit(u'Cannot add entry without attribute values.')

  if dn:
    new_dn = ','.join([rdn,add_basedn])
  else:
    # Makes it possible to add entries for a namingContext
    new_dn = rdn

  if PostReadControl.controlType in ls.supportedControl:
    add_serverctrls = [PostReadControl(criticality=False,attrList=['entryUUID'])]
  else:
    add_serverctrls = None

  # Try to add the new entry
  try:
    add_msg_id = ls.l.add_ext(
      new_dn,
      modlist,
      serverctrls=add_serverctrls
    )
    _,_,_,add_resp_ctrls = ls.l.result3(add_msg_id)
  except ldap.NO_SUCH_OBJECT,e:
    raise w2lapp.core.ErrorExit(
      u"""
      %s<br>
      Probably this superiour entry does not exist:<br>%s<br>
      Maybe wrong base DN in LDIF template?<br>
      """ % (
        w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset),
        w2lapp.gui.DisplayDN(sid,form,ls,add_basedn.decode(ls.charset),commandbutton=0),
    ))
  except (
    ldap.ALREADY_EXISTS,
    ldap.CONSTRAINT_VIOLATION,
    ldap.INVALID_DN_SYNTAX,
    ldap.INVALID_SYNTAX,
    ldap.NAMING_VIOLATION,
    ldap.OBJECT_CLASS_VIOLATION,
    ldap.OTHER,
    ldap.TYPE_OR_VALUE_EXISTS,
    ldap.UNDEFINED_TYPE,
    ldap.UNWILLING_TO_PERFORM,
  ),e:
    # Some error in user's input => present input form to edit input values
    w2lapp.addmodifyform.w2l_AddForm(
      sid,outf,'addform',form,ls,dn,
      Msg=w2lapp.gui.LDAPError2ErrMsg(e,form,ls.charset),
      rdn_default=rdn,
      entry=entry,
      skip_oc_input=1
    )
  else:
    # Try to extract Post Read Entry response control
    prec_ctrls = [
      c
      for c in add_resp_ctrls or []
      if c.controlType == PostReadControl.controlType
    ]
    if prec_ctrls:
      new_dn = prec_ctrls[0].dn
    w2lapp.gui.SimpleMessage(
      sid,outf,form,ls,dn,
      'Added Entry',
      'Added entry %s with your input data:\n%s' % (
        w2lapp.gui.DisplayDN(sid,form,ls,new_dn.decode(ls.charset),commandbutton=1),
        ModlistTable(sub_schema,modlist)
      ),
      main_menu_list=w2lapp.gui.MainMenu(sid,form,ls,dn),
      context_menu_list=[]
    )
