    /**
     * 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";
      }

    });