/**
 * Functions dedicated to edition.
 *
 *  :organization: Logilab
 *  :copyright: 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
 *  :contact: https://www.logilab.fr/ -- mailto:contact@logilab.fr
 *
 */

//============= Eproperty form functions =====================================//
/**
 * .. function:: setPropValueWidget(varname)
 *
 * called on CWProperty key selection:
 * - get the selected value
 * - get a widget according to the key by a sync query to the server
 * - fill associated div with the returned html
 *
 * * `varname`, the name of the variable as used in the original creation form
 */

function setPropValueWidget(varname) {
  var key = firstSelected(document.getElementById("pkey-subject:" + varname));
  if (key) {
    var args = {
      fname: "prop_widget",
      pageid: pageid,
      arg: $.map([key.value, varname], JSON.stringify),
    };
    cw.jqNode("div:value-subject:" + varname).loadxhtml(
      AJAX_BASE_URL,
      args,
      "post"
    );
  }
}

// *** EDITION FUNCTIONS ****************************************** //
/**
 * .. function:: reorderTabindex(start, formid)
 *
 * this function is called when an AJAX form was generated to
 * make sure tabindex remains consistent
 */
function reorderTabindex(start, formid) {
  var form = cw.getNode(formid || "entityForm");
  var inputTypes = ["INPUT", "SELECT", "TEXTAREA"];
  var tabindex = start == null ? 15 : start;
  cw.utils.nodeWalkDepthFirst(form, function (elem) {
    var tagName = elem.tagName.toUpperCase();
    if (jQuery.inArray(tagName, inputTypes) != -1) {
      if (jQuery(elem).attr("tabindex") != null) {
        tabindex += 1;
        jQuery(elem).attr("tabindex", tabindex);
      }
      return null;
    }
    return jQuery.grep(elem.childNodes, isElementNode);
  });
}

function showMatchingSelect(selectedValue, eid) {
  if (selectedValue) {
    var divId = "div" + selectedValue + "_" + eid;
    var divNode = jQuery("#" + divId);
    if (!divNode.length) {
      var args = {
        vid: "unrelateddivs",
        relation: selectedValue,
        rql: rql_for_eid(eid),
        __notemplate: 1,
      };
      var d = jQuery("#unrelatedDivs_" + eid).loadxhtml(
        BASE_URL + "view",
        args,
        "post",
        "append"
      );
      d.addCallback(function () {
        _showMatchingSelect(eid, jQuery("#" + divId));
      });
    } else {
      _showMatchingSelect(eid, divNode);
    }
  } else {
    _showMatchingSelect(eid, null);
  }
}

/**
 * .. function:: _showMatchingSelect(eid, divNode)
 *
 * * `divNode`, a jQuery selection
 */
function _showMatchingSelect(eid, divNode) {
  // hide all divs, and then show the matching one
  // (would actually be better to directly hide the displayed one)
  jQuery("#unrelatedDivs_" + eid)
    .children()
    .hide();
  // divNode not found means 'no relation selected' (i.e. first blank item)
  if (divNode && divNode.length) {
    divNode.show();
  }
}

/**
 * .. function:: buildPendingInsertHandle(elementId, element_name, selectNodeId, eid)
 *
 * this function builds a Handle to cancel pending insertion
 */
function buildPendingInsertHandle(elementId, element_name, selectNodeId, eid) {
  jscall =
    "javascript: cancelPendingInsert('" +
    [elementId, element_name, selectNodeId, eid].join("', '") +
    "')";
  return A(
    {
      class: "handle",
      href: jscall,
      title: _("cancel this insert"),
    },
    "[x]"
  );
}

function buildEntityLine(relationName, selectedOptionNode, comboId, eid) {
  // textContent doesn't seem to work on selectedOptionNode
  var content = selectedOptionNode.firstChild.nodeValue;
  var handle = buildPendingInsertHandle(
    selectedOptionNode.id,
    "tr",
    comboId,
    eid
  );
  var link = A(
    {
      href: "view?rql=" + selectedOptionNode.value,
      class: "editionPending",
      id: "a" + selectedOptionNode.id,
    },
    content
  );
  var tr = TR(
    {
      id: "tr" + selectedOptionNode.id,
    },
    [TH(null, relationName), TD(null, [handle, link])]
  );
  try {
    var separator = cw.getNode("relationSelectorRow_" + eid);
    //dump('relationSelectorRow_' + eid) XXX warn dump is not implemented in konqueror (at least)
    // XXX Warning: separator.parentNode is not (always ?) the
    // table itself, but an intermediate node (TableSectionElement)
    var tableBody = separator.parentNode;
    tableBody.insertBefore(tr, separator);
  } catch (ex) {
    log("got exception(2)!" + ex);
  }
}

function buildEntityCell(relationName, selectedOptionNode, comboId, eid) {
  var handle = buildPendingInsertHandle(
    selectedOptionNode.id,
    "div_insert_",
    comboId,
    eid
  );
  var link = A(
    {
      href: "view?rql=" + selectedOptionNode.value,
      class: "editionPending",
      id: "a" + selectedOptionNode.id,
    },
    content
  );
  var div = DIV(
    {
      id: "div_insert_" + selectedOptionNode.id,
    },
    [handle, link]
  );
  try {
    var td = jQuery("#cell" + relationName + "_" + eid);
    td.appendChild(div);
  } catch (ex) {
    alert("got exception(3)!" + ex);
  }
}

function addPendingInsert(optionNode, eid, cell, relname) {
  var value = jQuery(optionNode).attr("value");
  if (!value) {
    // occurs when the first element in the box is selected (which is not
    // an entity but the combobox title)
    return;
  }
  // 2nd special case
  if (value.indexOf("http") == 0) {
    document.location = value;
    return;
  }
  // add hidden parameter
  var entityForm = jQuery("#entityForm");
  var oid = optionNode.id.substring(2); // option id is prefixed by "id"
  loadRemote(
    AJAX_BASE_URL,
    ajaxFuncArgs("add_pending_inserts", null, [oid.split(":")]),
    "POST",
    true
  );
  var selectNode = optionNode.parentNode;
  // remove option node
  selectNode.removeChild(optionNode);
  // add line in table
  if (cell) {
    // new relation as a cell in multiple edit
    // var relation_name = relationSelected.getAttribute('value');
    // relation_name = relation_name.slice(0, relation_name.lastIndexOf('_'));
    buildEntityCell(relname, optionNode, selectNode.id, eid);
  } else {
    var relationSelector = cw.getNode("relationSelector_" + eid);
    var relationSelected =
      relationSelector.options[relationSelector.selectedIndex];
    // new relation as a line in simple edit
    buildEntityLine(relationSelected.text, optionNode, selectNode.id, eid);
  }
}

function cancelPendingInsert(elementId, element_name, comboId, eid) {
  // remove matching insert element
  var entityView = cw.jqNode("a" + elementId).text();
  cw.jqNode(element_name + elementId).remove();
  if (comboId) {
    // re-insert option in combobox if it was taken from there
    var selectNode = cw.getNode(comboId);
    // XXX what on object relation
    if (selectNode) {
      var options = selectNode.options;
      var node_id = elementId.substring(0, elementId.indexOf(":"));
      options[options.length] = OPTION(
        {
          id: elementId,
          value: node_id,
        },
        entityView
      );
    }
  }
  elementId = elementId.substring(2, elementId.length);
  loadRemote(
    AJAX_BASE_URL,
    ajaxFuncArgs("remove_pending_insert", null, elementId.split(":")),
    "GET",
    true
  );
}

/**
 * .. function:: buildPendingDeleteHandle(elementId, eid)
 *
 * this function builds a Handle to cancel pending insertion
 */
function buildPendingDeleteHandle(elementId, eid) {
  var jscall =
    "javascript: addPendingDelete('" + elementId + ", " + eid + "');";
  return A(
    {
      href: jscall,
      class: "pendingDeleteHandle",
      title: _("delete this relation"),
    },
    "[x]"
  );
}

/**
 * .. function:: addPendingDelete(nodeId, eid)
 *
 * * `nodeId`, eid_from:r_type:eid_to
 */
function addPendingDelete(nodeId, eid) {
  var d = loadRemote(
    AJAX_BASE_URL,
    ajaxFuncArgs("add_pending_delete", null, nodeId.split(":"))
  );
  d.addCallback(function () {
    // and strike entity view
    cw.jqNode("span" + nodeId).addClass("pendingDelete");
    // replace handle text
    cw.jqNode("handle" + nodeId).text("+");
  });
}

/**
 * .. function:: cancelPendingDelete(nodeId, eid)
 *
 * * `nodeId`, eid_from:r_type:eid_to
 */
function cancelPendingDelete(nodeId, eid) {
  var d = loadRemote(
    AJAX_BASE_URL,
    ajaxFuncArgs("remove_pending_delete", null, nodeId.split(":"))
  );
  d.addCallback(function () {
    // reset link's CSS class
    cw.jqNode("span" + nodeId).removeClass("pendingDelete");
    // replace handle text
    cw.jqNode("handle" + nodeId).text("x");
  });
}

/**
 * .. function:: togglePendingDelete(nodeId, eid)
 *
 * * `nodeId`, eid_from:r_type:eid_to
 */
function togglePendingDelete(nodeId, eid) {
  // node found means we should cancel deletion
  if (jQuery(cw.getNode("span" + nodeId)).hasClass("pendingDelete")) {
    cancelPendingDelete(nodeId, eid);
  } else {
    addPendingDelete(nodeId, eid);
  }
}

function selectForAssociation(tripletIdsString, originalEid) {
  var tripletlist = $.map(tripletIdsString.split("-"), function (x) {
    return [x.split(":")];
  });
  var d = loadRemote(
    AJAX_BASE_URL,
    ajaxFuncArgs("add_pending_inserts", null, tripletlist)
  );
  d.addCallback(function () {
    var args = {
      vid: "edition",
      __mode: "normal",
      rql: rql_for_eid(originalEid),
    };
    document.location = "view?" + asURL(args);
  });
}

function updateInlinedEntitiesCounters(rtype, role) {
  jQuery("div.inline-" + rtype + "-" + role + "-slot span.icounter").each(
    function (i) {
      this.innerHTML = i + 1;
    }
  );
}

/**
 * .. function:: addInlineCreationForm(peid, petype, ttype, rtype, role, i18nctx, insertBefore)
 *
 * makes an AJAX request to get an inline-creation view's content
 * * `peid`, the parent entity eid
 *
 * * `petype`, the parent entity type
 *
 * * `ttype`, the target (inlined) entity type
 *
 * * `rtype`, the relation type between both entities
 */
function addInlineCreationForm(
  peid,
  petype,
  ttype,
  rtype,
  role,
  i18nctx,
  insertBefore
) {
  insertBefore =
    insertBefore || cw.getNode("add" + rtype + ":" + peid + "link").parentNode;
  var args = ajaxFuncArgs(
    "inline_creation_form",
    null,
    peid,
    petype,
    ttype,
    rtype,
    role,
    i18nctx
  );
  var d = loadRemote(AJAX_BASE_URL, args);
  d.addCallback(function (response) {
    var dom = getDomFromResponse(response);
    loadAjaxHtmlHead(dom);
    var form = jQuery(dom);
    form.css("display", "none");
    form.insertBefore(insertBefore).slideDown("fast");
    updateInlinedEntitiesCounters(rtype, role);
    reorderTabindex(null, $(insertBefore).closest("form")[0]);
    jQuery(cw).trigger("inlinedform-added", form);
    // if the inlined form contains a file input, we must force
    // the form enctype to multipart/form-data
    if (form.find("input:file").length) {
      // NOTE: IE doesn't support dynamic enctype modification, we have
      //       to set encoding too.
      form
        .closest("form")
        .attr("enctype", "multipart/form-data")
        .attr("encoding", "multipart/form-data");
    }
    _postAjaxLoad(dom);
  });
  d.addErrback(function (xxx) {
    cw.log("xxx =", xxx);
  });
}

/**
 * .. function:: removeInlineForm(peid, rtype, role, eid, showaddnewlink)
 *
 * removes the part of the form used to edit an inlined entity
 */
function removeInlineForm(peid, rtype, role, eid, showaddnewlink) {
  cw.jqNode(["div", peid, rtype, eid].join("-")).slideUp("fast", function () {
    $(this).remove();
    updateInlinedEntitiesCounters(rtype, role);
  });
  if (showaddnewlink) {
    toggleVisibility(showaddnewlink);
  }
}

/**
 * .. function:: removeInlinedEntity(peid, rtype, eid)
 *
 * alternatively adds or removes the hidden input that make the
 * edition of the relation `rtype` possible between `peid` and `eid`
 * * `peid`, the parent entity eid
 *
 * * `rtype`, the relation type between both entities
 *
 * * `eid`, the inlined entity eid
 */
function removeInlinedEntity(peid, rtype, eid) {
  // XXX work around the eid_param thing (eid + ':' + eid) for #471746
  var nodeid = ["rel", peid, rtype, eid + ":" + eid].join("-");
  var node = cw.jqNode(nodeid);
  if (!node.attr("cubicweb:type")) {
    node.attr("cubicweb:type", node.val());
    node.val("");
    var divid = ["div", peid, rtype, eid].join("-");
    cw.jqNode(divid).fadeTo("fast", 0.5);
    var noticeid = ["notice", peid, rtype, eid].join("-");
    cw.jqNode(noticeid).fadeIn("fast");
  }
}

function restoreInlinedEntity(peid, rtype, eid) {
  // XXX work around the eid_param thing (eid + ':' + eid) for #471746
  var nodeid = ["rel", peid, rtype, eid + ":" + eid].join("-");
  var node = cw.jqNode(nodeid);
  if (node.attr("cubicweb:type")) {
    node.val(node.attr("cubicweb:type"));
    node.attr("cubicweb:type", "");
    cw.jqNode(["fs", peid, rtype, eid].join("-")).append(node);
    var divid = ["div", peid, rtype, eid].join("-");
    cw.jqNode(divid).fadeTo("fast", 1);
    var noticeid = ["notice", peid, rtype, eid].join("-");
    cw.jqNode(noticeid).hide();
  }
}

function _clearPreviousErrors(formid) {
  // on some case (eg max request size exceeded, we don't know the formid
  if (formid) {
    jQuery("#" + formid + "ErrorMessage").remove();
    jQuery("#" + formid + " span.errorMsg").remove();
    jQuery("#" + formid + " .error").removeClass("error");
  } else {
    jQuery("span.errorMsg").remove();
    jQuery(".error").removeClass("error");
  }
}

function _displayValidationerrors(formid, eid, errors) {
  var globalerrors = [];
  var firsterrfield = null;
  for (fieldname in errors) {
    var errmsg = errors[fieldname];
    if (!fieldname) {
      globalerrors.push(errmsg);
    } else {
      var fieldid = fieldname + ":" + eid;
      var suffixes = ["", "-subject", "-object"];
      var found = false;
      // XXX remove suffixes at some point
      for (var i = 0, length = suffixes.length; i < length; i++) {
        var field = cw.jqNode(fieldname + suffixes[i] + ":" + eid);
        if (field && jQuery(field).attr("type") != "hidden") {
          if (!firsterrfield) {
            firsterrfield = "err-" + fieldid;
          }
          jQuery(field).addClass("error");
          var span = SPAN(
            {
              id: "err-" + fieldid,
              class: "errorMsg",
            },
            errmsg
          );
          field.before(span);
          found = true;
          break;
        }
      }
      if (!found) {
        firsterrfield = formid;
        globalerrors.push(_(fieldname) + " : " + errmsg);
      }
    }
  }
  if (globalerrors.length) {
    if (globalerrors.length == 1) {
      var innernode = SPAN(null, globalerrors[0]);
    } else {
      var linodes = [];
      for (var i = 0; i < globalerrors.length; i++) {
        linodes.push(LI(null, globalerrors[i]));
      }
      var innernode = UL(null, linodes);
    }
    // insert DIV and innernode before the form
    var div = DIV({
      class: "errorMessage",
      id: formid + "ErrorMessage",
    });
    div.appendChild(innernode);
    jQuery("#" + formid).before(div);
  }
  return firsterrfield || formid;
}

function handleFormValidationResponse(
  formid,
  onsuccess,
  onfailure,
  result,
  cbargs
) {
  // Success
  if (result[0]) {
    if (onsuccess) {
      onsuccess(result, formid, cbargs);
      if(typeof onsuccess === "function") {
        try {
          onsuccess(result, formid, cbargs);
        } catch(error) {
          console.error(error);
            return false;
        }
      } else {
        console.error("onsuccess is not a function!");
        return false;
      }
    } else {
      document.location.href = result[1];
    }
    return true;
  }
  if (onfailure && !onfailure(result, formid, cbargs)) {
    return false;
  }
  unfreezeFormButtons(formid);
  // Failures
  _clearPreviousErrors(formid);
  var descr = result[1];
  var errmsg;
  // Unknown structure
  if (!cw.utils.isArrayLike(descr) || descr.length != 2) {
    errmsg = descr;
  } else {
    _displayValidationerrors(formid, descr[0], descr[1]);
    errmsg = _("please correct errors below");
  }
  updateMessage(errmsg);
  // ensure the browser does not scroll down
  document.location.hash = "#header";
  return false;
}

/**
 * .. function:: unfreezeFormButtons(formid)
 *
 * unfreeze form buttons when the validation process is over
 */
function unfreezeFormButtons(formid) {
  jQuery("#progress").hide();
  // on some case (eg max request size exceeded, we don't know the formid
  if (formid) {
    jQuery("#" + formid + " .validateButton").removeAttr("disabled");
  } else {
    jQuery(".validateButton").removeAttr("disabled");
  }
  return true;
}

/**
 * .. function:: freezeFormButtons(formid)
 *
 * disable form buttons while the validation is being done
 */
function freezeFormButtons(formid) {
  jQuery("#progress").show();
  jQuery("#" + formid + " .validateButton").attr("disabled", "disabled");
  return true;
}

/**
 * .. function:: postForm(bname, bvalue, formid)
 *
 * used by additional submit buttons to remember which button was clicked
 */
function postForm(bname, bvalue, formid) {
  var form = cw.getNode(formid);
  if (bname) {
    var child = form.appendChild(
      INPUT({
        type: "hidden",
        name: bname,
        value: bvalue,
      })
    );
  }
  var onsubmit = form.onsubmit;
  if (!onsubmit || (onsubmit && onsubmit())) {
    form.submit();
  }
  if (bname) {
    jQuery(child).remove();
  }
}

/**
 * Cancel the operations done on the given form.
 *
 */
$(function () {
  $(document).on("click", ".cwjs-edition-cancel", function (evt) {
    var $mynode = $(evt.currentTarget),
      $form = $mynode.closest("form"),
      $error = $form.find(':input[name="__errorurl"]'),
      errorurl = $error.attr("value"),
      args = ajaxFuncArgs("cancel_edition", null, errorurl);
    loadRemote(AJAX_BASE_URL, args, "POST", true);
    history.back();
    return false;
  });
});

/**
 * .. function:: validateForm(formid, action, onsuccess, onfailure)
 *
 * called on traditionnal form submission : the idea is to try
 * to post the form. If the post is successful, `validateForm` redirects
 * to the appropriate URL. Otherwise, the validation errors are displayed
 * around the corresponding input fields.
 */
function validateForm(formid, action, onsuccess, onfailure) {
  freezeFormButtons(formid);
  try {
    var zipped = cw.utils.formContents(formid);
    var args = ajaxFuncArgs(
      "validate_form",
      null,
      action,
      zipped[0],
      zipped[1]
    );
    var d = loadRemote(AJAX_BASE_URL, args, "POST");
  } catch (ex) {
    cw.log("got exception", ex);
    return false;
  }
  d.addCallback(function (result, req) {
    handleFormValidationResponse(formid, onsuccess, onfailure, result);
  });
  return false;
}
