/**
 * Implements a file-chooser, drago-drop functionality (Webbrowser only) and webcam functionality for ELO wf Forms
 *
 * @author ESt, ELO Digital Office GmbH
 * @version 1.03.006
 *
 * In some forms, you'd maybe like to let the user select a picture. And save it for later use.
 * If so, please use the 113_capturepic_webcam "HR"-Form as an inspiration.
 *
 * As soon as you defined your own form having all required fields, you can use the following config for initializing this class
 * ### Example config
 *
 *     {
 *       name: "personnelphotopicker",
 *       webcamName: "personnelphotocam",
 *       webcamConfig: {
 *         javaStartupButton: "WEBCAM_JAVA",
 *         varNameBtnReset: "JS_WEBCAM_RESET",
 *         varNameBtnSnap: "JS_WEBCAM_SNAP",
 *         varNameContainer: "WEBCAM_INIT",
 *         width: 540,
 *         height: 390,
 *         dest_width: 720,
 *         dest_height: 520,
 *         crop_width: 400,
 *         crop_height: 520,
 *         image_format: "jpeg",
 *         jpeg_quality: 90,
 *         swfURL: "lib_webcam.swf",
 *         fps: 45,
 *         showIfNoCam: true
 *       },
 *       dropZoneId: "dropZone",
 *       filePickerId: "filePicker",
 *       accept: "image/jpeg, image/jpg, image/png",
 *       maxSize: "3", //Megabyte (float values possible)
 *       maskNameForRule: "Personnel file",
 *       solTypeForRule: "PERSONNELFILE",
 *       photoReferenceField: "HR_PERSONNEL_PHOTO_GUID",
 *       photoReferenceFieldObjId: "HR_PERSONNEL_PHOTO_OBJID",
 *       clearPreviewField: "JS_PICTURE_CLEAR",
 *       filePickerField: "JS_FILEPICKER",
 *       photoConfig: {
 *         maskName: "Personnel file document",
 *         pictureName: "Mitarbeiterfoto"
 *       }
 *     }
 *
 * ### Setup
 * Add the following to your `Header.txt`
 *
 *     function onInit() {
 *       this.fcv = sol.create("sol.common.forms.FileChooserVariants", config);
 *     }
 *
 *     // i recommend to save using these precautions, otherwise empty images might get saved
 *     // also, note that "webbrowser"-webcam pictures need to be saved using the function "webcam.savePicture" instead of calling "fcv.uploadImage" directly
 *     function nextClicked(id) {
 *       return (
 *         sol.common.forms.Utils.disableCancelButtonValidation(id, ["sol.common.wf.node.cancel"])
 *         || (typeof this.fcv.displayImage !== "undefined" && this.fcv.displayImage.src && this.fcv.uploadImage(this.fcv.displayImage.src))
 *         || ($var(config.webcamConfig.varNameContainer) && this.fcv.webcam.savePicture(this.fcv.uploadImage.bind(this.fcv)));
 *       );
 *     }
 *
 */
sol.define("sol.common.forms.FileChooserVariants", {
  //instruments
  webcam: undefined,
  dropZone: undefined,
  filePicker: undefined,
  // base64 temporary stores
  dragdropfile: undefined,
  webacamImage: undefined,
  //divs
  displayDiv: undefined,
  displayImage: undefined,
  defaultConfig: {
    name: "filepicker",
    webcamName: "photocam",
    webcamConfig: {
      javaStartupButton: "WEBCAM_JAVA",
      varNameBtnReset: "JS_WEBCAM_RESET",
      varNameBtnSnap: "JS_WEBCAM_SNAP",
      varNameContainer: "WEBCAM_INIT",
      width: 540,
      height: 390,
      dest_width: 720,
      dest_height: 520,
      crop_width: 400,
      crop_height: 520,
      image_format: "jpeg",
      jpeg_quality: 90,
      swfURL: "lib_webcam.swf",
      fps: 45,
      showIfNoCam: true
    },
    dropZoneId: "dropZone",
    filePickerId: "filePicker",
    accept: "image/jpeg, image/jpg, image/png",
    maxSize: "3",
    photoReferenceField: "",
    photoReferenceFieldObjId: "FILEPICKER_FILE_OBJID",
    clearPreviewField: "JS_PICTURE_CLEAR",
    filePickerField: "JS_FILEPICKER",
    photoConfig: {
      maskName: "Basic Entry",
      pictureName: "my_file"
    }
  },

  /**
   * Initialize the tools
   * @param {Object} config Configuration
   */
  initialize: function (config) {
    var me = this,
        fakeWebcamDiv;

    sol.common.forms.Utils.initializeIxSession();

    me.merge(config, me.defaultConfig);
    me.config = config;

    // initialize webcam
    me.webcam = (navigator.userAgent.indexOf("JavaFX") === -1) && sol.create("sol.common.forms.Webcam", me.config.webcamConfig);

    if (/*(navigator.userAgent.indexOf("JavaFX") > -1) && */$var(me.config.webcamConfig.varNameContainer)) {
      $var(me.config.webcamConfig.varNameContainer).style.width = String(me.calcLiveCrop()[0]) + "px";
      $var(me.config.webcamConfig.varNameContainer).style.height = String(me.calcLiveCrop()[1]) + "px";
    }

    //initialize dropZone
    me.dropZone = document.createElement("div");
    me.dropZone.id = me.config.dropZoneId;
    me.dropZone.style = "background: gray; position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 999; opacity: 0.6; visibility: hidden;";
    document.body.appendChild(me.dropZone);
    me.dropZone.addEventListener("dragleave", me.hideDropZone.bind(me));
    me.dropZone.addEventListener("drop", me.fileDroppedHandler.bind(me));
    me.dropZone.addEventListener("dragenter", function (e) {
      e.dataTransfer.dropEffect = "copy"; e.preventDefault();
    });
    me.dropZone.addEventListener("dragover", function (e) {
      e.dataTransfer.dropEffect = "copy"; e.preventDefault();
    });
    window.addEventListener("dragenter", me.showDropZone.bind(me));

    //initialize filePicker
    me.filePicker = document.createElement("input");
    me.filePicker.type = "file";
    me.filePicker.id = me.config.filePickerId;
    me.filePicker.accept = me.config.accept;
    me.filePicker.style = "display:none";
    me.filePicker.addEventListener("change", me.filePickedInBrowser.bind(me)); // will only be called in Browser
    $var(me.config.filePickerField).addEventListener("click", me.filePickedHandler.bind(me));

    //assign event-handlers
    $var(me.config.webcamConfig.varNameBtnSnap).onclick = me.JS_WEBCAM_SNAP.bind(me);
    $var(me.config.webcamConfig.varNameBtnReset).onclick = me.webcam && me.webcam.reset.bind(me);
    $var(me.config.webcamConfig.javaStartupButton).onclick = me.JS_WEBCAM_SNAP.bind(me);
    $var(me.config.clearPreviewField).onclick = me.JS_PICTURE_CLEAR.bind(me);

    if (navigator.userAgent.indexOf("JavaFX") === -1) {
      $hide(me.config.webcamConfig.javaStartupButton); //Filereader only exists in Web-Clients. Hide Java Webcambutton in Webclient
    } else {
      $hide(me.config.webcamConfig.varNameBtnSnap); // Hide JS Webcambutton in Java Client
      fakeWebcamDiv = document.createElement("div");
      fakeWebcamDiv.classList.add("sol-webcam");
      (document.getElementById(me.config.webcamConfig.varNameContainer)).appendChild(fakeWebcamDiv);
    }
    me.buttonMode("noReset");
  },
  calcLiveCrop: function () {
    var me = this, c = me.config.webcamConfig;
    return [Math.floor(c.crop_width / c.dest_width * c.width), Math.floor(c.crop_height / c.dest_height * c.height)];
  },

  javaBrowser: function () {
    return (navigator.userAgent.indexOf("JavaFX") > -1);
  },

  buttonMode: function (mode) {
    var me = this,
        jCam = me.config.webcamConfig.javaStartupButton,
        wCam = me.config.webcamConfig.varNameBtnSnap,
        xChoose = me.config.filePickerField,
        xReset = me.config.clearPreviewField;

    if (mode === "resetOnly") {
      $show(xReset);
      $hide(xChoose);
      if (me.javaBrowser()) {
        $hide(jCam);
      } else {
        $hide(wCam);
      }
    } else if (mode === "noReset") {
      $hide(xReset);
      $show(xChoose);
      if (me.javaBrowser()) {
        $show(jCam);
      } else {
        Webcam.loaded && $show(wCam);
      }
    }
  },

  // guarded file upload for nextClicked or saveClicked
  uploadFile: function () {
    var me = this;
    (typeof me.displayImage !== "undefined" && me.displayImage.src && me.uploadImage(me.displayImage.src)) ||
    ($var(me.config.webcamConfig.varNameContainer) && me.webcam && me.webcam.savePicture(me.uploadImage.bind(me)));
  },

  // function can be used to upload an image
  uploadImage: function (image) {
    var me = this,
        base64, result;
    try {
      ELOF.showLoadingDiv();
      base64 = image.replace(/^data:image\/(jpeg|png|gif|bmp);base64,/, "");
      if (base64 !== "") {

        result = sol.common.IxUtils.execute("RF_sol_common_document_service_UploadFile", {
          objId: ELO_PARAMS.ELO_OBJID,
          base64Content: base64,
          encryptionSet: 0,
          cfg: me.config.photoConfig
        });

        sol.common.forms.Utils.setHiddenValue({ type: "GRP", key: me.config.photoReferenceField }, result.guid);
        sol.common.forms.Utils.setHiddenValue({ type: "MAP", key: me.config.photoReferenceFieldObjId }, result.objId);
      }
    } catch (err) {
      console.info("Error: " + err);
    } finally {
      ELOF.hideLoadingDiv();
    }
    return false;
  },

  createDivsIfNoneExist: function () {
    var me = this;
    if (!me.displayImage) {
      me.displayDiv = document.createElement("div");
      me.displayDiv.classList.add("display-image-result-container");
      me.displayDiv = (document.getElementById(me.config.webcamConfig.varNameContainer)).appendChild(me.displayDiv);
      me.displayImage = document.createElement("img");
      me.displayImage.classList.add("display-image-result");
      me.displayDiv.appendChild(me.displayImage);
    }
  },

  displayImageInPage: function (reader, file) {
    var me = this;

    me.createDivsIfNoneExist();
    me.displayImage.file = file;
    reader.onload = (function (aImg) { return function (e) { aImg.src = e.target.result; }; })(me.displayImage);
    reader.readAsDataURL(file);
    me.buttonMode("resetOnly");
    $var(me.config.webcamConfig.varNameContainer) && $var(me.config.webcamConfig.varNameContainer).classList.add("webcamImageHidden");
  },

  fileExtOk: function (allowedTypes, fileName) {
    var allowedExts = allowedTypes.map(function (type) {
          var match = type.match(/.*\/(\w+)$/);
          return match ? match[1] : type;
        }),
        fileExt = (function (name) {
          var match = name.match(/.*\.(\w+)$/);
          return match ? match[1] : "";
        })(fileName);

    return allowedExts.indexOf(fileExt) > -1;
  },

  preconditionsFulfilled: function (file) {
    var me = this,
        ftMsg = ($val("WF_MAP_FILEPICKER_FILETYPEMSG") || "File type not supported. Allowed file types:") + " ",
        fsMsg = ($val("WF_MAP_FILEPICKER_FILESIZEMSG") || "File too big. Maximum allowed file size:") + " ",
        fcMsg = ($val("WF_MAP_FILEPICKER_FILECORRUPTMSG") || "This file is corrupt and can't be uploaded. Please try to repair it or convert it to a supported format:") + " ",
        allowedTypes = (me.config.accept.split(",").map(function (s) { return s.trim(); }));

    if (file.size <= (Math.floor(me.config.maxSize * 1000000))) {
      if (allowedTypes.indexOf(file.type) > -1) {
        return true;
      } else if (file.name && me.fileExtOk(allowedTypes, file.name)) {
        return true;
      } else if (!file.name && (!file.type || file.type === "null")) {
        eloAlert(fcMsg + me.config.accept, "Error");
      } else {
        eloAlert(ftMsg + me.config.accept, "Info");
      }
    } else {
      eloAlert(fsMsg + me.config.maxSize + "MB.", "Info");
    }
    return false;
  },

  fileDroppedHandler: function (ev) {
    var me = this, dt = ev.dataTransfer, reader = new FileReader();
    ev.preventDefault();
    me.hideDropZone();
    // If dropped items aren't files, reject them
    if (dt.items && dt.items.length > 0 && dt.items[0].kind == "file") {
      me.dragdropfile = dt.items[0].getAsFile();
      me.preconditionsFulfilled(me.dragdropfile) && me.displayImageInPage(reader, me.dragdropfile);
    }
  },

  filePickedInBrowser: function (ev) {
    var me = this, dt = ev.target, reader = new FileReader();
    if (navigator.userAgent.indexOf("JavaFX") === -1) { //web-client
      ev.preventDefault();
      if (dt.files && dt.files.length > 0) {
        me.dragdropfile = dt.files[0];
        me.preconditionsFulfilled(me.dragdropfile) && me.displayImageInPage(reader, me.dragdropfile);
      }
    }
  },

  filePickedHandler: function (e) {
    var me = this, data;
    if (navigator.userAgent.indexOf("JavaFX") === -1) { // Web-Client
      if (me.filePicker) {
        me.filePicker.value = null;
        me.filePicker.click();  //this executes the actual handler for opening the file selection dialog. This trick is required, because I did not
                                //want to use the standard filepicker Button. (it would not look like an ELO Button)
      }
    } else {  // Java-Client
      data = { text: "capture", objId: ELO_PARAMS.ELO_OBJID }; // I think, sendCustomMessage needs this?
      me.createDivsIfNoneExist();
      api.communication.Parent.sendCustomMessage("workspace.showFileChooserDialog_" + JSON.stringify({ accept: me.config.accept, maxSize: me.config.maxSize }), data, function (data, event) {
        me.dragdropfile = { base64: String(data.response.base64), type: String(data.response.type) };
        if (me.preconditionsFulfilled(data.response)) {
          if (me.dragdropfile.base64 !== "") {
            me.displayImage.src = "data:" + data.response.type + ";base64," + me.dragdropfile.base64;
            me.buttonMode("resetOnly");
            $var(me.config.webcamConfig.varNameContainer) && $var(me.config.webcamConfig.varNameContainer).classList.add("webcamImageHidden");
          }
        }
      });
    }
  },

  merge: function (custom, base, log, path, assignCallback, recursionCheck) {
    var me = this,
        prop;
    log = log || [];
    path = path || "";

    recursionCheck = recursionCheck || function (custom, base, prop) {
      return base[prop] instanceof Object && !(base[prop] instanceof Array) && !(base[prop] instanceof Date);
    };

    assignCallback = assignCallback || function (target, source, propertyName) {
      target[propertyName] = source[propertyName];
    };

    for (prop in base) {
      if (base.hasOwnProperty(prop)) {
        //check for same type (array must be checked separately) and use default property instead
        if (typeof custom[prop] !== "undefined" && ((typeof custom[prop] !== typeof base[prop]) ||
            (Array.isArray(base[prop]) !== Array.isArray(custom[prop])))) {
          log.push("Warning: The type of custom property " + path + "." + prop + " is not the same as in the target. Custom property is ignored.");
          custom[prop] = me.clone(base[prop]);
        } else if (recursionCheck(custom, base, prop)) {
          //recursion
          custom[prop] = me.merge(custom[prop] || {}, base[prop], log, path + "." + prop, assignCallback, recursionCheck); //return empty object if p does not exist in target
        } else if (custom[prop] === undefined) {
          //copy default property only if not exist in custom
          if (base[prop] instanceof Date) {
            custom[prop] = new Date(base[prop]);
          } else if (base[prop] instanceof Array) {
            custom[prop] = me.clone(base[prop]);
          } else {
            assignCallback(custom, base, prop);
          }
        }
      }
    }
    return custom;
  },

  /**
   * small wrapper around interval functions
   *
   * This function is 100% decoupled from any ELO-specifics and can be used in pure Javascript environments
   *
   * f: function to perform work
   *
   * until: function to check if f should be run again or not
   *
   * after: function to run after until conditions were met
   *
   * intervalTime: time in ms (interval runs every x ms)
   *
   *     params: {
   *       f: any            pass f any param you like
   *       f_message:String  console.log before f is called
   *       until:any   pass any any param you like
   *       until_message: String  console.log before until is called
   *       conditionmet_message:String  console.log if contition is met
   *       after:any   pass after any param you like
   *       afterafter_message:String   console.log after after was called
   *       logging: Boolean    enables logging if set to true
   *       maxtries: Integer     how often the f function will be executed before the interval is stopped
   *       name: String          name in logger
   *     }
   *
   * if the messages are defined as empty strings, no console-logging will be done.
   *
   * Of course the execution time of f and until is synchronized. f always runs before until.
   *
   * @returns a function, which can later be used to stop the interval, if the until condition seems to never reach an end
  */
  intervalUntil: function (f, until, after, intervalTime, params) {
    var intervals = [], parms = params || {}, maxTries = params.maxtries - 1, executed = 0,
        allowF = true, allowUntil = false; //for synchronizing f and until
    function logIt(message, paramMsg) {
      parms.logging && paramMsg !== "" && console.log("Interval " + (parms.name ? "'" + parms.name + "'" : "") + ":" + (paramMsg || message));
    }

    intervals.push(
      setInterval(  //setInterval is a standard Javascript function
        function () {
          if (!allowF) {
            return;
          }
          logIt("performing work", parms.f_message);
          f && f(parms.f);
          executed++;
          allowF = false;
          allowUntil = true;
        }, intervalTime
      )
    );
    intervals.push(
      setInterval(
        function () {
          if (!allowUntil) {
            return;
          }
          logIt("until condition met?", parms.until_message);
          if (parms.maxtries && maxTries-- === 0 || until && until(parms.until)) {
            logIt(maxTries === 0 ? "Tried too many times:" + parms.maxTries : "until condition met!", maxTries === 0 ? parms.maxTriesMessage : parms.conditionmet_message);
            intervals.forEach(clearInterval);   //clearInterval is a standard Javascript function
            after && after(parms.after);
            logIt("after callback executed. Interval finished & closed after " + executed + " cycles!", parms.afterafter_message);
          }
          allowF = true;
          allowUntil = false;
        }, intervalTime
      )
    );
    return function () {
      intervals.forEach(clearInterval);
      logIt("closed manually after " + executed + " cycles!", parms.clearedManually);
    };
  },

  // takes a webcam-picture
  JS_WEBCAM_SNAP: function () {
    var me = this,
        data = { text: "capture", objId: ELO_PARAMS.ELO_OBJID, cropImageDimension: { width: me.config.webcamConfig.width, height: me.config.webcamConfig.height } };

    me.createDivsIfNoneExist();

    if (navigator.userAgent.indexOf("JavaFX") === -1) { // Web-Client
      me.JS_PICTURE_CLEAR();
      Webcam.freeze();  //library function
      me.webcam.state = true;
      me.buttonMode("resetOnly");
    } else {  // Java-Client
      try {
        me.webcamImage = "";
        api.communication.Parent.sendCustomMessage("sol.common.jc.WebcamUtils.getImage", data, function (data, event) {
          me.webcamImage = data.response;
          if (typeof me.webcamImage === "string" && me.webcamImage !== "") {
            me.displayImage.src = "data:image/jpeg;base64," + me.webcamImage;
            me.buttonMode("resetOnly");
            $var(me.config.webcamConfig.varNameContainer) && $var(me.config.webcamConfig.varNameContainer).classList.add("webcamImageHidden");
          }
        });
      } catch (e) {
        console.log("Webcam already in use in another application");
      }
    }

  },

  // clears / resets the captured picture
  JS_PICTURE_CLEAR: function () {
    var me = this;

    me.webcam.state = false;
    me.webcam.capture = false;
    Webcam.unfreeze();  //library function
    if (typeof me.displayImage !== "undefined" && me.displayImage.src) {
      me.displayImage.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+P+/HgAFhAJ/wlseKgAAAABJRU5ErkJggg=="; // base64 empty pixel
      me.displayImage.src = "";
      me.buttonMode("noReset");
      $var(me.config.webcamConfig.varNameContainer) && $var(me.config.webcamConfig.varNameContainer).classList.remove("webcamImageHidden");
    } else {
      me.buttonMode("noReset");
    }
  },

  showDropZone: function (e) {
    this.dropZone.style.visibility = "visible";
  },

  hideDropZone: function (e) {
    this.dropZone.style.visibility = "hidden";
  }

});