import page from '/lib/page.mjs';
import { Lang } from '/lib/polyglot/language.js';
import { Helpers } from '/helpers.js';
import { IndexApp } from '/web/index-app.js';
import { Widgets } from '/lib/widgets.js';


class App {
  constructor() {
    console.log("app constructor");
    this.widgets = new Widgets;

    //globals
    window._app = this;
    window._cellWidgets = this.widgets;
    window._LangManager = new Lang;
    window._noty = new Noty;

    _LangManager.setLanguage().then(res => {
      return this.initDB()
    }).then(res => {

      this.helpers = new Helpers;
      this.initUser();
      
      //client routes
      page('/', this.HandleIndex);
      page('/setup', this.HandleSetupIndex);
      page('/profile', this.HandleUserIndex);
      page('/worlds', this.HandleIndex);
      page('/:user/worlds', this.HandleUserWorlds);
      page('/:user/worlds/:type', this.HandleUserWorldsWithType);
      page('/:user/:type/:name/edit/:file', this.HandleFileEdit);
      page('/:user/:space', this.HandleParsableRequestGenID);
      page('/:user/:space/:id', this.HandleParsableRequestWithID);
      page('/:user/:space/index.vwf/:id', this.HandleParsableRequestWithID);
      page('/:user/:space/load/:savename', this.HandleParsableLoadRequest);
      page('/:user/:space/:id/load/:savename', this.HandleParsableRequestWithID);

      page('/:user/:space/load/:savename/:rev', this.HandleParsableLoadRequestWithRev);
      page('/:user/:space/:id/load/:savename/:rev', this.HandleParsableRequestWithID);

      page('*', this.HandleNoPage);

      page();


    })


  }

  initDB() {

    var config = JSON.parse(localStorage.getItem('lcs_config'));
    if (!config) {
      config = {
        'dbhost': 'https://' + window.location.hostname + ':8080/gun', //'http://localhost:8080/gun',
        'reflector': 'https://' + window.location.hostname + ':3002',
        'language': 'en'
      }
      localStorage.setItem('lcs_config', JSON.stringify(config));
    }

    const dbConnection = new Promise((resolve, reject) => {

      this.db = Gun(this.dbHost);
      this.user = this.db.user();
      window._LCSDB = this.db;
      window._LCSUSER = this.user;
      window._LCS_SYS_USER = undefined;
      window._LCS_WORLD_USER = undefined;

      _LCSDB.get('lcs/app').get('pub').once(res => {

        if (res) {
          window._LCS_SYS_USER = this.db.user(res);
        }
      });

      _LCSDB.on('hi', function (peer) {

        let msg = 'Connected to ' + peer.url;

        let noty = new Noty({
          text: msg,
          timeout: 2000,
          theme: 'mint',
          layout: 'bottomRight',
          type: 'success'
        });
        noty.show();
        console.log(msg)
      })

      _LCSDB.on('bye', function (peer) {

        let msg = 'No connection to ' + peer.url;
        let noty = new Noty({
          text: msg,
          timeout: 1000,
          theme: 'mint',
          layout: 'bottomRight',
          type: 'error'
        });
        noty.show();
        console.log(msg)
      })
      resolve('ok');
    });
    return dbConnection
  }

  initUser() {
    _LCSUSER.recall({ sessionStorage: 1 });
  }


  get reflectorHost() {

    var res = "";
    let config = localStorage.getItem('lcs_config');
    if (config) {
      res = JSON.parse(config).reflector;
    }
    return res;
  }

  get dbHost() {

    var res = "";
    let config = localStorage.getItem('lcs_config');
    if (config) {
      res = JSON.parse(config).dbhost;
    }
    return res;
  }

  async loadProxyDefaults() {

    //load to DB default proxy files (VWF & A-Frame components)

    let proxyResponse = await fetch('/proxy-files', { method: 'get' });
    let proxyFiles = await proxyResponse.json();
    let filterProxyFiles = proxyFiles.filter(el => (el !== null));
    console.log(filterProxyFiles);

    var origin = window.location.origin;
    //var userPub = _LCSUSER.is.pub;

    let proxyObj = {};

    for (var index in filterProxyFiles) {

      let el = filterProxyFiles[index];

      if (el) {
        var url = origin + el;
        var entryName = url.replace(origin + '/defaults/', "").split(".").join("_");
        let proxyFile = await fetch(url, { method: 'get' });
        let responseText = await proxyFile.text();

        if (responseText) {
          let obj = {
            //'owner': userPub,
            'file': responseText
          }
          proxyObj[entryName] = obj;
        }
      }
    }
    console.log(proxyObj);

    Object.keys(proxyObj).forEach(el => {
      _LCSDB.user().get('proxy').get(el).put(proxyObj[el]);
    })

  }

  async loadWorldsDefaults() {

   //load to DB default worlds

    let worldsResponse = await fetch('/world-files', { method: 'get' });
    let worldFiles = await worldsResponse.json();
    let filterworldFiles = worldFiles.filter(el => (el !== null));
    console.log(filterworldFiles);

    let worldsObj = {};

    for (var index in filterworldFiles) {

      let el = filterworldFiles[index];

      if (el) {
        let url = window.location.origin + el;
        var entryName = url.replace(window.location.origin + '/defaults/worlds/', "").split(".").join("_");

        let worldName = entryName.split("/")[0];
        let userPub = _LCSUSER.is.pub;

        let worldFile = await fetch(url, { method: 'get' });
        let worldSource = await worldFile.text();
        if (worldSource) {
          let modified = new Date().valueOf();

          let obj = {
            'file': worldSource,
            'modified': modified

          }

          if (!worldsObj[worldName]) {
            worldsObj[worldName] = {
              'parent': '-',
              'owner': userPub,
              'featured': true,
              'published': true
            }
          }

          let entry = entryName.replace(worldName + '/', "");
          worldsObj[worldName][entry] = obj;

        }
      }
    }

    console.log(worldsObj);

    Object.entries(worldsObj).forEach(el => {

      let worldName = el[0];
      let files = el[1];
      Object.entries(files).forEach(file => {

        _LCSDB.user().get('worlds').get(worldName).get(file[0]).put(file[1]);

      })
    })
  }

  async loadEmptyDefaultProto() {

     //empty proto world
     let userPub = _LCSUSER.is.pub;
     let worldsObj = {};
     let emptyWorld = {

      "index_vwf_yaml": YAML.stringify(
        {
          "extends": "http://vwf.example.com/aframe/ascene.vwf"
        }, 4),

      "index_vwf_config_yaml": YAML.stringify(
        {
        "info": {
          "title": "Empty World"
        },
        "model": {
          "vwf/model/aframe": null
        },
        "view": {
          "vwf/view/aframe": null,
          "vwf/view/editor-new": null
        } 
      }, 4),

      "index_vwf_html": "",
      "appui_js": "",
      "info_json": JSON.stringify ({
        "info": {
          "en": {
              "title": "Empty World",
              "imgUrl": "",
              "text": "Empty World"
          },
          "ru": {
              "title": "Новый Мир",
              "imgUrl": "",
              "text": "Новый Мир"
          }
      }
      })
    }

    worldsObj['empty'] = {
      'parent': '-',
      'owner': userPub,
      'featured': true,
      'published': true
    }

    Object.keys(emptyWorld).forEach(el=>{
      let modified = new Date().valueOf();
      let obj = {
        'file': emptyWorld[el],
        'modified': modified
      }
      worldsObj['empty'][el] = obj;
    }) 

    console.log(worldsObj);

    Object.entries(worldsObj).forEach(el => {

      let worldName = el[0];
      let files = el[1];
      Object.entries(files).forEach(file => {

        _LCSDB.user().get('worlds').get(worldName).get(file[0]).put(file[1]);

      })
    })

  }

  //load defaults for first registered user running ./setup

  HandleSetupIndex() {

    window._app.hideProgressBar();
    window._app.hideUIControl();

    let el = document.createElement("div");
    el.setAttribute("id", "admin");
    document.body.appendChild(el);

   
    _LCSDB.on('auth',
      async function (ack) {

        if (_LCSUSER.is) {

          let setPubKey = {

            $cell: true,
            $components: [
              {
                $type: "p",
                class: "mdc-typography--headline5",
                $text: "1. Set app system user PUB key"
            },
              {
                $type: "button",
                class: "mdc-button mdc-button--raised",
                $text: "Set app PUB key",
                onclick: function (e) {
                  console.log("admin action");
                  _LCSDB.get('lcs/app').get('pub').put(_LCSUSER.is.pub);
                }
              }
            ]
      
          }
      
          let adminComponents = [];
      

          let defaultPub = await _LCSDB.get('lcs/app').get('pub').once().then();
          if (!defaultPub) {
            adminComponents.push(setPubKey);
          }

          if (_LCSUSER.is.pub == defaultPub) {

            let loadEmpty = {
              $cell: true,
              $components: [
                {
                  $type: "p",
                  class: "mdc-typography--headline5",
                  $text: "3. Initialize empty World proto"
              },
                {
                  $type: "button",
                  id: "loadDefaults",
                  class: "mdc-button mdc-button--raised",
                  $text: "Init empty world",
                  onclick: function (e) {
                    console.log("admin action");
                    window._app.loadEmptyDefaultProto();
                  }
                }
              ]
            }
            

            let loadDefaults = {
              $cell: true,
              $components: [
                {
                  $type: "p",
                  class: "mdc-typography--headline5",
                  $text: "4. Load Sample Worlds protos from server (optional)"
              },
                {
                  $type: "button",
                  id: "loadDefaults",
                  class: "mdc-button mdc-button--raised",
                  $text: "Load default worlds (from server)",
                  onclick: function (e) {
                    console.log("admin action");
                    window._app.loadWorldsDefaults();
                  }
                }
              ]
            }

            let loadDefaultsProxy = {
              $cell: true,
              $components: [
                {
                  $type: "p",
                  class: "mdc-typography--headline5",
                  $text: "3. Load VWF & A-Frame default components"
              },
                {
                  $type: "button",
                  class: "mdc-button mdc-button--raised",
                  $text: "Load defaults Proxy",
                  onclick: function (e) {
                    console.log("admin action");
                    window._app.loadProxyDefaults();
                  }
                }
              ]
            }
            adminComponents.push(setPubKey, loadDefaultsProxy, loadEmpty, loadDefaults);
          }

          document.querySelector("#admin").$cell({
            $cell: true,
            id: 'adminComponents',
            $type: "div",
            $components: adminComponents
          });

        }
      })
  }

  //TODO: profile

  HandleUserIndex(ctx) {

    console.log("USER INDEX");
    window._app.hideProgressBar();
    window._app.hideUIControl();

    _LCSDB.on('auth',
      async function (ack) {
        if(ack.pub){
          document.querySelector("#profile")._status = "User: " + _LCSUSER.is.alias //+' pub: ' + _LCSUSER.is.pub;
          document.querySelector("#profile").$update();
        }
      })

      let el = document.createElement("div");
      el.setAttribute("id", "userProfile");
      document.body.appendChild(el);

      let userProfile = {
        $type: 'div',
        id: "profile",
        _status: "",
        $init: function(){
          this._status = "user is not signed in..."
        },
        $update: function(){
          this.$components = [
            {
                $type: "h1",
                class: "mdc-typography--headline4",
                $text: this._status //"Profile for: " + _LCSUSER.is.alias
            }
          ]
        }
      }

      document.querySelector("#userProfile").$cell({
        $cell: true,
        $type: "div",
        $components: [userProfile]
      })
  }


  async HandleUserWorlds(ctx) {

    console.log("USER WORLDS INDEX");
    console.log(ctx.params);
    let user = ctx.params.user;

    page.redirect('/' + user + '/worlds/protos');

  }

  async HandleFileEdit(ctx) {

    console.log("USER WORLD FILE EDIT");

    let user = ctx.params.user;
    let worldName = ctx.params.name;
    let fileOriginal = ctx.params.file;
    let type = ctx.params.type;

    window._app.hideProgressBar();
    window._app.hideUIControl();


    _LCSDB.on('auth',
      async function (ack) {

        if (_LCSUSER.is) {

          if (_LCSUSER.is.alias == user) {

            var worldType = 'worlds';
            var file = fileOriginal;
            if (type == 'state') {
              worldType = 'documents';
              file = _app.helpers.replaceSubStringALL(fileOriginal, "~", '/');
            }

            let worldFile = await _LCSUSER.get(worldType).get(worldName).get(file).once().then();
            if (worldFile) {
              console.log(worldFile.file);

              let el = document.createElement("div");
              el.setAttribute("id", "worldFILE");
              document.body.appendChild(el);


              let aceEditorCell = {

                $type: "div",
                $components: [
                  {
                    class: "aceEditor",
                    id: "aceEditor",
                    //style: "width:1200px; height: 800px",
                    $type: "div",
                    $text: worldFile.file,
                    $init: function () {
                      var mode = "ace/mode/json";
                      if (file.includes('_yaml'))
                        mode = "ace/mode/yaml"
                      if (file.includes('_js'))
                        mode = "ace/mode/javascript"

                      var editor = ace.edit("aceEditor");
                      editor.setTheme("ace/theme/monokai");
                      editor.setFontSize(16);
                      editor.getSession().setMode(mode);
                      editor.setOptions({
                        maxLines: Infinity
                    });
                    }
                  },
                  {
                    $type: "button",
                    class: "mdc-button mdc-button--raised",
                    $text: "Save",
                    onclick: async function (e) {
                      console.log("save new info");
                      let editor = document.querySelector("#aceEditor").env.editor;
                      let newInfo = editor.getValue();
                      _LCSUSER.get(worldType).get(worldName).get(file).get('file').put(newInfo, res => {
                        if (res) {
                          let modified = new Date().valueOf();
                          _LCSUSER.get(worldType).get(worldName).get(file).get('modified').put(modified);
                        }
                      })
                    }
                  },
                  {
                    $type: "button",
                    class: "mdc-button mdc-button--raised",
                    $text: "Close",
                    onclick: function (e) {
                      console.log("close");
                      if (type == "proto")
                        window.location.pathname = "/" + user + '/worlds/protos'
                      if (type == "state")
                        window.location.pathname = "/" + user + '/worlds/states'
                    }
                  }
                ]
              }
              document.querySelector("#worldFILE").$cell({
                $cell: true,
                $type: "div",
                $components: [aceEditorCell
                ]
              })
            }
          }
        }
      })
  }

  async HandleUserWorldsWithType(ctx) {

    console.log("USER WORLDS INDEX");
    console.log(ctx.params);
    let user = ctx.params.user;
    let type = ctx.params.type;

    window._app.hideProgressBar();
    window._app.hideUIControl();

    if (!_app.indexApp) {
      _app.indexApp = new IndexApp;
      document.querySelector('head').innerHTML += '<link rel="stylesheet" href="/web/index-app.css">';
    }

    if (!document.querySelector('#app')) {
      await _app.indexApp.initApp();
    }

    if (type == 'protos') {
      await _app.indexApp.getWorldsProtosFromUserDB(user);
    } else if (type == 'states') {
      await _app.indexApp.getWorldsFromUserDB(user);
    }

  }

  async HandleIndex() {

    console.log("INDEX");

    window._app.hideProgressBar();
    window._app.hideUIControl();

    _app.indexApp = new IndexApp;
    document.querySelector('head').innerHTML += '<link rel="stylesheet" href="/web/index-app.css">';

    await _app.indexApp.generateFrontPage();
    await _app.indexApp.initApp();
    await _app.indexApp.getAppDetailsFromDB();

  }


  HandleNoPage() {

    console.log("no such page")

  }

  //handle parcable requests

  HandleParsableLoadRequest(ctx) {

    let app = window._app;
    console.log(ctx.params);
    //var pathname = ctx.pathname;
    var spaceName = ctx.params.space;
    var saveName = ctx.params.savename;
    let user = ctx.params.user;

    page.redirect('/' + user + '/' + spaceName + '/' + app.helpers.GenerateInstanceID() + '/load/' + saveName);

  }

  HandleParsableLoadRequestWithRev(ctx) {

    let app = window._app;
    console.log(ctx.params);
    //var pathname = ctx.pathname;
    var spaceName = ctx.params.space;
    var saveName = ctx.params.savename;
    var rev = ctx.params.rev;
    let user = ctx.params.user;

    page.redirect('/' + user + '/' + spaceName + '/' + app.helpers.GenerateInstanceID() + '/load/' + saveName + '/' + rev);

  }

  async setUserPaths(user) {

    await _LCSDB.get('users').get(user).get('pub').once(res => {
      if (res)
        window._LCS_WORLD_USER = _LCSDB.user(res);
    }).then();

  }

  async HandleParsableRequestGenID(ctx) {

    let app = window._app;
    console.log(ctx.params);
    let user = ctx.params.user;
    var pathname = ctx.pathname;

    await app.setUserPaths(user);

    if (pathname[pathname.length - 1] == '/') {
      pathname = pathname.slice(0, -1)
    }

    let pathToParse = pathname.replace('/' + user, "");

    app.helpers.Process(pathToParse).then(parsedRequest => {

      localStorage.setItem('lcs_app', JSON.stringify({ path: parsedRequest }));
      console.log(parsedRequest);

      if ((parsedRequest['instance'] == undefined) && (parsedRequest['private_path'] == undefined) && (parsedRequest['public_path'] !== "/") && (parsedRequest['application'] !== undefined)) {

        page.redirect(pathname + '/' + app.helpers.GenerateInstanceID());

      }
    });

  }

  
  async HandleParsableRequestWithID(ctx) {

    let app = window._app;
    console.log(ctx.params);
    var pathname = ctx.pathname;
    let user = ctx.params.user;

    if (pathname[pathname.length - 1] == '/') {
      pathname = pathname.slice(0, -1)
    }

    await app.setUserPaths(user);

    let pathToParse = pathname.replace('/' + user, "");

    app.helpers.Process(pathToParse).then(parsedRequest => {

      localStorage.setItem('lcs_app', JSON.stringify({ path: parsedRequest }));
      console.log(parsedRequest);

      var userLibraries = { model: {}, view: {} };
      var application;

      vwf.loadConfiguration(application, userLibraries, compatibilityCheck);
    });
  }

  async HandleParsableRequest(ctx) {

    let app = window._app;
    console.log(ctx.params);
    var pathname = ctx.pathname;

    if (pathname[pathname.length - 1] == '/') {
      pathname = pathname.slice(0, -1)
    }

    var parsedRequest = await app.helpers.Process(pathname);
    localStorage.setItem('lcs_app', JSON.stringify({ path: parsedRequest }));
    console.log(parsedRequest);

    if ((parsedRequest['instance'] == undefined) && (parsedRequest['private_path'] == undefined) && (parsedRequest['public_path'] !== "/") && (parsedRequest['application'] !== undefined)) {

      // Redirect if the url request does not include an application/file && a default 'index.vwf.yaml' exists
      // page.redirect(pathname + '/' + app.helpers.GenerateInstanceID());
      window.location.pathname = pathname + '/' + app.helpers.GenerateInstanceID() 

      //return true;
    } else {

      //return false;
    }

    var userLibraries = { model: {}, view: {} };
    var application;

    vwf.loadConfiguration(application, userLibraries, compatibilityCheck);

  }

  //get DB application state information for reflector (called from VWF)

  async getApplicationState() {

    let dataJson = JSON.parse(localStorage.getItem('lcs_app'));
    if (dataJson) {
      if (!dataJson.path['instance']) return undefined;
    }

    let userAlias = await _LCS_WORLD_USER.get('alias').once().then();
    let userPub = await _LCSDB.get('users').get(userAlias).get('pub').once().then();

    let loadInfo = await this.getLoadInformation(dataJson);
    let saveInfo = await this.loadSaveObject(loadInfo);
    let loadObj = {
      loadInfo: loadInfo,
      path: dataJson.path,
      saveObject: saveInfo,
      user: userAlias
    }

    //dataJson.app = loadObj;
    localStorage.setItem('lcs_app', JSON.stringify(loadObj));

    console.log(loadObj);

    //temporary solution for syncing DB replicas using Gun.load()
    await _LCS_SYS_USER.get('proxy').load(res=>{}, {wait: 200}).then();
    await _LCSDB.user(userPub).get('worlds').get(loadObj.path.public_path.slice(1)).load(res=>{}, {wait: 200}).then();

    return loadObj

  }

  // LookupSaveRevisions takes the public path and the name of a save, and provides
  // an array of all revisions for that save. (If the save does not exist, this will be
  // an empty array).

  async lookupSaveRevisions(public_path, save_name) {
    var result = [];
    var states = [];
    let docName = 'savestate_/' + public_path + '/' + save_name + '_vwf_json';

    let revs = await _LCS_WORLD_USER.get('documents').get(public_path).get(docName).get('revs').once().then();
    if (revs) {
      for (const res of Object.keys(revs)) {
        if (res !== '_') {
          let el = await _LCS_WORLD_USER.get('documents').get(public_path).get(docName).get('revs').get(res).once().then();
          if (el)
            result.push(parseInt(el.revision));
        }
      }
      return result
    }
  }

  // GetLoadInformation receives a parsed request {private_path, public_path, instance, application} and returns the
  // details of the save that is designated by the initial request. The details are returned in an object
  // composed of: save_name (name of the save) save_revision (revision of the save), explicit_revision (boolean, true if the request
  // explicitly specified the revision, false if it did not), and application_path (the public_path of the application this is a save for).

  async getLoadInformation(response) {
    let parsedRequest = response.path;
    var result = { 'save_name': undefined, 'save_revision': undefined, 'explicit_revision': undefined, 'application_path': undefined };
    if (parsedRequest['private_path']) {
      var segments = this.helpers.GenerateSegments(parsedRequest['private_path']);
      if ((segments.length > 1) && (segments[0] == "load")) {
        var potentialRevisions = await this.lookupSaveRevisions((parsedRequest['public_path']).slice(1), segments[1]);

        console.log('!!!!! - ', potentialRevisions);
        if (potentialRevisions.length > 0) {
          result['save_name'] = segments[1];

          if (segments.length > 2) {
            var requestedRevision = parseInt(segments[2]);
            if (requestedRevision) {
              if (potentialRevisions.indexOf(requestedRevision) > -1) {
                result['save_revision'] = requestedRevision;
                result['explicit_revision'] = true;
                result['application_path'] = parsedRequest['public_path'];
              }
            }
          }
          if (result['explicit_revision'] == undefined) {
            result['explicit_revision'] = false;
            potentialRevisions.sort();
            result['save_revision'] = potentialRevisions.pop();
            result['application_path'] = parsedRequest['public_path'];
          }
        }
      }

    }
    return result;
  }

  async loadSaveObject(loadInfo) {

    //let objName = loadInfo[ 'save_name' ] +'/'+ "savestate_" + loadInfo[ 'save_revision' ];

    let objName = "savestate_" + loadInfo['application_path'] + '/' + loadInfo['save_name'] + '_vwf_json';
    let objNameRev = "savestate_" + loadInfo['save_revision'] + loadInfo['application_path'] + '/' + loadInfo['save_name'] + '_vwf_json';

    // if(loadInfo[ 'save_revision' ]){
    // } 

    let worldName = this.helpers.appPath //loadInfo[ 'application_path' ].slice(1);

    let saveObject = await _LCS_WORLD_USER.get('documents').get(worldName).get(objName).get('revs').get(objNameRev).once().then();
    let saveInfo = saveObject ? JSON.parse(saveObject.jsonState) : saveObject;

    return saveInfo;

  }

  // GetSaveInformation is a helper function that takes the application_path (/path/to/application).
  // It returns an array of all saves found for that
  // application (including separate entries for individual revisions of saves ).
  async getSaveInformation(application_path, userPUB) {
    var result = [];
    let user = _LCSDB.user(userPUB);

    var docName = application_path.slice(1);
    let potentialSaveNames = await user.get('documents').get(docName).once().then();

    if (potentialSaveNames) {

      for (const res of Object.keys(potentialSaveNames)) {
        if (res !== '_') {

          let el = await user.get('documents').path(docName).get(res).once().then();

          let revisionList = await this.lookupSaveRevisions(application_path.slice(1), el.filename);
          var latestsave = true;
          revisionList.sort();
          while (revisionList.length > 0) {
            var newEntry = {};
            newEntry['applicationpath'] = application_path;
            newEntry['savename'] = el.filename;
            newEntry['revision'] = revisionList.pop().toString();
            newEntry['latestsave'] = latestsave;
            if (latestsave) {
              newEntry['url'] = this.helpers.JoinPath(window.location.origin, application_path, "load", el.filename + "/");
            }
            else {
              newEntry['url'] = this.helpers.JoinPath(window.location.origin, application_path, "load", el.filename + "/", newEntry['revision'] + "/");
            }
            latestsave = false;
            result.push(newEntry);
          }
        }
      }
    }
    return result;
  }

  async getProtoWorldFiles(userPub, worldName, date) {

    let fileNamesAll = await _LCSDB.user(userPub).get('worlds').get(worldName).once().then();
    let worldFileNames = Object.keys(fileNamesAll).filter(el => (el !== '_') && (el !== 'owner') && (el !== 'parent')
      && (el !== 'info_json'));

    let worldObj = {};
    for (var el in worldFileNames) {
      let fn = worldFileNames[el];
      let res = await _LCSDB.user(userPub).get('worlds').get(worldName).get(fn).once().then();
      var data = {
        'file': res.file,
        'modified': res.modified
      }
      if (!date) {
        data = {
          'file': res.file
        }
      }
      worldObj[fn] = data;
    }
    console.log(worldObj);
    return worldObj
  }

  async cloneWorldPrototype(worldName, userName, newWorldName) {

    let userPub = await _LCSDB.get('users').get(userName).get('pub').once().then();
    //let worldProto = await _LCSDB.user(userPub).get('worlds').get(worldName).once().then();

    var worldID = window._app.helpers.GenerateInstanceID().toString();
    if (newWorldName) {
      worldID = newWorldName
    }

    //let modified = new Date().valueOf();
    console.log('clone: ' + worldName + 'to: ' + worldID);

    let newOwner = _LCSUSER.is.pub;

    let worldObj = {
      'owner': newOwner,
      'parent': userName + '/' + worldName,
      'featured': true,
      'published': true
    };

    let fileNamesAll = await _LCSDB.user(userPub).get('worlds').get(worldName).once().then();
    let worldFileNames = Object.keys(fileNamesAll).filter(el => (el !== '_') && (el !== 'owner') && (el !== 'parent') && (el !== 'featured') && (el !== 'published'));

    for (var el in worldFileNames) {

      let fn = worldFileNames[el];
      let res = await _LCSDB.user(userPub).get('worlds').get(worldName).get(fn).once().then();
      let data = {
        'file': res.file,
        'modified': res.modified
      }
      worldObj[fn] = data;
    }
    console.log(worldObj);

    Object.keys(worldObj).forEach(el => {
      _LCSUSER.get('worlds').get(worldID).get(el).put(worldObj[el]);
    })
  }

  async cloneWorldState(filename) {

    let myWorldProtos = await _LCSUSER.get('worlds').once().then();
    let userName = this.helpers.worldUser;
    let userPub = await _LCSDB.get('users').get(userName).get('pub').once().then();
    let protoUserRoot = this.helpers.getRoot(true).root;
    //let myName = _LCSUSER.is.alias;

    //let proto = Object.keys(myWorldProtos).filter(el => el == protoUserRoot);

    var protosKeys = [];
    if (myWorldProtos)
      protosKeys = Object.keys(myWorldProtos);

    if (protosKeys.includes(protoUserRoot)) {

      let userProtoFiles = await this.getProtoWorldFiles(userPub, protoUserRoot);
      let myProtoFiles = await this.getProtoWorldFiles(_LCSUSER.is.pub, protoUserRoot);

      let hashUP = await this.helpers.sha256(JSON.stringify(userProtoFiles));
      let hashMP = await this.helpers.sha256(JSON.stringify(myProtoFiles));

      if (hashUP == hashMP) {
        this.saveStateAsFile(filename);
      } else {

        let noty = new Noty({
          text: 'world prototype is modified.. could not clone world state',
          timeout: 2000,
          theme: 'mint',
          layout: 'bottomRight',
          type: 'error'
        });
        noty.show();
      }
    } else {
      await this.cloneWorldPrototype(protoUserRoot, userName, protoUserRoot);
      this.saveStateAsFile(filename);
    }
  }

  //TODO: refactor and config save
  saveStateAsFile(filename, otherProto) // invoke with the view as "this"
  {
    console.log("Saving: " + filename);

    //var clients = this.nodes["http://vwf.example.com/clients.vwf"];

    // Save State Information
    var state = vwf.getState();
    state.nodes[0].children = {};

    var timestamp = state["queue"].time;
    timestamp = Math.round(timestamp * 1000);

    var objectIsTypedArray = function (candidate) {
      var typedArrayTypes = [
        Int8Array,
        Uint8Array,
        // Uint8ClampedArray,
        Int16Array,
        Uint16Array,
        Int32Array,
        Uint32Array,
        Float32Array,
        Float64Array
      ];

      var isTypedArray = false;

      if (typeof candidate == "object" && candidate != null) {
        typedArrayTypes.forEach(function (typedArrayType) {
          isTypedArray = isTypedArray || candidate instanceof typedArrayType;
        });
      }

      return isTypedArray;
    };

    var transitTransformation = function (object) {
      return objectIsTypedArray(object) ?
        Array.prototype.slice.call(object) : object;
    };


    let jsonValuePure = require("vwf/utility").transform(
      state, transitTransformation
    );

    //remove all Ohm generated grammarsfrom state

    let jsonValue = _app.helpers.removeGrammarObj(jsonValuePure);
    var jsonState = JSON.stringify(jsonValue);

    let rootPath = this.helpers.getRoot(true);

    var inst = rootPath.inst;
    if (filename == '') filename = inst;
    //if (root.indexOf('.vwf') != -1) root = root.substring(0, root.lastIndexOf('/'));
    var root = rootPath.root;

    var json = jsonState;

    if (otherProto) {

      console.log('need to modify state...');
      json = this.helpers.replaceSubStringALL(jsonState, '/' + root + '/', '/' + otherProto + '/');//jsonState.replace(('/' + root + '/'), ('/' + otherProto +'/') );
      root = otherProto;
      console.log(json);
    }

    //var documents = _LCSUSER.get('documents');

    var saveRevision = new Date().valueOf();

    var stateForStore = {
      "root": root,
      "filename": filename,
      "inst": inst,
      "timestamp": timestamp,
      "extension": ".vwf.json",
      "jsonState": json,
      "publish": true
    };

    //let objName = loadInfo[ 'save_name' ] +'/'+ "savestate_" + loadInfo[ 'save_revision' ];
    // "savestate_" + loadInfo[ 'save_revision' ] + '/' + loadInfo[ 'save_name' ] + '_vwf_json'

    var docName = 'savestate_/' + root + '/' + filename + '_vwf_json';
    _LCSUSER.get('documents').get(root).get(docName).put(stateForStore, res => {

      if (res) {

        let noty = new Noty({
          text: 'Saved to ' + docName,
          timeout: 2000,
          theme: 'mint',
          layout: 'bottomRight',
          type: 'success'
        });
        noty.show();
      }
    });

    _LCSUSER.get('worlds').get(root).get('info_json').once(res => {

      if (res) {

        let modified = new Date().valueOf();
        let newOwner = _LCSUSER.is.pub;
        let userName = _LCSUSER.is.alias;

        let obj = {
          'parent': userName + '/' + root,
          'owner': newOwner,
          'file': res.file,
          'modified': modified

        }

        let docInfoName = 'savestate_/' + root + '/' + filename + '_info_vwf_json';

        _LCSUSER.get('documents').get(root).get(docInfoName).not(res => {
          _LCSUSER.get('documents').get(root).get(docInfoName).put(obj);
        });
      }
    });

    var docNameRev = 'savestate_' + saveRevision.toString() + '/' + root + '/' + filename + '_vwf_json';
    _LCSUSER.get('documents').get(root).get(docName).get('revs').get(docNameRev).put(stateForStore)
      .path("revision").put(saveRevision);


    // Save Config Information
    var config = { "info": {}, "model": {}, "view": {} };

    // Save browser title
    config["info"]["title"] = document.title//$('title').html();

    // Save model drivers
    Object.keys(vwf_view.kernel.kernel.models).forEach(function (modelDriver) {
      if (modelDriver.indexOf('vwf/model/') != -1) config["model"][modelDriver] = "";
    });

    // If neither glge or threejs model drivers are defined, specify nodriver
    if (config["model"]["vwf/model/glge"] === undefined && config["model"]["vwf/model/threejs"] === undefined) config["model"]["nodriver"] = "";

    // Save view drivers and associated parameters, if any
    Object.keys(vwf_view.kernel.kernel.views).forEach(function (viewDriver) {
      if (viewDriver.indexOf('vwf/view/') != -1) {
        if (vwf_view.kernel.kernel.views[viewDriver].parameters) {
          config["view"][viewDriver] = vwf_view.kernel.kernel.views[viewDriver].parameters;
        }
        else config["view"][viewDriver] = "";
      }
    });

    //var jsonConfig = $.encoder.encodeForURL(JSON.stringify(config));
    var jsonConfig = JSON.stringify(config);



    let configStateForStore = {
      "root": root,
      "filename": filename,
      "inst": inst,
      "timestamp": timestamp,
      "extension": "config.vwf.json",
      "jsonState": jsonConfig
    };

    //let objName = loadInfo[ 'save_name' ] +'/'+ "savestate_" + loadInfo[ 'save_revision' ];
    // "savestate_" + loadInfo[ 'save_revision' ] + '/' + loadInfo[ 'save_name' ] + '_vwf_json'

    // let configName = 'savestate_/' + root + '/' + filename + '_config_vwf_json';
    // let documentSaveConfigState = _LCSUSER.get(configName).put(configStateForStore);
    // //documents.path(root).set(documentSaveConfigState);

    // let configNameRev = 'savestate_' + saveRevision.toString() + '/' + root + '/' + filename + '_config_vwf_json';
    // _LCSUSER.get(configNameRev).put(configStateForStore);
    // _LCSUSER.get(configNameRev).path("revision").put(saveRevision);

    //documentSaveConfigState.path('revs').set(documentSaveStateRevision);


    // Save config file to server
    // var xhrConfig = new XMLHttpRequest();
    // xhrConfig.open("POST", "/" + root + "/save/" + filename, true);
    // xhrConfig.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    // xhrConfig.send("root=" + root + "/" + filename + "&filename=saveState&inst=" + inst + "&timestamp=" + timestamp + "&extension=.vwf.config.json" + "&jsonState=" + jsonConfig);


  }

  // LoadSavedState 

  async loadSavedState(filename, applicationpath, revision) {
    console.log("Loading: " + filename);

    let userName = await _LCS_WORLD_USER.get('alias').once().then();

    if (revision) {
      window.location.pathname = '/' + userName + applicationpath + '/load/' + filename + '/' + revision + '/';
    }
    else { // applicationpath + "/" + inst + '/load/' + filename + '/';
      window.location.pathname = '/' + userName + applicationpath + '/load/' + filename + '/';
    }

  }

  hideUIControl() {

    var el = document.getElementById("ui-controls");
    if (el) {
      el.classList.remove("visible");
      el.classList.add("not-visible");
    }
  }

  showUIControl() {

    var el = document.getElementById("ui-controls");
    if (el) {
      el.classList.remove("not-visible");
      el.classList.add("visible");
    }
  }


  hideProgressBar() {

    var progressbar = document.getElementById("load-progressbar");
    if (progressbar) {
      progressbar.classList.remove("visible");
      progressbar.classList.add("not-visible");
      progressbar.classList.add("mdc-linear-progress--closed");

    }

  }

  showProgressBar() {

    let progressbar = document.getElementById("load-progressbar");
    if (progressbar) {
      progressbar.classList.add("visible");
      progressbar.classList.add("mdc-linear-progress--indeterminate");
    }
  }

}

export { App }