(function(){
  if (!("$TDP" in window)){
    console.error("Missing $TDP");
  }
  
  const validatePluginObject = function(pluginObject){
    if (!("$token" in pluginObject)){
      throw "Invalid plugin object.";
    }
  };
  
  //
  // Block: Setup a simple JavaScript object configuration loader.
  //
  window.TDPF_loadConfigurationFile = function(pluginObject, fileNameUser, fileNameDefault, onSuccess, onFailure){
    validatePluginObject(pluginObject);
    
    let identifier = pluginObject.$id;
    let token = pluginObject.$token;
    
    $TDP.checkFileExists(token, fileNameUser).then(exists => {
      let fileName = exists ? fileNameUser : fileNameDefault;
      
      if (fileName === null){
        onSuccess && onSuccess({});
        return;
      }
      
      (exists ? $TDP.readFile(token, fileName, true) : $TDP.readFileRoot(token, fileName)).then(contents => {
        let obj;
        
        try{
          obj = eval("(" + contents + ")");
        }catch(err){
          if (!(onFailure && onFailure(err))){
            $TD.alert("warning", "Problem loading '" + fileName + "' file for '" + identifier + "' plugin, the JavaScript syntax is invalid: " + err.message);
          }
          
          return;
        }
        
        onSuccess && onSuccess(obj);
      }).catch(err => {
        if (!(onFailure && onFailure(err))){
          $TD.alert("warning", "Problem loading '" + fileName + "' file for '" + identifier + "' plugin: " + err.message);
        }
      });
    }).catch(err => {
      if (!(onFailure && onFailure(err))){
        $TD.alert("warning", "Problem checking '" + fileNameUser + "' file for '" + identifier + "' plugin: " + err.message);
      }
    });
  };
  
  //
  // Block: Setup a function to add/remove custom CSS.
  //
  window.TDPF_createCustomStyle = function(pluginObject){
    validatePluginObject(pluginObject);
    
    let element = document.createElement("style");
    element.id = "plugin-" + pluginObject.$id + "-"+Math.random().toString(36).substring(2, 7);
    document.head.appendChild(element);
    
    return {
      insert: (rule) => element.sheet.insertRule(rule, 0),
      remove: () => element.remove(),
      element: element
    };
  };
  
  //
  // Block: Setup a function to mimic a Storage object that will be saved in the plugin.
  //
  window.TDPF_createStorage = function(pluginObject, onReady){
    validatePluginObject(pluginObject);
    
    if ("$storage" in pluginObject){
      if (pluginObject.$storage !== null){ // set to null while the file is still loading
        onReady(pluginObject.$storage);
      }
      
      return;
    }
    
    class Storage{
      get length(){
        return Object.keys(this).length;
      }
      
      key(index){
        return Object.keys(this)[index];
      }
      
      getItem(key){
        return this[key] || null;
      }
      
      setItem(key, value){
        this[key] = value;
        updateFile();
      }
      
      removeItem(key){
        delete this[key];
        updateFile();
      }
      
      clear(){
        for(key of Object.keys(this)){
          delete this[key];
        }
        
        updateFile();
      }
      
      replace(obj, silent){
        for(let key of Object.keys(this)){
          delete this[key];
        }
        
        for(let key in obj){
          this[key] = obj[key];
        }
        
        if (!silent){
          updateFile();
        }
      }
    };
    
    var storage = new Proxy(new Storage(), {
      get: function(obj, prop, receiver){
        const value = obj[prop];
        return typeof value === "function" ? value.bind(obj) : value;
      },
      
      set: function(obj, prop, value){
        obj.setItem(prop, value);
        return true;
      },
      
      deleteProperty: function(obj, prop){
        obj.removeItem(prop);
        return true;
      },
      
      enumerate: function(obj){
        return Object.keys(obj);
      }
    });
    
    var delay = -1;
    
    const updateFile = function(){
      window.clearTimeout(delay);
      
      delay = window.setTimeout(function(){
        $TDP.writeFile(pluginObject.$token, ".storage", JSON.stringify(storage));
      }, 0);
    };
    
    pluginObject.$storage = null;
    
    window.TDPF_loadConfigurationFile(pluginObject, ".storage", null, function(obj){
      storage.replace(obj, true);
      onReady(pluginObject.$storage = storage);
    }, function(){
      onReady(pluginObject.$storage = storage);
    });
  };
})();