/**
 * Blockly Demos: Plane Seat Calculator
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * 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.
 */

/**
 * @fileoverview JavaScript for Blockly's Plane Seat Calculator demo.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * Create a namespace for the application.
 */
var Plane = {};

/**
 * Lookup for names of supported languages.  Keys should be in ISO 639 format.
 */
Plane.LANGUAGE_NAME = {
  'ar': 'العربية',
  'be-tarask': 'Taraškievica',
  'br': 'Brezhoneg',
  'ca': 'Català',
  'da': 'Dansk',
  'de': 'Deutsch',
  'el': 'Ελληνικά',
  'en': 'English',
  'es': 'Español',
  'fa': 'فارسی',
  'fr': 'Français',
  'he': 'עברית',
  'hrx': 'Hunsrik',
  'hu': 'Magyar',
  'ia': 'Interlingua',
  'is': 'Íslenska',
  'it': 'Italiano',
  'ja': '日本語',
  'ko': '한국어',
  'ms': 'Bahasa Melayu',
  'nb': 'Norsk Bokmål',
  'nl': 'Nederlands, Vlaams',
  'pl': 'Polski',
  'pms': 'Piemontèis',
  'pt-br': 'Português Brasileiro',
  'ro': 'Română',
  'ru': 'Русский',
  'sc': 'Sardu',
  'sv': 'Svenska',
  'th': 'ภาษาไทย',
  'tr': 'Türkçe',
  'uk': 'Українська',
  'vi': 'Tiếng Việt',
  'zh-hans': '简体中文',
  'zh-hant': '正體中文'
};

/**
 * List of RTL languages.
 */
Plane.LANGUAGE_RTL = ['ar', 'fa', 'he'];

/**
 * Main Blockly workspace.
 * @type {Blockly.WorkspaceSvg}
 */
Plane.workspace = null;

/**
 * Extracts a parameter from the URL.
 * If the parameter is absent default_value is returned.
 * @param {string} name The name of the parameter.
 * @param {string} defaultValue Value to return if paramater not found.
 * @return {string} The parameter value or the default value if not found.
 */
Plane.getStringParamFromUrl = function(name, defaultValue) {
  var val = location.search.match(new RegExp('[?&]' + name + '=([^&]+)'));
  return val ? decodeURIComponent(val[1].replace(/\+/g, '%20')) : defaultValue;
};

/**
 * Extracts a numeric parameter from the URL.
 * If the parameter is absent or less than min_value, min_value is
 * returned.  If it is greater than max_value, max_value is returned.
 * @param {string} name The name of the parameter.
 * @param {number} minValue The minimum legal value.
 * @param {number} maxValue The maximum legal value.
 * @return {number} A number in the range [min_value, max_value].
 */
Plane.getNumberParamFromUrl = function(name, minValue, maxValue) {
  var val = Number(Plane.getStringParamFromUrl(name, 'NaN'));
  return isNaN(val) ? minValue : Math.min(Math.max(minValue, val), maxValue);
};

/**
 * Get the language of this user from the URL.
 * @return {string} User's language.
 */
Plane.getLang = function() {
  var lang = Plane.getStringParamFromUrl('lang', '');
  if (Plane.LANGUAGE_NAME[lang] === undefined) {
    // Default to English.
    lang = 'en';
  }
  return lang;
};

/**
 * Is the current language (Plane.LANG) an RTL language?
 * @return {boolean} True if RTL, false if LTR.
 */
Plane.isRtl = function() {
  return Plane.LANGUAGE_RTL.indexOf(Plane.LANG) != -1;
};

/**
 * Load blocks saved in session/local storage.
 * @param {string} defaultXml Text representation of default blocks.
 */
Plane.loadBlocks = function(defaultXml) {
  try {
    var loadOnce = window.sessionStorage.loadOnceBlocks;
  } catch(e) {
    // Firefox sometimes throws a SecurityError when accessing sessionStorage.
    // Restarting Firefox fixes this, so it looks like a bug.
    var loadOnce = null;
  }
  if (loadOnce) {
    // Language switching stores the blocks during the reload.
    delete window.sessionStorage.loadOnceBlocks;
    var xml = Blockly.Xml.textToDom(loadOnce);
    Blockly.Xml.domToWorkspace(xml, Plane.workspace);
  } else if (defaultXml) {
    // Load the editor with default starting blocks.
    var xml = Blockly.Xml.textToDom(defaultXml);
    Blockly.Xml.domToWorkspace(xml, Plane.workspace);
  }
  Plane.workspace.clearUndo();
};

/**
 * Save the blocks and reload with a different language.
 */
Plane.changeLanguage = function() {
  // Store the blocks for the duration of the reload.
  // This should be skipped for the index page, which has no blocks and does
  // not load Blockly.
  // MSIE 11 does not support sessionStorage on file:// URLs.
  if (typeof Blockly != 'undefined' && window.sessionStorage) {
    var xml = Blockly.Xml.workspaceToDom(Plane.workspace);
    var text = Blockly.Xml.domToText(xml);
    window.sessionStorage.loadOnceBlocks = text;
  }

  var languageMenu = document.getElementById('languageMenu');
  var newLang = encodeURIComponent(
      languageMenu.options[languageMenu.selectedIndex].value);
  var search = window.location.search;
  if (search.length <= 1) {
    search = '?lang=' + newLang;
  } else if (search.match(/[?&]lang=[^&]*/)) {
    search = search.replace(/([?&]lang=)[^&]*/, '$1' + newLang);
  } else {
    search = search.replace(/\?/, '?lang=' + newLang + '&');
  }

  window.location = window.location.protocol + '//' +
      window.location.host + window.location.pathname + search;
};

/**
 * Gets the message with the given key from the document.
 * @param {string} key The key of the document element.
 * @return {string} The textContent of the specified element,
 *     or an error message if the element was not found.
 */
Plane.getMsg = function(key) {
  var element = document.getElementById(key);
  if (element) {
    var text = element.textContent;
    // Convert newline sequences.
    text = text.replace(/\\n/g, '\n');
    return text;
  } else {
    return '[Unknown message: ' + key + ']';
  }
};

/**
 * User's language (e.g. "en").
 * @type {string}
 */
Plane.LANG = Plane.getLang();

Plane.MAX_LEVEL = 3;
Plane.LEVEL = Plane.getNumberParamFromUrl('level', 1, Plane.MAX_LEVEL);

Plane.rows1st = 0;
Plane.rows2nd = 0;

/**
 * Redraw the rows when the slider has moved.
 * @param {number} value New slider position.
 */
Plane.sliderChange = function(value) {
  var newRows = Math.round(value * 410 / 20);
  Plane.redraw(newRows);
};

/**
 * Change the text of a label.
 * @param {string} id ID of element to change.
 * @param {string} text New text.
 */
Plane.setText = function(id, text) {
  var el = document.getElementById(id);
  while (el.firstChild) {
    el.removeChild(el.firstChild);
  }
  el.appendChild(document.createTextNode(text));
};

/**
 * Display a checkmark or cross next to the answer.
 * @param {?boolean} ok True for checkmark, false for cross, null for nothing.
 */
Plane.setCorrect = function(ok) {
  var yes = document.getElementById('seatYes');
  var no = document.getElementById('seatNo');
  yes.style.display = 'none';
  no.style.display = 'none';
  if (ok === true) {
    yes.style.display = 'block';
  } else if (ok === false) {
    no.style.display = 'block';
  }
};

/**
 * Initialize Blockly and the SVG plane.
 */
Plane.init = function() {
  Plane.initLanguage();

  // Fixes viewport for small screens.
  var viewport = document.querySelector('meta[name="viewport"]');
  if (viewport && screen.availWidth < 725) {
    viewport.setAttribute('content',
        'width=725, initial-scale=.35, user-scalable=no');
  }

  Plane.workspace = Blockly.inject('blockly',
      {media: '../../media/',
       rtl: Plane.isRtl(),
       toolbox: document.getElementById('toolbox')});

  var defaultXml =
      '<xml>' +
      '  <block type="plane_set_seats" deletable="false" x="70" y="70">' +
      '  </block>' +
      '</xml>';
  Plane.loadBlocks(defaultXml);

  Plane.workspace.addChangeListener(Plane.recalculate);
  Plane.workspace.addChangeListener(Blockly.Events.disableOrphans);

  // Initialize the slider.
  var svg = document.getElementById('plane');
  Plane.rowSlider = new Slider(60, 330, 425, svg, Plane.sliderChange);
  Plane.rowSlider.setValue(0.225);

  // Draw five 1st class rows.
  Plane.redraw(5);
};

/**
 * Initialize the page language.
 */
Plane.initLanguage = function() {
  // Set the page title with the content of the H1 title.
  document.title += ' ' + document.getElementById('title').textContent;

  // Set the HTML's language and direction.
  // document.dir fails in Mozilla, use document.body.parentNode.dir instead.
  // https://bugzilla.mozilla.org/show_bug.cgi?id=151407
  var rtl = Plane.isRtl();
  document.head.parentElement.setAttribute('dir', rtl ? 'rtl' : 'ltr');
  document.head.parentElement.setAttribute('lang', Plane.LANG);

  // Sort languages alphabetically.
  var languages = [];
  for (var lang in Plane.LANGUAGE_NAME) {
    languages.push([Plane.LANGUAGE_NAME[lang], lang]);
  }
  var comp = function(a, b) {
    // Sort based on first argument ('English', 'Русский', '简体字', etc).
    if (a[0] > b[0]) return 1;
    if (a[0] < b[0]) return -1;
    return 0;
  };
  languages.sort(comp);
  // Populate the language selection menu.
  var languageMenu = document.getElementById('languageMenu');
  languageMenu.options.length = 0;
  for (var i = 0; i < languages.length; i++) {
    var tuple = languages[i];
    var lang = tuple[tuple.length - 1];
    var option = new Option(tuple[0], lang);
    if (lang == Plane.LANG) {
      option.selected = true;
    }
    languageMenu.options.add(option);
  }
  languageMenu.addEventListener('change', Plane.changeLanguage, true);
};

/**
 * Use the blocks to calculate the number of seats.
 * Display the calculated number.
 */
Plane.recalculate = function() {
  // Find the 'set' block and use it as the formula root.
  var rootBlock = null;
  var blocks = Plane.workspace.getTopBlocks(false);
  for (var i = 0, block; block = blocks[i]; i++) {
    if (block.type == 'plane_set_seats') {
      rootBlock = block;
    }
  }
  var seats = NaN;
  Blockly.JavaScript.init(Plane.workspace);
  var code = Blockly.JavaScript.blockToCode(rootBlock);
  try {
    seats = eval(code);
  } catch (e) {
    // Allow seats to remain NaN.
  }
  Plane.setText('seatText',
      Plane.getMsg('Plane_seats').replace(
          '%1', isNaN(seats) ? '?' : seats));
  Plane.setCorrect(isNaN(seats) ? null : (Plane.answer() == seats));

  // Update blocks to show values.
  function updateBlocks(blocks) {
    for (var i = 0, block; block = blocks[i]; i++) {
      block.customUpdate && block.customUpdate();
    }
  }
  updateBlocks(Plane.workspace.getAllBlocks());
  updateBlocks(Plane.workspace.flyout_.workspace_.getAllBlocks());
};

/**
 * Calculate the correct answer.
 * @return {number} Number of seats.
 */
Plane.answer = function() {
  if (Plane.LEVEL == 1) {
    return Plane.rows1st * 4;
  } else if (Plane.LEVEL == 2) {
    return 2 + (Plane.rows1st * 4);
  } else if (Plane.LEVEL == 3) {
    return 2 + (Plane.rows1st * 4) + (Plane.rows2nd * 5);
  }
  throw 'Unknown level.';
};

/**
 * Redraw the SVG to show a new number of rows.
 * @param {number} newRows
 */
Plane.redraw = function(newRows) {
  var rows1st = Plane.rows1st;
  var rows2nd = Plane.rows2nd;
  var svg = document.getElementById('plane');
  if (newRows != rows1st) {
    while (newRows < rows1st) {
      var row = document.getElementById('row1st' + rows1st);
      row.parentNode.removeChild(row);
      rows1st--;
    }
    while (newRows > rows1st) {
      rows1st++;
      var row = document.createElementNS('http://www.w3.org/2000/svg', 'use');
      row.setAttribute('id', 'row1st' + rows1st);
      // Row of 4 seats.
      row.setAttribute('x', (rows1st - 1) * 20);
      row.setAttributeNS('http://www.w3.org/1999/xlink',
          'xlink:href', '#row1st');
      svg.appendChild(row);
    }

    if (Plane.LEVEL == 3) {
      newRows = Math.floor((21 - newRows) * 1.11);
      while (newRows < rows2nd) {
        var row = document.getElementById('row2nd' + rows2nd);
        row.parentNode.removeChild(row);
        rows2nd--;
      }
      while (newRows > rows2nd) {
        rows2nd++;
        var row = document.createElementNS('http://www.w3.org/2000/svg',
                                                    'use');
        row.setAttribute('id', 'row2nd' + rows2nd);
        row.setAttribute('x', 400 - (rows2nd - 1) * 18);
        row.setAttributeNS('http://www.w3.org/1999/xlink',
            'xlink:href', '#row2nd');
        svg.appendChild(row);
      }
    }

    if (Plane.LEVEL < 3) {
      Plane.setText('row1stText',
          Plane.getMsg('Plane_rows').replace('%1', rows1st));
    } else {
      Plane.setText('row1stText',
          Plane.getMsg('Plane_rows1').replace('%1', rows1st));
      Plane.setText('row2ndText',
          Plane.getMsg('Plane_rows2').replace('%1', rows2nd));
    }

    Plane.rows1st = rows1st;
    Plane.rows2nd = rows2nd;
    Plane.recalculate();
  }
};

window.addEventListener('load', Plane.init);

// Load the user's language pack.
document.write('<script src="generated/' + Plane.LANG + '.js"></script>\n');
