// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you 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.

'use strict';

const fs = require('fs');
const http = require('http');
const path = require('path');
const url = require('url');

const express = require('express');
const multer = require('multer');
const serveIndex = require('serve-index');

const isDevMode = require('../devmode');
const resources = require('./resources');
const {Server} = require('./httpserver');

const WEB_ROOT = '/common';
const DATA_ROOT = '/data';
const JS_ROOT = '/javascript';

const baseDirectory = resources.locate(isDevMode ? 'common/src/web' : '.');
const dataDirectory = path.join(__dirname, 'data');
const jsDirectory = resources.locate(isDevMode ? 'javascript' : '..');

const Pages = (function() {
  var pages = {};
  function addPage(page, path) {
    pages.__defineGetter__(page, function() {
      return exports.whereIs(path);
    });
  }

  addPage('ajaxyPage', 'ajaxy_page.html');
  addPage('alertsPage', 'alerts.html');
  addPage('bodyTypingPage', 'bodyTypingTest.html');
  addPage('booleanAttributes', 'booleanAttributes.html');
  addPage('childPage', 'child/childPage.html');
  addPage('chinesePage', 'cn-test.html');
  addPage('clickJacker', 'click_jacker.html');
  addPage('clickEventPage', 'clickEventPage.html');
  addPage('clicksPage', 'clicks.html');
  addPage('colorPage', 'colorPage.html');
  addPage('deletingFrame', 'deletingFrame.htm');
  addPage('draggableLists', 'draggableLists.html');
  addPage('dragAndDropPage', 'dragAndDropTest.html');
  addPage('droppableItems', 'droppableItems.html');
  addPage('documentWrite', 'document_write_in_onload.html');
  addPage('dynamicallyModifiedPage', 'dynamicallyModifiedPage.html');
  addPage('dynamicPage', 'dynamic.html');
  addPage('echoPage', 'echo');
  addPage('errorsPage', 'errors.html');
  addPage('xhtmlFormPage', 'xhtmlFormPage.xhtml');
  addPage('formPage', 'formPage.html');
  addPage('formSelectionPage', 'formSelectionPage.html');
  addPage('framesetPage', 'frameset.html');
  addPage('grandchildPage', 'child/grandchild/grandchildPage.html');
  addPage('html5Page', 'html5Page.html');
  addPage('html5OfflinePage', 'html5/offline.html');
  addPage('iframePage', 'iframes.html');
  addPage('javascriptEnhancedForm', 'javascriptEnhancedForm.html');
  addPage('javascriptPage', 'javascriptPage.html');
  addPage('linkedImage', 'linked_image.html');
  addPage('longContentPage', 'longContentPage.html');
  addPage('macbethPage', 'macbeth.html');
  addPage('mapVisibilityPage', 'map_visibility.html');
  addPage('metaRedirectPage', 'meta-redirect.html');
  addPage('missedJsReferencePage', 'missedJsReference.html');
  addPage('mouseTrackerPage', 'mousePositionTracker.html');
  addPage('nestedPage', 'nestedElements.html');
  addPage('readOnlyPage', 'readOnlyPage.html');
  addPage('rectanglesPage', 'rectangles.html');
  addPage('redirectPage', 'redirect');
  addPage('resultPage', 'resultPage.html');
  addPage('richTextPage', 'rich_text.html');
  addPage('selectableItemsPage', 'selectableItems.html');
  addPage('selectPage', 'selectPage.html');
  addPage('simpleTestPage', 'simpleTest.html');
  addPage('simpleXmlDocument', 'simple.xml');
  addPage('sleepingPage', 'sleep');
  addPage('slowIframes', 'slow_loading_iframes.html');
  addPage('slowLoadingAlertPage', 'slowLoadingAlert.html');
  addPage('svgPage', 'svgPiechart.xhtml');
  addPage('tables', 'tables.html');
  addPage('underscorePage', 'underscore.html');
  addPage('unicodeLtrPage', 'utf8/unicode_ltr.html');
  addPage('uploadPage', 'upload.html');
  addPage('veryLargeCanvas', 'veryLargeCanvas.html');
  addPage('xhtmlTestPage', 'xhtmlTest.html');

  return pages;
})();


const Path = {
  BASIC_AUTH: WEB_ROOT + '/basicAuth',
  ECHO: WEB_ROOT + '/echo',
  GENERATED: WEB_ROOT + '/generated',
  MANIFEST: WEB_ROOT + '/manifest',
  REDIRECT: WEB_ROOT + '/redirect',
  PAGE: WEB_ROOT + '/page',
  SLEEP: WEB_ROOT + '/sleep',
  UPLOAD: WEB_ROOT + '/upload'
};

var app = express();

app.get('/', sendIndex)
.get('/favicon.ico', function(req, res) {
  res.writeHead(204);
  res.end();
})
.use(JS_ROOT, serveIndex(jsDirectory), express.static(jsDirectory))
.post(Path.UPLOAD, handleUpload)
.use(WEB_ROOT, serveIndex(baseDirectory), express.static(baseDirectory))
.use(DATA_ROOT, serveIndex(dataDirectory), express.static(dataDirectory))
.get(Path.ECHO, sendEcho)
.get(Path.PAGE, sendInifinitePage)
.get(Path.PAGE + '/*', sendInifinitePage)
.get(Path.REDIRECT, redirectToResultPage)
.get(Path.SLEEP, sendDelayedResponse)

if (isDevMode) {
  var closureDir = resources.locate('third_party/closure/goog');
  app.use('/third_party/closure/goog',
      serveIndex(closureDir), express.static(closureDir));
}
var server = new Server(app);


function redirectToResultPage(_, response) {
  response.writeHead(303, {
    Location: Pages.resultPage
  });
  return response.end();
}


function sendInifinitePage(request, response) {
  var pathname = url.parse(request.url).pathname;
  var lastIndex = pathname.lastIndexOf('/');
  var pageNumber =
      (lastIndex == -1 ? 'Unknown' : pathname.substring(lastIndex + 1));
  var body = [
    '<!DOCTYPE html>',
    '<title>Page', pageNumber, '</title>',
    'Page number <span id="pageNumber">', pageNumber, '</span>',
    '<p><a href="../xhtmlTest.html" target="_top">top</a>'
  ].join('');
  response.writeHead(200, {
    'Content-Length': Buffer.byteLength(body, 'utf8'),
    'Content-Type': 'text/html; charset=utf-8'
  });
  response.end(body);
}


function sendDelayedResponse(request, response) {
  var duration = 0;
  var query = url.parse(request.url).query || '';
  var match = query.match(/\btime=(\d+)/);
  if (match) {
    duration = parseInt(match[1], 10);
  }

  setTimeout(function() {
    var body = [
      '<!DOCTYPE html>',
      '<title>Done</title>',
      '<body>Slept for ', duration, 's</body>'
    ].join('');
    response.writeHead(200, {
      'Content-Length': Buffer.byteLength(body, 'utf8'),
      'Content-Type': 'text/html; charset=utf-8',
      'Cache-Control': 'no-cache',
      'Pragma': 'no-cache',
      'Expires': 0
    });
    response.end(body);
  }, duration * 1000);
}


function handleUpload(request, response) {
  let upload = multer({storage: multer.memoryStorage()}).any();
  upload(request, response, function(err) {
    if (err) {
      response.writeHead(500);
      response.end(err + '');
    } else {
      response.writeHead(200);
      response.write(request.files[0].buffer);
      response.end('<script>window.top.window.onUploadDone();</script>');
    }
  });
}


function sendEcho(request, response) {
  var body = [
    '<!DOCTYPE html>',
    '<title>Echo</title>',
    '<div class="request">',
    request.method, ' ', request.url, ' ', 'HTTP/', request.httpVersion,
    '</div>'
  ];
  for (var name in request.headers) {
    body.push('<div class="header ', name , '">',
        name, ': ', request.headers[name], '</div>');
  }
  body = body.join('');
  response.writeHead(200, {
    'Content-Length': Buffer.byteLength(body, 'utf8'),
    'Content-Type': 'text/html; charset=utf-8'
  });
  response.end(body);
}


/**
 * Responds to a request for the file server's main index.
 * @param {!http.ServerRequest} request The request object.
 * @param {!http.ServerResponse} response The response object.
 */
function sendIndex(request, response) {
  var pathname = url.parse(request.url).pathname;

  var host = request.headers.host;
  if (!host) {
    host = server.host();
  }

  var requestUrl = ['http://' + host + pathname].join('');

  function createListEntry(path) {
    var url = requestUrl + path;
    return ['<li><a href="', url, '">', path, '</a>'].join('');
  }

  var data = ['<!DOCTYPE html><h1>/</h1><hr/><ul>',
              createListEntry('common'),
              createListEntry('data')];
  if (isDevMode) {
    data.push(createListEntry('javascript'));
  }
  data.push('</ul>');
  data = data.join('');

  response.writeHead(200, {
    'Content-Type': 'text/html; charset=UTF-8',
    'Content-Length': Buffer.byteLength(data, 'utf8')
  });
  response.end(data);
}


// PUBLIC application


/**
 * Starts the server on the specified port.
 * @param {number=} opt_port The port to use, or 0 for any free port.
 * @return {!Promise<Host>} A promise that will resolve
 *     with the server host when it has fully started.
 */
exports.start = server.start.bind(server);


/**
 * Stops the server.
 * @return {!Promise} A promise that will resolve when the
 *     server has closed all connections.
 */
exports.stop = server.stop.bind(server);


/**
 * Formats a URL for this server.
 * @param {string=} opt_pathname The desired pathname on the server.
 * @return {string} The formatted URL.
 * @throws {Error} If the server is not running.
 */
exports.url = server.url.bind(server);


/**
 * Builds the URL for a file in the //common/src/web directory of the
 * Selenium client.
 * @param {string} filePath A path relative to //common/src/web to compute a
 *     URL for.
 * @return {string} The formatted URL.
 * @throws {Error} If the server is not running.
 */
exports.whereIs = function(filePath) {
  filePath = filePath.replace(/\\/g, '/');
  if (!filePath.startsWith('/')) {
    filePath = `${WEB_ROOT}/${filePath}`;
  }
  return server.url(filePath);
};


exports.Pages = Pages;


if (require.main === module) {
  server.start(2310).then(function() {
    console.log('Server running at ' + server.url());
  });
}
