<commit_before>/*
File: main-window-func.js
Description: HyperReload server-related UI functions.
Author: <NAME>

License:

Copyright (c) 2013-2014 <NAME>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*** Imported modules ***/
const SHELL = require('electron').shell;
const ipcRenderer = require('electron').ipcRenderer
var MAIN = require('electron').remote.getGlobal('main');

var PATH = require('path')
var OS = require('os')
var PATH = require('path')
var FS = require('fs')
var FSEXTRA = require('fs-extra')
var FILEUTIL = require('../server/file-util.js')
var EVENTS = require('../server/system-events.js')
// Awful, but I am not sure how to get hold of the BrowserWindow.id otherwise
EVENTS.myID = MAIN.workbenchWindow.id
var APP_SETTINGS = require('../server/app-settings.js')
var SETTINGS = require('../settings/settings.js')
var LOGGER = require('../server/log.js')
var SERVER = require('../server/file-server.js')
var MONITOR = require('../server/file-monitor.js')
var UTIL = require('../util/util.js')
var TEMP = require('temp').track()
var BABEL = require('babel-core')
var GLOB = require('glob')
var CHEERIO = require('cheerio')
var SEMVER_REGEX = require('semver-regex')
var URL = require('url')
const CHILD_PROCESS = require('child_process')

// This is not a strict domain regex, but enough to make Cordova happy (and Cordova is not fully sane either)
const reverseDomainRE = /^(([a-zA-Z]+)\.)*([a-zA-Z]+)$/

// Counter for "popup" menus on an app entry in the My Apps list.
var mEntryMenuIdCounter = 0

/**
 * UI functions.
 */
exports.defineUIFunctions = function(hyper)
{
	var mConnectKeyTimer
	// The merged final array of metadata on Examples, Libraries and Projects
	hyper.UI.mNewsList = []
	hyper.UI.mExampleList = []
	hyper.UI.mLibraryList = []
	hyper.UI.mBuildConfigList = []
	hyper.UI.mBuildList = []
	hyper.UI.mPluginList = []
	
	// Ugly flag introduced because we can get a flurry of EVENTS.LOGIN
	mUpdatingLists = false

	var mProjectList = []
	var mWorkbenchPath = MAIN.getRootDir()

	hyper.UI.setupUI = function()
	{
		ensureCloudApiTokenExists()
		styleUI()
		setUIActions()
		setWindowActions()
		setUpFileDrop()
		restoreSavedUIState()
		MAIN.showWorkbenchWindow()
		initAppLists()
	}

  function updateLists(silent) {
    if (!mUpdatingLists) {
      mUpdatingLists = true
      setTimeout(function() {mUpdatingLists = false}, 5000)
			hyper.UI.updateNewsList(silent)
      hyper.UI.updateExampleList(silent)
      hyper.UI.updateLibraryList(silent)
			hyper.UI.updateBuildConfigList(silent)
			hyper.UI.updatePluginList(silent)
	  	//UTIL.updateTranslations(SETTINGS.getTranslationsURL())
    } else {
      console.log("Already updating lists, ignoring")
    }
  }
  
	function initAppLists()
	{
		readProjectList()
		hyper.UI.displayProjectList()

    // If we have verified basic internet access - we load the lists but not silently.
    UTIL.checkInternet().then(hasInternet => {
      if (hasInternet) {
        updateLists(false)
	    }
    })

		// Register a timer so that we update the lists every 30 min, but silently.
	  setInterval(function() {
      updateLists(true)
	  }, 30 * 60 * 1000);
	  
	  // When user logs in we update silently
	  EVENTS.subscribe(EVENTS.LOGIN, function() {updateLists(true)})
	  
		hyper.UI.setServerMessageFun()
	}

	function ensureCloudApiTokenExists()
	{
		var token = SETTINGS.getEvoCloudToken()
		var dialog = hyper.UI.$('#dialog-cloud-token-alert')[0]
		if(!token)
		{
			console.dir(dialog)
			//dialog.showModal()
		}
		else
		{
			console.log('existing cloud api token found: '+token)
			hyper.UI.showToken(token)
		}
		console.log('------------------ setting up open token dialog listener...')
		EVENTS.subscribe(EVENTS.OPENTOKENDIALOG, function(message)
		{
			console.log('------------------ token dialog event. message = '+message)
			if(message)
			{
				console.log('open cloud token dialog')
				MAIN.openDialog('Cloud Token Message', message, 'info')
			}

			/*
			if(message)
			{
				hyper.UI.$('#tokentext')[0].innerHTML = message
			}
			hyper.UI.$('#connect-spinner').removeClass('icon-spin-animate')
			dialog.showModal()
			*/
			hyper.UI.hideToken()
		})
	}

	hyper.UI.showToken = function(token)
	{
		var panelHaveToken = hyper.UI.$('#panel-have-token')[0]
		var panelHaveNoToken = hyper.UI.$('#panel-have-no-token')[0]
		var tokenInputField = hyper.UI.$('.token-input-field')[0]
		panelHaveToken.style.display = 'block'
		panelHaveNoToken.style.display = 'none'
		tokenInputField.value = ''
		hyper.UI.$('.token-key').html(token)
	}

	hyper.UI.hideToken = function()
	{
		var panelHaveToken = hyper.UI.$('#panel-have-token')[0]
		var panelHaveNoToken = hyper.UI.$('#panel-have-no-token')[0]
		panelHaveToken.style.display = 'none'
		panelHaveNoToken.style.display = 'block'
		hyper.UI.$('.token-key').html('')
	}

	// Helper function that returns the application name
	// specified in package.json.
	function getApplicationName()
	{
		var data = FILEUTIL.readFileSync('package.json')
		var applicationName = JSON.parse(data).name
		return applicationName
	}

	function styleUI()
	{
		// Put some content into connect key field to make field visible.
		// Skip for now. hyper.UI.displayConnectKey('Click "Get Key"')

		// Apply jQuery UI button style.
		//hyper.UI.$('button').button()

		// Set layout properties.
		/*
		hyper.UI.$('body').layout(
		{
			west: { size: 400 },
			center: { maskContents: true },
			fxName: 'none'
		})
		*/
	}

	function setUIActions()
	{
		// Enable reorder of project list by drag and drop.
		hyper.UI.$(function()
		{
			hyper.UI.$('#screen-projects').sortable(
			{
				stop: function()
				{
					updateProjectList()
				}
			})
			hyper.UI.$('#screen-projects').disableSelection()
		})

		// Display of file monitor counter.
		setInterval(function() {
			hyper.UI.displayNumberOfMonitoredFiles() },
			1500)
	}

	function setWindowActions()
	{
		// Listen to main window's close event
		var win = MAIN.workbenchWindow
    win.on('close', function() {
      saveUIState()
			hyper.UI.stopEvobox(function() {
	      this.close(true)
			})
    })
	}

	function saveUIState()
	{
		var geometry = MAIN.workbenchWindow.getBounds()
		// Do not save if window is minimized on Windows.
		// On Windows an icon has x,y coords -32000 when
		// window is minimized. On Linux and OS X the window
		// coordinates and size are intact when minimized.
		if (geometry.x < -1000)
		{
			return;
		}
		SETTINGS.setProjectWindowGeometry(geometry)
	}

	function restoreSavedUIState()
	{
		var geometry = SETTINGS.getProjectWindowGeometry()
		if (geometry)
		{
			// Make sure top-left corner is visible.
			var offsetY = 0
			if ('darwin' == OS.platform())
			{
				offsetY = 22
			}
			geometry.x = Math.max(geometry.x, 1)
			geometry.y = Math.max(geometry.y, 1 + offsetY)
			geometry.x = Math.min(geometry.x, hyper.UI.DOM.screen.width - 100)
			geometry.y = Math.min(geometry.y, hyper.UI.DOM.screen.height - 200)

			// Set window size.
			MAIN.workbenchWindow.setBounds(geometry)
		}
		// Restore remember me state for logout action
		var checked = SETTINGS.getRememberMe()
		hyper.UI.$('#remember-checkbox').attr('checked', checked)
	}

	function setUpFileDrop()
	{
		var originalDropTaget = null

		// Block change page on drop.
		window.ondragover = function(e) { e.preventDefault(); return false }
		window.ondragend = function(e) { e.preventDefault(); return false }
		window.ondrop = function(e) { e.preventDefault(); return false }

		// Set up drop handling using a drop target overlay area.
		var dropTarget = hyper.UI.$('#panel-page')
		var enterTarget;
		dropTarget.on('dragenter', function(e)
		{
			e.stopPropagation()
			e.preventDefault()
			hyper.UI.showTab('projects')
			hyper.UI.$('#drag-overlay').show()
			enterTarget = e.target
		})
		dropTarget.on('dragleave', function(e)
		{
			if (enterTarget == e.target) {
				e.stopPropagation()
				e.preventDefault()
				hyper.UI.$('#drag-overlay').hide()
			}
		})
		dropTarget.on('drop', function(e)
		{
			e.stopPropagation()
			e.preventDefault()
			hyper.UI.$('#drag-overlay').hide()
			handleFileDrop(e.originalEvent.dataTransfer.files)
		})
	}

	function handleFileDrop(files)
	{
/*
		// Debug print.
		console.log('@@@ handleFileDrop');
		for (var i = 0; i < files.length; ++i)
		{
			console.log(files[i].path);
		}
*/

		for (var i = 0; i < files.length; ++i)
		{
			var path = files[i].path
			if (pathIsValidAppPath(path))
			{
				hyper.UI.addProject(path)
			}
			else
			{
				window.alert('Not a valid evothings.json file or HTML file (extension .html or .htm)')
				break;
			}
		}

		hyper.UI.displayProjectList()
	}

    function pathIsValidAppPath(path)
    {
      // Is it an existing HTML file?
		  if (FILEUTIL.fileIsHTML(path) && FILEUTIL.pathExists(path))
		  {
			  return true
		  }

		  // Directory containing evothings.json file.
		  var dirPath = null

		  // If path points to evothings.json file, get the directory
		  if (FILEUTIL.fileIsEvothingsSettings(path))
		  {
			  dirPath = PATH.dirname(path)
		  }
		  else if (FILEUTIL.fileIsDirectory(path))
		  {
			  // Dropped file is a directory.
			  dirPath = path
		  }

		  // Must have directory to continue.
		  if (!dirPath)
		  {
			  return false
		  }

      // Does the directory have an evothings.json file pointing to existing index file?
      var indexPath = APP_SETTINGS.getIndexFileFullPath(dirPath)
		  if (FILEUTIL.pathExists(indexPath))
		  {
			  return true
		  }

		  return false
    }

	/**
	 * Possible options include:
	 *   options.path
	 *   options.title
	 *   options.version
	 *   options.versions
	 *   options.description
	 *   options.tags { label, type }
	 *   options.libraries { name, version }
	 *   options.imagePath
	 *   options.docURL
	 *   options.active
	 *	 options.screen
	 *	 options.docButton
	 *	 options.copyButton
	 *	 options.openButton
	 *	 options.deleteButton
	 */
	function createProjectEntry(isLocal, isLibrary, options)
	{
		options = options || {}
    // Set base to where we loaded the metadata from
    var base = options.url || 'file://' 

		// Create div tag for app items.
		var html = '<div class="project-entry ui-state-default ui-corner-all"'

		// Set background color of active app entry.
		if (options.active)
		{
			html += ' style="background-color:#EAFFEA;"'
		}

		// Close opening div tag.
		html += '>'

		// Full URL to application, local or online
		var dirOrFile = options.dirOrFile || options.path  // Ugh..
		var appURL = URL.resolve(base, dirOrFile)
    var imagePath = options.imagePath
    var docURL = options.docURL
    var appTags = options.tags || []
    var appLibraries = options.libraries || []
		var appVersion = options.version || null
		var versions = options.versions || null
		var shortName = options.name
    var appTitle = options.title
    var appDescription = options.description

		// Escape any backslashes in the path (needed on Windows).
		var escapedPath = dirOrFile.replace(/[\\]/g,'\\\\')
    
    if (isLocal) {
		  var imagePath = imagePath || APP_SETTINGS.getAppImage(options.path)
		  var docURL = docURL || APP_SETTINGS.getDocURL(options.path)
		  var appTags = APP_SETTINGS.getTags(options.path) || []
		  var appLibraries = APP_SETTINGS.getLibraries(options.path) || []
		  var appVersion = APP_SETTINGS.getVersion(options.path) || null
		  var shortName = APP_SETTINGS.getName(options.path)
		  var appDescription = APP_SETTINGS.getDescription(options.path) || '&lt;no description entered&gt;'
    }
    
    // Fallback on missing doc-url is locally inside the app/library
    if (!docURL) {
      docURL = URL.resolve(appURL, 'doc/index.html')
    }
    
		// Show app icon.
		if (imagePath) {
			var fullImageURL = URL.resolve(appURL + '/', imagePath)
			html += '<div class="app-icon" style="background-image: url(\'' +
				fullImageURL + '\');"></div>'
		} else {
			// Show a default icon if no image file is provided.
			html += '<div class="app-icon" style="background-image: url(\'images/app-icon.png\');"></div>'
		}

		// Get app name.
		var appHasValidHTMLFile = options.runButton
    if (isLocal) {
		  // Get title of app, either given in options above or extracted from project.
		  // Uses title tag as first choice.
      if (!appTitle) {
        // Returns null if HTML file not found.
		    appTitle = hyper.UI.getTitleFromFile(options.dirOrFile)
		    appHasValidHTMLFile = !!appTitle
		    if (!appHasValidHTMLFile) {
			    // If app name was not found, index.html does not exist.
			    appTitle = 'Warning: HTML file does not exist, no title found'
		    }
      }
    }
	
		// Run button for examples and apps.
		// Add Run button only if app has an HTML file.
		// We use different run functions depending on if it isLocal.
		if (!isLibrary && appHasValidHTMLFile)
		{
			html += '<button type="button" class="button-run btn et-btn-green entry-button" '
			if (isLocal)
				html += `onclick="window.hyper.UI.runApp('${escapedPath}')">`
			else 
				html += `onclick="window.hyper.UI.runExampleApp('${escapedPath}')">`
			html +=	'Run</button>'
		}

		// Edit button for apps.
		// Added if it is a locally stored app that can be edited.
		if (isLocal && !isLibrary)
		{
			html += 
				'<button type="button" '
				+ 'class="button-edit btn et-btn-blue entry-button" '
				+ `onclick="window.hyper.UI.editApp('${escapedPath}')">`
				+	'Edit</button>'
		}

		// Doc button for libs.
		if (isLibrary && docURL)
		{
			html +=
				'<button type="button" '
				+ 'class="button-doc-lib btn et-btn-yellow-dark entry-button" '
				+ `onclick="window.hyper.UI.openDocURL('${docURL}')">`
				+	'Doc</button>'
		}

		// Doc button for examples.
		if (!isLocal && !isLibrary && docURL)
		{
			html +=
				'<button type="button" '
				+ 'class="button-doc-example btn et-btn-yellow-dark entry-button" '
				+ `onclick="window.hyper.UI.openDocURL('${docURL}')">`
				+	'Doc</button>'
		}

		// Copy button for examples.
		if (options.copyButton)
		{
			html +=
				'<button type="button" '
				+	'class="button-copy-example btn et-btn-indigo entry-button" '
				+	`onclick="window.hyper.UI.openCopyAppDialog('${escapedPath}', '${shortName}')">`
				+	'Copy'
				+ '</button>'
		}

		// Menu button for apps that shows more buttons.
		if (isLocal && !isLibrary)
		{
			// Increment button menu counter.
			++mEntryMenuIdCounter

			// Id for dynamic button menu.
			var entryMenuId = `entry-menu-id-${mEntryMenuIdCounter}`

			html += 
				'<button type="button" '
				+ 'class="button-more btn et-btn-indigo entry-button" '
				+ `onclick="window.hyper.UI.toggleEntryMenu('${entryMenuId}')">`
				+	'More</button>'

			// Build popup menu for app entry, shown on menu button.
			++mEntryMenuIdCounter
			html += `<div class="entry-menu" id="${entryMenuId}">`

			// Open files button.
			html +=
				'<button type="button" '
				+	'class="btn btn-default entry-menu-button" '
				+	`onclick="window.hyper.UI.openFolder('${escapedPath}')">`
				+	'Files</button>'

			// Config button.
			html +=
				'<button type="button" '
				+	'class="btn btn-default entry-menu-button" '
				+	`onclick="window.hyper.UI.openConfigAppDialog('${escapedPath}')">`
				+	'Config</button>'
			
			// Copy button.
			html +=
				'<button type="button" '
				+	'class="btn btn-default entry-menu-button" '
				+	`onclick="window.hyper.UI.openCopyAppDialog('${escapedPath}', '${shortName}')">`
				+	'Copy</button>'

			// Build button.
		  html +=
				'<button type="button" '
				+	'class="btn btn-default entry-menu-button" '
				+	`onclick="window.hyper.UI.openBuildAppDialog('${escapedPath}')">`
				+	'Build</button>'

			// Doc button.
			if (docURL) {
				html +=
					'<button type="button" '
					+	'class="btn btn-default entry-menu-button" '
					+	`onclick="window.hyper.UI.openDocURL('${docURL}')">`
					+	'Doc</button>'
			}
			// End of entry menu.
			html += '</div>'
		}

		// Delete icon.
		if (options.deleteButton)
		{
			html +=
				'<button '
				+	'type="button" '
				+	'class="close button-delete" '
				+	'onclick="window.hyper.UI.deleteEntry(this)">'
				+	'&times;'
				+ '</button>'
		}

    // Tags HTML
    var tagsHTML = ''
    appTags.forEach(tag => {
      tagsHTML += ` <span class="label label-${tag.type}">${tag.label}</span>`
    })
    
    // Meta data
    var metaHTML = ''
    if (shortName) {
      metaHTML += '<strong>Name:</strong>&nbsp;' + shortName
    }
    if (appVersion) {
      metaHTML += ' <strong>Version:</strong>&nbsp;' + appVersion
    }
		if (versions) {
			metaHTML += ' <strong>Versions:</strong>&nbsp;' + versions
		}
    if (appLibraries.length > 0) {
      metaHTML += ' <strong>Libraries:</strong>'
      var first = true
      appLibraries.forEach(lib => {
        if (first) {
          metaHTML += '&nbsp;'
          first = false
        } else {
          metaHTML += ',&nbsp;'
        }
        metaHTML += lib.name + ' (' + lib.version + ')'
      })
    }
    if (isLocal && options.path) {
      metaHTML += ' <strong>Path:</strong>&nbsp;' + options.path
    }
    
		// Different CSS classes for entry content
		var entryContentClass = 'entry-content'
		if (isLibrary) {
		  entryContentClass += '-libraries'
		} else if (!isLocal) {
		  entryContentClass += '-examples'
		}
		
		html += `<div class="${entryContentClass}"><h4>${appTitle}</h4>`
		html += `<p>${appDescription}</p>`
		html += '<p>'
		if (metaHTML.length > 0) {
		  html += metaHTML.trim()
		}
    html += `<span style="float:right">${tagsHTML}</span></p>`
    html += '<div class="project-list-entry-path" style="display:none;">' + options.path + '</div>'
		html += '</div>'
		// Create element.
		var element = hyper.UI.$(html)

		// Insert element first in list.
		options.screen && hyper.UI.$(options.screen).append(element)
	}

	// Get last part of path. Purpose is to make path fit
	// inside a project list item.
	// TODO: We don't use this anymore, but keeping it for later
	function getShortPathFromPath(path)
	{
		var limit = 58
		if (path.length < limit)
		{
			return path
		}
		else
		{
			var shortPath = path.substring(path.length - limit, path.length)
			var index1 = shortPath.indexOf('/') // OS X, Linux
			var index2 = shortPath.indexOf('\\') // Windows
			if (index1 > -1)
			{
				return shortPath.substring(index1 + 1)
			}
			else if (index2 > -1)
			{
				return shortPath.substring(index2 + 1)
			}
			else
			{
				// Fallback
				return '...' + shortPath.substring(3, shortPath.length)
			}
		}
	}

	// Project list has been reordered/changed, save new list.
	// Reads data from the DOM tree.
	function updateProjectList()
	{
		var projects = []
		var elements = hyper.UI.$('#screen-projects .project-list-entry-path')
		elements.each(function(index, element)
		{
			var path = hyper.UI.$(element).text()
			if (path != '')
			{
				projects.push(path)
			}
		})
		hyper.UI.setProjectList(projects)
	}

	hyper.UI.getTitleFromFile = function(path)
	{
			return APP_SETTINGS.getTitle(path)
	}

	function parseProjectList(json)
	{
		// Replace slashes with backslashes on Windows.
		if (process.platform === 'win32')
		{
			json = json.replace(/[\/]/g,'\\\\')
		}

		return JSON.parse(json)
	}

	function readProjectList()
	{
		var list = SETTINGS.getProjectList()
		if (list)
		{
			mProjectList = list
		}
	}

	function saveProjectList()
	{
	    SETTINGS.setProjectList(mProjectList)
	}

	hyper.UI.addProject = function(path) {
		var dir
		if (FILEUTIL.fileIsHTML(path) || FILEUTIL.fileIsEvothingsSettings(path)) {
			var dir = PATH.dirname(path)
		} else if (FILEUTIL.fileIsDirectory(path)) {
			dir = path
		}
		// If duplicate, then warn and offer to fix - otherwise bail out
		if (hyper.UI.duplicateUUID(dir)) {
			var doit = "Yes, please fix it"
			var answer = MAIN.openWorkbenchDialog('Duplicate UUID',
				'Generate a new UUID?',
				`The evothings.json file for this new project has a duplicate UUID. This can happen if you copied the whole app directory using tools outside Evothings. The easy fix is to just generate a new unique UUID.\n\nShould we generate a new UUID?`, 'question', [doit, "Cancel"])
			if (answer == doit) {
				APP_SETTINGS.generateNewAppUUID(dir)
			} else {
				return // Do NOT add project
			}
		}
		// Add the path to the project list.
		if (FILEUTIL.fileIsHTML(path)) {
			// Add the path, including HTML file, to the project list.
			mProjectList.unshift(path)
		} else {
			mProjectList.unshift(dir)
		}
		saveProjectList()
	}

	hyper.UI.duplicateUUID = function(path) {
		var newUUID = APP_SETTINGS.getAppID(path)
		for (let p of mProjectList) {
			// p can actually be == path, since the Studio may have an old entry
			// pointing to this new path (if you copy stuff around too much!)
			if (p != path && APP_SETTINGS.getAppID(p) == newUUID) {
				return true
			}
		}
		return false
	}

	hyper.UI.setProjectList = function(list) {
		mProjectList = list
		saveProjectList()
	}

	hyper.UI.getProjectList = function() {
		return mProjectList
	}

	/**
	 * Get path to the Workbench application directory.
	 */
	hyper.UI.getWorkbenchPath = function(path)
	{
		return mWorkbenchPath
	}

	/**
	 * Edit app in VS Code, either a file path or dir path.
	 */
	hyper.UI.editApp = function(path) {
		var error = null<commit_msg>Added extra cross check for duplicate UUIDs.<commit_after>/*
File: main-window-func.js
Description: HyperReload server-related UI functions.
Author: <NAME>

License:

Copyright (c) 2013-2014 <NAME>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

/*** Imported modules ***/
const SHELL = require('electron').shell;
const ipcRenderer = require('electron').ipcRenderer
var MAIN = require('electron').remote.getGlobal('main');

var PATH = require('path')
var OS = require('os')
var PATH = require('path')
var FS = require('fs')
var FSEXTRA = require('fs-extra')
var FILEUTIL = require('../server/file-util.js')
var EVENTS = require('../server/system-events.js')
// Awful, but I am not sure how to get hold of the BrowserWindow.id otherwise
EVENTS.myID = MAIN.workbenchWindow.id
var APP_SETTINGS = require('../server/app-settings.js')
var SETTINGS = require('../settings/settings.js')
var LOGGER = require('../server/log.js')
var SERVER = require('../server/file-server.js')
var MONITOR = require('../server/file-monitor.js')
var UTIL = require('../util/util.js')
var TEMP = require('temp').track()
var BABEL = require('babel-core')
var GLOB = require('glob')
var CHEERIO = require('cheerio')
var SEMVER_REGEX = require('semver-regex')
var URL = require('url')
const CHILD_PROCESS = require('child_process')

// This is not a strict domain regex, but enough to make Cordova happy (and Cordova is not fully sane either)
const reverseDomainRE = /^(([a-zA-Z]+)\.)*([a-zA-Z]+)$/

// Counter for "popup" menus on an app entry in the My Apps list.
var mEntryMenuIdCounter = 0

/**
 * UI functions.
 */
exports.defineUIFunctions = function(hyper)
{
	var mConnectKeyTimer
	// The merged final array of metadata on Examples, Libraries and Projects
	hyper.UI.mNewsList = []
	hyper.UI.mExampleList = []
	hyper.UI.mLibraryList = []
	hyper.UI.mBuildConfigList = []
	hyper.UI.mBuildList = []
	hyper.UI.mPluginList = []
	
	// Ugly flag introduced because we can get a flurry of EVENTS.LOGIN
	mUpdatingLists = false

	var mProjectList = []
	var mWorkbenchPath = MAIN.getRootDir()

	hyper.UI.setupUI = function()
	{
		ensureCloudApiTokenExists()
		styleUI()
		setUIActions()
		setWindowActions()
		setUpFileDrop()
		restoreSavedUIState()
		MAIN.showWorkbenchWindow()
		initAppLists()
	}

  function updateLists(silent) {
    if (!mUpdatingLists) {
      mUpdatingLists = true
      setTimeout(function() {mUpdatingLists = false}, 5000)
			hyper.UI.updateNewsList(silent)
      hyper.UI.updateExampleList(silent)
      hyper.UI.updateLibraryList(silent)
			hyper.UI.updateBuildConfigList(silent)
			hyper.UI.updatePluginList(silent)
	  	//UTIL.updateTranslations(SETTINGS.getTranslationsURL())
    } else {
      console.log("Already updating lists, ignoring")
    }
  }
  
	function initAppLists()
	{
		readProjectList()
		hyper.UI.displayProjectList()

    // If we have verified basic internet access - we load the lists but not silently.
    UTIL.checkInternet().then(hasInternet => {
      if (hasInternet) {
        updateLists(false)
	    }
    })

		// Register a timer so that we update the lists every 30 min, but silently.
	  setInterval(function() {
      updateLists(true)
	  }, 30 * 60 * 1000);
	  
	  // When user logs in we update silently
	  EVENTS.subscribe(EVENTS.LOGIN, function() {updateLists(true)})
	  
		hyper.UI.setServerMessageFun()
	}

	function ensureCloudApiTokenExists()
	{
		var token = SETTINGS.getEvoCloudToken()
		var dialog = hyper.UI.$('#dialog-cloud-token-alert')[0]
		if(!token)
		{
			console.dir(dialog)
			//dialog.showModal()
		}
		else
		{
			console.log('existing cloud api token found: '+token)
			hyper.UI.showToken(token)
		}
		console.log('------------------ setting up open token dialog listener...')
		EVENTS.subscribe(EVENTS.OPENTOKENDIALOG, function(message)
		{
			console.log('------------------ token dialog event. message = '+message)
			if(message)
			{
				console.log('open cloud token dialog')
				MAIN.openDialog('Cloud Token Message', message, 'info')
			}

			/*
			if(message)
			{
				hyper.UI.$('#tokentext')[0].innerHTML = message
			}
			hyper.UI.$('#connect-spinner').removeClass('icon-spin-animate')
			dialog.showModal()
			*/
			hyper.UI.hideToken()
		})
	}

	hyper.UI.showToken = function(token)
	{
		var panelHaveToken = hyper.UI.$('#panel-have-token')[0]
		var panelHaveNoToken = hyper.UI.$('#panel-have-no-token')[0]
		var tokenInputField = hyper.UI.$('.token-input-field')[0]
		panelHaveToken.style.display = 'block'
		panelHaveNoToken.style.display = 'none'
		tokenInputField.value = ''
		hyper.UI.$('.token-key').html(token)
	}

	hyper.UI.hideToken = function()
	{
		var panelHaveToken = hyper.UI.$('#panel-have-token')[0]
		var panelHaveNoToken = hyper.UI.$('#panel-have-no-token')[0]
		panelHaveToken.style.display = 'none'
		panelHaveNoToken.style.display = 'block'
		hyper.UI.$('.token-key').html('')
	}

	// Helper function that returns the application name
	// specified in package.json.
	function getApplicationName()
	{
		var data = FILEUTIL.readFileSync('package.json')
		var applicationName = JSON.parse(data).name
		return applicationName
	}

	function styleUI()
	{
		// Put some content into connect key field to make field visible.
		// Skip for now. hyper.UI.displayConnectKey('Click "Get Key"')

		// Apply jQuery UI button style.
		//hyper.UI.$('button').button()

		// Set layout properties.
		/*
		hyper.UI.$('body').layout(
		{
			west: { size: 400 },
			center: { maskContents: true },
			fxName: 'none'
		})
		*/
	}

	function setUIActions()
	{
		// Enable reorder of project list by drag and drop.
		hyper.UI.$(function()
		{
			hyper.UI.$('#screen-projects').sortable(
			{
				stop: function()
				{
					updateProjectList()
				}
			})
			hyper.UI.$('#screen-projects').disableSelection()
		})

		// Display of file monitor counter.
		setInterval(function() {
			hyper.UI.displayNumberOfMonitoredFiles() },
			1500)
	}

	function setWindowActions()
	{
		// Listen to main window's close event
		var win = MAIN.workbenchWindow
    win.on('close', function() {
      saveUIState()
			hyper.UI.stopEvobox(function() {
	      this.close(true)
			})
    })
	}

	function saveUIState()
	{
		var geometry = MAIN.workbenchWindow.getBounds()
		// Do not save if window is minimized on Windows.
		// On Windows an icon has x,y coords -32000 when
		// window is minimized. On Linux and OS X the window
		// coordinates and size are intact when minimized.
		if (geometry.x < -1000)
		{
			return;
		}
		SETTINGS.setProjectWindowGeometry(geometry)
	}

	function restoreSavedUIState()
	{
		var geometry = SETTINGS.getProjectWindowGeometry()
		if (geometry)
		{
			// Make sure top-left corner is visible.
			var offsetY = 0
			if ('darwin' == OS.platform())
			{
				offsetY = 22
			}
			geometry.x = Math.max(geometry.x, 1)
			geometry.y = Math.max(geometry.y, 1 + offsetY)
			geometry.x = Math.min(geometry.x, hyper.UI.DOM.screen.width - 100)
			geometry.y = Math.min(geometry.y, hyper.UI.DOM.screen.height - 200)

			// Set window size.
			MAIN.workbenchWindow.setBounds(geometry)
		}
		// Restore remember me state for logout action
		var checked = SETTINGS.getRememberMe()
		hyper.UI.$('#remember-checkbox').attr('checked', checked)
	}

	function setUpFileDrop()
	{
		var originalDropTaget = null

		// Block change page on drop.
		window.ondragover = function(e) { e.preventDefault(); return false }
		window.ondragend = function(e) { e.preventDefault(); return false }
		window.ondrop = function(e) { e.preventDefault(); return false }

		// Set up drop handling using a drop target overlay area.
		var dropTarget = hyper.UI.$('#panel-page')
		var enterTarget;
		dropTarget.on('dragenter', function(e)
		{
			e.stopPropagation()
			e.preventDefault()
			hyper.UI.showTab('projects')
			hyper.UI.$('#drag-overlay').show()
			enterTarget = e.target
		})
		dropTarget.on('dragleave', function(e)
		{
			if (enterTarget == e.target) {
				e.stopPropagation()
				e.preventDefault()
				hyper.UI.$('#drag-overlay').hide()
			}
		})
		dropTarget.on('drop', function(e)
		{
			e.stopPropagation()
			e.preventDefault()
			hyper.UI.$('#drag-overlay').hide()
			handleFileDrop(e.originalEvent.dataTransfer.files)
		})
	}

	function handleFileDrop(files)
	{
/*
		// Debug print.
		console.log('@@@ handleFileDrop');
		for (var i = 0; i < files.length; ++i)
		{
			console.log(files[i].path);
		}
*/

		for (var i = 0; i < files.length; ++i)
		{
			var path = files[i].path
			if (pathIsValidAppPath(path))
			{
				hyper.UI.addProject(path)
			}
			else
			{
				window.alert('Not a valid evothings.json file or HTML file (extension .html or .htm)')
				break;
			}
		}

		hyper.UI.displayProjectList()
	}

    function pathIsValidAppPath(path)
    {
      // Is it an existing HTML file?
		  if (FILEUTIL.fileIsHTML(path) && FILEUTIL.pathExists(path))
		  {
			  return true
		  }

		  // Directory containing evothings.json file.
		  var dirPath = null

		  // If path points to evothings.json file, get the directory
		  if (FILEUTIL.fileIsEvothingsSettings(path))
		  {
			  dirPath = PATH.dirname(path)
		  }
		  else if (FILEUTIL.fileIsDirectory(path))
		  {
			  // Dropped file is a directory.
			  dirPath = path
		  }

		  // Must have directory to continue.
		  if (!dirPath)
		  {
			  return false
		  }

      // Does the directory have an evothings.json file pointing to existing index file?
      var indexPath = APP_SETTINGS.getIndexFileFullPath(dirPath)
		  if (FILEUTIL.pathExists(indexPath))
		  {
			  return true
		  }

		  return false
    }

	/**
	 * Possible options include:
	 *   options.path
	 *   options.title
	 *   options.version
	 *   options.versions
	 *   options.description
	 *   options.tags { label, type }
	 *   options.libraries { name, version }
	 *   options.imagePath
	 *   options.docURL
	 *   options.active
	 *	 options.screen
	 *	 options.docButton
	 *	 options.copyButton
	 *	 options.openButton
	 *	 options.deleteButton
	 */
	function createProjectEntry(isLocal, isLibrary, options)
	{
		options = options || {}
    // Set base to where we loaded the metadata from
    var base = options.url || 'file://' 

		// Create div tag for app items.
		var html = '<div class="project-entry ui-state-default ui-corner-all"'

		// Set background color of active app entry.
		if (options.active)
		{
			html += ' style="background-color:#EAFFEA;"'
		}

		// Close opening div tag.
		html += '>'

		// Full URL to application, local or online
		var dirOrFile = options.dirOrFile || options.path  // Ugh..
		var appURL = URL.resolve(base, dirOrFile)
    var imagePath = options.imagePath
    var docURL = options.docURL
    var appTags = options.tags || []
    var appLibraries = options.libraries || []
		var appVersion = options.version || null
		var versions = options.versions || null
		var shortName = options.name
    var appTitle = options.title
    var appDescription = options.description

		// Escape any backslashes in the path (needed on Windows).
		var escapedPath = dirOrFile.replace(/[\\]/g,'\\\\')
    
    if (isLocal) {
		  var imagePath = imagePath || APP_SETTINGS.getAppImage(options.path)
		  var docURL = docURL || APP_SETTINGS.getDocURL(options.path)
		  var appTags = APP_SETTINGS.getTags(options.path) || []
		  var appLibraries = APP_SETTINGS.getLibraries(options.path) || []
		  var appVersion = APP_SETTINGS.getVersion(options.path) || null
		  var shortName = APP_SETTINGS.getName(options.path)
		  var appDescription = APP_SETTINGS.getDescription(options.path) || '&lt;no description entered&gt;'
    }
    
    // Fallback on missing doc-url is locally inside the app/library
    if (!docURL) {
      docURL = URL.resolve(appURL, 'doc/index.html')
    }
    
		// Show app icon.
		if (imagePath) {
			var fullImageURL = URL.resolve(appURL + '/', imagePath)
			html += '<div class="app-icon" style="background-image: url(\'' +
				fullImageURL + '\');"></div>'
		} else {
			// Show a default icon if no image file is provided.
			html += '<div class="app-icon" style="background-image: url(\'images/app-icon.png\');"></div>'
		}

		// Get app name.
		var appHasValidHTMLFile = options.runButton
    if (isLocal) {
		  // Get title of app, either given in options above or extracted from project.
		  // Uses title tag as first choice.
      if (!appTitle) {
        // Returns null if HTML file not found.
		    appTitle = hyper.UI.getTitleFromFile(options.dirOrFile)
		    appHasValidHTMLFile = !!appTitle
		    if (!appHasValidHTMLFile) {
			    // If app name was not found, index.html does not exist.
			    appTitle = 'Warning: HTML file does not exist, no title found'
		    }
      }
    }
	
		// Run button for examples and apps.
		// Add Run button only if app has an HTML file.
		// We use different run functions depending on if it isLocal.
		if (!isLibrary && appHasValidHTMLFile)
		{
			html += '<button type="button" class="button-run btn et-btn-green entry-button" '
			if (isLocal)
				html += `onclick="window.hyper.UI.runApp('${escapedPath}')">`
			else 
				html += `onclick="window.hyper.UI.runExampleApp('${escapedPath}')">`
			html +=	'Run</button>'
		}

		// Edit button for apps.
		// Added if it is a locally stored app that can be edited.
		if (isLocal && !isLibrary)
		{
			html += 
				'<button type="button" '
				+ 'class="button-edit btn et-btn-blue entry-button" '
				+ `onclick="window.hyper.UI.editApp('${escapedPath}')">`
				+	'Edit</button>'
		}

		// Doc button for libs.
		if (isLibrary && docURL)
		{
			html +=
				'<button type="button" '
				+ 'class="button-doc-lib btn et-btn-yellow-dark entry-button" '
				+ `onclick="window.hyper.UI.openDocURL('${docURL}')">`
				+	'Doc</button>'
		}

		// Doc button for examples.
		if (!isLocal && !isLibrary && docURL)
		{
			html +=
				'<button type="button" '
				+ 'class="button-doc-example btn et-btn-yellow-dark entry-button" '
				+ `onclick="window.hyper.UI.openDocURL('${docURL}')">`
				+	'Doc</button>'
		}

		// Copy button for examples.
		if (options.copyButton)
		{
			html +=
				'<button type="button" '
				+	'class="button-copy-example btn et-btn-indigo entry-button" '
				+	`onclick="window.hyper.UI.openCopyAppDialog('${escapedPath}', '${shortName}')">`
				+	'Copy'
				+ '</button>'
		}

		// Menu button for apps that shows more buttons.
		if (isLocal && !isLibrary)
		{
			// Increment button menu counter.
			++mEntryMenuIdCounter

			// Id for dynamic button menu.
			var entryMenuId = `entry-menu-id-${mEntryMenuIdCounter}`

			html += 
				'<button type="button" '
				+ 'class="button-more btn et-btn-indigo entry-button" '
				+ `onclick="window.hyper.UI.toggleEntryMenu('${entryMenuId}')">`
				+	'More</button>'

			// Build popup menu for app entry, shown on menu button.
			++mEntryMenuIdCounter
			html += `<div class="entry-menu" id="${entryMenuId}">`

			// Open files button.
			html +=
				'<button type="button" '
				+	'class="btn btn-default entry-menu-button" '
				+	`onclick="window.hyper.UI.openFolder('${escapedPath}')">`
				+	'Files</button>'

			// Config button.
			html +=
				'<button type="button" '
				+	'class="btn btn-default entry-menu-button" '
				+	`onclick="window.hyper.UI.openConfigAppDialog('${escapedPath}')">`
				+	'Config</button>'
			
			// Copy button.
			html +=
				'<button type="button" '
				+	'class="btn btn-default entry-menu-button" '
				+	`onclick="window.hyper.UI.openCopyAppDialog('${escapedPath}', '${shortName}')">`
				+	'Copy</button>'

			// Build button.
		  html +=
				'<button type="button" '
				+	'class="btn btn-default entry-menu-button" '
				+	`onclick="window.hyper.UI.openBuildAppDialog('${escapedPath}')">`
				+	'Build</button>'

			// Doc button.
			if (docURL) {
				html +=
					'<button type="button" '
					+	'class="btn btn-default entry-menu-button" '
					+	`onclick="window.hyper.UI.openDocURL('${docURL}')">`
					+	'Doc</button>'
			}
			// End of entry menu.
			html += '</div>'
		}

		// Delete icon.
		if (options.deleteButton)
		{
			html +=
				'<button '
				+	'type="button" '
				+	'class="close button-delete" '
				+	'onclick="window.hyper.UI.deleteEntry(this)">'
				+	'&times;'
				+ '</button>'
		}

    // Tags HTML
    var tagsHTML = ''
    appTags.forEach(tag => {
      tagsHTML += ` <span class="label label-${tag.type}">${tag.label}</span>`
    })
    
    // Meta data
    var metaHTML = ''
    if (shortName) {
      metaHTML += '<strong>Name:</strong>&nbsp;' + shortName
    }
    if (appVersion) {
      metaHTML += ' <strong>Version:</strong>&nbsp;' + appVersion
    }
		if (versions) {
			metaHTML += ' <strong>Versions:</strong>&nbsp;' + versions
		}
    if (appLibraries.length > 0) {
      metaHTML += ' <strong>Libraries:</strong>'
      var first = true
      appLibraries.forEach(lib => {
        if (first) {
          metaHTML += '&nbsp;'
          first = false
        } else {
          metaHTML += ',&nbsp;'
        }
        metaHTML += lib.name + ' (' + lib.version + ')'
      })
    }
    if (isLocal && options.path) {
      metaHTML += ' <strong>Path:</strong>&nbsp;' + options.path
    }
    
		// Different CSS classes for entry content
		var entryContentClass = 'entry-content'
		if (isLibrary) {
		  entryContentClass += '-libraries'
		} else if (!isLocal) {
		  entryContentClass += '-examples'
		}
		
		html += `<div class="${entryContentClass}"><h4>${appTitle}</h4>`
		html += `<p>${appDescription}</p>`
		html += '<p>'
		if (metaHTML.length > 0) {
		  html += metaHTML.trim()
		}
    html += `<span style="float:right">${tagsHTML}</span></p>`
    html += '<div class="project-list-entry-path" style="display:none;">' + options.path + '</div>'
		html += '</div>'
		// Create element.
		var element = hyper.UI.$(html)

		// Insert element first in list.
		options.screen && hyper.UI.$(options.screen).append(element)
	}

	// Get last part of path. Purpose is to make path fit
	// inside a project list item.
	// TODO: We don't use this anymore, but keeping it for later
	function getShortPathFromPath(path)
	{
		var limit = 58
		if (path.length < limit)
		{
			return path
		}
		else
		{
			var shortPath = path.substring(path.length - limit, path.length)
			var index1 = shortPath.indexOf('/') // OS X, Linux
			var index2 = shortPath.indexOf('\\') // Windows
			if (index1 > -1)
			{
				return shortPath.substring(index1 + 1)
			}
			else if (index2 > -1)
			{
				return shortPath.substring(index2 + 1)
			}
			else
			{
				// Fallback
				return '...' + shortPath.substring(3, shortPath.length)
			}
		}
	}

	// Project list has been reordered/changed, save new list.
	// Reads data from the DOM tree.
	function updateProjectList()
	{
		var projects = []
		var elements = hyper.UI.$('#screen-projects .project-list-entry-path')
		elements.each(function(index, element)
		{
			var path = hyper.UI.$(element).text()
			if (path != '')
			{
				projects.push(path)
			}
		})
		hyper.UI.setProjectList(projects)
	}

	hyper.UI.getTitleFromFile = function(path)
	{
			return APP_SETTINGS.getTitle(path)
	}

	function parseProjectList(json)
	{
		// Replace slashes with backslashes on Windows.
		if (process.platform === 'win32')
		{
			json = json.replace(/[\/]/g,'\\\\')
		}

		return JSON.parse(json)
	}

	function readProjectList()
	{
		var list = SETTINGS.getProjectList()
		if (list)
		{
			mProjectList = list
		}
	}

	function saveProjectList()
	{
	    SETTINGS.setProjectList(mProjectList)
	}

	hyper.UI.addProject = function(path) {
		var dir
		if (FILEUTIL.fileIsHTML(path) || FILEUTIL.fileIsEvothingsSettings(path)) {
			var dir = PATH.dirname(path)
		} else if (FILEUTIL.fileIsDirectory(path)) {
			dir = path
		}
		// If duplicate, then warn and offer to fix - otherwise bail out
		if (hyper.UI.duplicateUUID(dir)) {
			var doit = "Yes, please fix it"
			var answer = MAIN.openWorkbenchDialog('Duplicate UUID',
				'Generate a new UUID?',
				`The evothings.json file for this new project has a duplicate UUID. This can happen if you copied the whole app directory using tools outside Evothings. The easy fix is to just generate a new unique UUID.\n\nShould we generate a new UUID?`, 'question', [doit, "Cancel"])
			if (answer == doit) {
				APP_SETTINGS.generateNewAppUUID(dir)
			} else {
				return // Do NOT add project
			}
		}
		// Add the path to the project list.
		if (FILEUTIL.fileIsHTML(path)) {
			// Add the path, including HTML file, to the project list.
			mProjectList.unshift(path)
		} else {
			mProjectList.unshift(dir)
		}
		saveProjectList()
	}

	hyper.UI.duplicateUUID = function(path) {
		var newUUID = APP_SETTINGS.getAppID(path)
		for (let p of mProjectList) {
			// p can actually be == path, since the Studio may have an old entry
			// pointing to this new path (if you copy stuff around too much!)
			if (p != path && APP_SETTINGS.getAppID(p) == newUUID) {
				return true
			}
		}
		for (let p of hyper.UI.mExampleList) {
			// We also crosscheck against the Examples
			if (p.uuid == newUUID) {
				return true
			}
		}
		return false
	}

	hyper.UI.setProjectList = function(list) {
		mProjectList = list
		saveProjectList()
	}

	hyper.UI.getProjectList = function() {
		return mProjectList
	}

	/**
	 * Get path to the Workbench application directory.
	 */
	hyper.UI.getWorkbenchPath = function(path)
	{
		return mWorkbenchPath
	}

	/**
	 * Edit app in VS Code, either a file path or dir path.
	 */
	hyper.UI.editApp = function(path) {
		var error = null
{'fraction_non_alphanumeric': 0.1460681896452761, 'fraction_numerical': 0.003765354149925382, 'mean_word_length': 3.0203064426804502, 'pattern_counts': {'":': 0, '<': 125, '<?xml version=': 0, '>': 133, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5302278', 'n_tokens_mistral': 16330, 'n_tokens_neox': 14273, 'n_words': 3505}