'use strict';

const https = require('https');

const TF = require('right-track-core/modules/classes/RightTrackTransitAgency/TransitFeed');
const TransitFeed = TF.TransitFeed;
const TransitDivision = TF.TransitDivision;
const TransitEvent = TF.TransitEvent;


// Transit Agency Config
let CONFIG = {};

// Feed Cache
let CACHE = undefined;
let CACHE_UPDATED = new Date(0);


/**
 * Load the MTA Transit Feed
 * @param {function} callback Callback function
 * @param {Error} callback.error Transit Feed Error. The Error's message will be a pipe (|) separated
 * string in the format of: Error Code|Error Type|Error Message that will be parsed out by the Right
 * Track API Server into a more specific error Response.
 * @param {TransitFeed} [callback.feed] The built Transit Feed for the MTA
 */
function loadFeed(config, callback) {
  CONFIG = config;

  // Return Cached Feed
  if ( CACHE && CACHE_UPDATED.getTime() >= (new Date().getTime() - (CONFIG.maxCache*1000)) ) {
    return callback(null, CACHE);
  }

  // Download the MTA Feed
  _download(function(mta) {

    // Process the MTA Feed
    if ( mta ) {
      let feed = _parse(mta);

      // Return the Feed
      if ( feed ) {
        CACHE = feed;
        CACHE_UPDATED = feed.updated;
        return callback(null, feed);
      }

      // No Feed Returned
      else {
        _parseError();
      }
    }

    // No Feed Returned
    else {
      _parseError();
    }

  });


  /**
   * Return a Parse Error Response
   * @private
   */
  function _parseError() {
    return callback(
      new Error("5004|Could Not Parse Transit Data|The MTA Status Feed did not return a valid response.  This may be temporary so try again later.")
    );
  }

}


/**
 * Build the Transit Feed using the parsed alert entities from the MTA API
 * @param {Date} last_updated DateTime of when the Feed was last updated
 * @param {Object[]} entities Parsed entities containing the info needed for the Feed
 * @returns {TransitFeed} The built Transit Feed
 * @private
 */
function _buildFeed(last_updated, entities) {
  let feed = new TransitFeed(last_updated);

  // Parse each division from the agency config...
  let divisions = [];
  for ( let i = 0; i < CONFIG.divisions.length; i++ ) {
    let d = CONFIG.divisions[i];

    // Create the Division
    let division = new TransitDivision(d.code, d.name, d.icon);

    // Build the Lines for the Division
    division.divisions =  _buildLines(d, entities);

    divisions.push(division);
  }

  // Add the Divisions to the Feed
  feed.divisions = divisions;
  return feed;
}


/**
 * Build the sub-Divisions for the specified parent Division
 * @param {Object} division Division information (from the agency config)
 * @param {Object[]} entities Parsed entities (from the MTA API)
 * @returns {TransitDivision[]} Sub-Divisions/Lines for the specified parent Division
 * @private
 */
function _buildLines(division, entities) {
  let mta_agency_ids = division.mta_agency_ids;
  let lines = [];

  // Parse the pre-defined lines for the division
  if ( division.lines ) {
    for ( let i = 0; i < division.lines.length; i++ ) {
      let l = division.lines[i];

      // Create top level division
      let line = new TransitDivision(l.code, l.name, l.backgroundColor, l.textColor);

      // Create Events for pre-defined routes
      if ( l.mta_route_id ) {
        line.events = _buildEvents(mta_agency_ids, l.mta_route_id, entities);
        line.status = line.events && line.events.length > 0 ? line.events[0].status : 'Good Service';
      }

      // Find dynamic routes (2nd level divisions)
      else if ( l.mta_route_id_matches ) {
        let routes = _findRoutes(mta_agency_ids, l.mta_route_id_matches, entities);
        let sub_lines = [];
        
        // Create dynamic routes
        for ( let j = 0; j < routes.length; j++ ) {
          let sub_line = new TransitDivision(routes[j], routes[j], l.backgroundColor, l.textColor);

          // Create Events for the dynamic route
          sub_line.events = _buildEvents(mta_agency_ids, routes[j], entities);
          sub_line.status = sub_line.events && sub_line.events.length > 0 ? sub_line.events[0].status : 'Good Service';

          sub_lines.push(sub_line);
        }

        line.divisions = sub_lines;
      }

      lines.push(line);
    }
  }

  return lines;
}


/**
 * Find matching MTA Route IDs given the specified Agency IDs and Route ID RegExps
 * @param {String[]} agency_ids MTA Agency IDs
 * @param {String[]} route_id_matches Regular Expressions for matching MTA Route IDs
 * @param {Object[]} entities Parsed entities (from the MTA API) 
 * @returns {String[]} Array of matching MTA Route IDs
 */
function _findRoutes(agency_ids, route_id_matches, entities) {
  let route_ids = [];
  for ( let i = 0; i < entities.length; i++ ) {
    let e = entities[i];
    for ( let j = 0; j < e.routes.length; j++ ) {
      let r = e.routes[j];
      for ( let k = 0; k < route_id_matches.length; k++ ) {
        let re = new RegExp(route_id_matches[k], "i");
        if ( agency_ids.includes(r.agency_id) && r.route_id.match(re) ) {
          if ( !route_ids.includes(r.route_id) ) route_ids.push(r.route_id);
        }
      }
    }
  }

  route_ids.sort();
  return(route_ids);
}


/**
 * Build the Transit Events for the specified Route ID
 * @param {String[]} agency_ids MTA Agency IDs
 * @param {String} route_id MTA Route ID
 * @param {Object[]} entities Parsed entities (from the MTA API)
 * @returns {TransitEvent[]} Array of build Transit Events
 */
function _buildEvents(agency_ids, route_id, entities) {
  let events = [];
  for ( let j = 0; j < entities.length; j++ ) {
    let e = entities[j];
    for ( let k = 0; k < e.routes.length; k++ ) {
      let r = e.routes[k];
      if ( agency_ids.includes(r.agency_id) && r.route_id === route_id ) {
        let event = new TransitEvent(e.event.status, e.event.details);
        event._sort = r.sort_order;
        events.push(event);
      }
    }
  }

  let rtn = [];
  events.sort(function(a, b){ return b._sort-a._sort });
  for ( let j = 0; j < events.length; j++ ) {
    rtn.push({
      status: events[j].status,
      details: events[j].details
    });
  }
  
  return rtn;
}


/**
 * Parse the MTA Service Feed API Response into a Transit Feed
 * @param {Object} mta JSON-parsed MTA Service Feed
 * @returns {TransitFeed} The built Transit Feed
 * @private
 */
 function _parse(mta) {

  // Set the Last Updated time from the Timestamp, if provided
  let last_updated = mta && mta.header && mta.header.timestamp 
      ? new Date(mta.header.timestamp * 1000) 
      : new Date();

  // Parse each of the Alert entities
  let entities = mta && mta.entity ? mta.entity : [];
  let parsed_entities = [];
  for ( let i = 0; i < entities.length; i++ ) {
    let parsed_entity = _parseEntity(entities[i]);
    if ( parsed_entity ) parsed_entities.push(parsed_entity);
  }

  // Build and Return the Feed
  return _buildFeed(last_updated, parsed_entities);

}


/**
 * Parse the MTA Alert Entity and retrieve the routes and event 
 * details for an entity that should be displayed in the Feed
 * @param {Object} e Alert Entity
 * @returns {Object|Boolean} false if the entity should not be displayed
 * For active alerts, return an object with the following keys:
 *    - routes: agency_id, route_id, and sort_order of impacted routes
 *    - event: id, status, and details of event
 */
function _parseEntity(e) {
  let a = e.alert;
  if ( a ) {
    let id = e.id;

    // Determine if the entity is active
    let now = (new Date().getTime()) / 1000;
    let aps = a.active_period ? a.active_period : [];
    let active = aps.length > 0 ? false : true;
    for ( let i = 0; i < aps.length; i++ ) {
      let ap = aps[i];
      if ( ap.start <= now && !ap.end ) active = true;
      if ( ap.start <= now && ap.end && ap.end >= now ) active = true;
    }

    // Continue parsing active alerts
    if ( active ) {
      
      // Get the Agencies and Routes
      let ies = a.informed_entity;
      let routes = [];
      for ( let i = 0; i < ies.length; i++ ) {
        let ie = ies[i];
        if ( ie.hasOwnProperty("transit_realtime.mercury_entity_selector") ) {
          routes.push({
            agency_id: ie.agency_id,
            route_id: ie.route_id,
            sort_order: (ie["transit_realtime.mercury_entity_selector"].sort_order).split(':')[2]
          });
        }
      }

      // Get the Event Details
      if ( routes.length > 0 ) {

        // Set status = alert_type
        let status = a["transit_realtime.mercury_alert"] && a["transit_realtime.mercury_alert"].alert_type 
          ? a["transit_realtime.mercury_alert"].alert_type 
          : "Alert";

        // Get properties for details
        let header;
        if ( a.header_text && a.header_text.translation ) { 
          for ( let i = 0; i < a.header_text.translation.length; i++ ) {
            if ( !header || a.header_text.translation[i].language === 'en-html' ) {
              header = a.header_text.translation[i].text;
            }
          }
        }
        let description;
        if ( a.description_text && a.description_text.translation ) {
          for ( let i = 0; i < a.description_text.translation.length; i++ ) {
            if ( !description || a.description_text.translation[i].language === 'en-html' ) {
              description = a.description_text.translation[i].text;
            }
          }
        }
        let additional_information;
        if ( a["transit_realtime.mercury_alert"].additional_information && a["transit_realtime.mercury_alert"].additional_information.translation ) {
          for ( let i = 0; i < a["transit_realtime.mercury_alert"].additional_information.translation.length; i++ ) {
            if ( !additional_information || a["transit_realtime.mercury_alert"].additional_information.translation[i].language === 'en-html' ) {
              additional_information = "<p>" + a["transit_realtime.mercury_alert"].additional_information.translation[i].text + "</p>";
            }
          }
        }
        let updated;
        if ( a["transit_realtime.mercury_alert"].updated_at ) {
          updated = "<p><strong>Last Updated:</strong> " + new Date(a["transit_realtime.mercury_alert"].updated_at * 1000).toLocaleString() + "</p>";
        }
        let active;
        if ( a["transit_realtime.mercury_alert"].human_readable_active_period && a["transit_realtime.mercury_alert"].human_readable_active_period.translation ) {
          for ( let i = 0; i < a["transit_realtime.mercury_alert"].human_readable_active_period.translation.length; i++ ) {
            if ( !active || a["transit_realtime.mercury_alert"].human_readable_active_period.translation[i].language === 'en-html' ) {
              active = "<p><strong>Active:</strong> " + a["transit_realtime.mercury_alert"].human_readable_active_period.translation[i].text + "</p>";
            }
          }
        }

        // Build details (header, details, info)
        let details = "";
        if ( header && header !== status ) details += "<div class='event-details-header'>" + _clean(header) + "</div>";
        details += "<div class='event-details-description'>";
        if ( description ) details += _clean(description);
        if ( additional_information ) details += _clean(additional_information);
        details += "</div>";
        details += "<div class='event-details-info'>";
        if ( active ) details += active;
        if ( updated ) details += updated;
        details += "</div>";

        // Parse Event Tokens
        details = _parseTokens(details);

        // Set event
        let event = {
          id: id,
          status: status,
          details: details
        };
        
        // Return the parsed routes and event
        return {
          routes: routes,
          event: event
        }

      }
    }
  }

  // Entity should not be displayed
  return false;
}


/**
 * Clean the specified HTML
 * - set hidden sections as visible
 * - replace leading and trailing breaks
 * - replace empty links
 * @param {String} html HTML to clean
 * @returns {String} cleaned HTML
 */
function _clean(html) {
  html = html.replace(/display: ?none/g, "display:block");    // replace hidden sections
  html = html.replace(/^(<br ?(\/)?>\s*)+/g, "");             // replace leading breaks
  html = html.replace(/(<br ?(\/)?>\s*)+$/g, "");             // replace trailing breaks
  html = html.replace(/<a ([^>]+)>\s<\/a>/g, "<a $1><i class='material-icons' style='font-size: 14px; padding: 5px'>open_in_new</i></a>");    
  return html;
}


/**
 * Find and replace icon tokens in the event details
 * @param {String} details The details to parse
 * @returns {String} parsed details
 */
function _parseTokens(details) {
  
  // Replace Event Tokens
  for ( let i = 0; i < CONFIG.eventTokens.length; i++ ) {
    if ( details.indexOf(CONFIG.eventTokens[i].token) > -1 ) {
      let replace = CONFIG.eventTokens[i].token.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
      let regex = new RegExp(replace, 'g');
      details = details.replace(regex, CONFIG.eventTokens[i].replace);
    }
  }

  // Add Event Styles
  let style = "";
  for ( let i = 0; i < CONFIG.eventStyles.length; i++ ) {
    if ( details.indexOf(CONFIG.eventStyles[i].selector) > -1 ) {
      style += CONFIG.eventStyles[i].style;
    }
  }
  if ( style !== "" ) {
    style = "<style>" + style + "</style>";
    details = style + details;
  }

  // Return the parsed details
  return details;

}


/**
 * Download the MTA Status Feed
 * @param {Function} callback Callback function(body)
 * @param {Object} callback.body JSON-parsed response
 * @private
 */
function _download(callback) {

  // Set Request Options
  const parsed = new URL(CONFIG.url);
  const options = {
    hostname: parsed.hostname,
    path: parsed.pathname,
    method: 'GET',
    headers: {
       'x-api-key': CONFIG.apiKey
    }
  };

  // Make the get request
  https.get(options, function(response) {
    let body = "";
    response.on("data", function(data) {
      body += data;
    });
    response.on("end", function() {
      try {
        body = body.toString();
        body = JSON.parse(body);
      }
      catch (err) {
        console.log("ERROR: Could not parse MTA Transit Feed API Response [" + err + "]");
      }
      return callback(body);
    });
  }).on('error', function(err) {
    console.error(err);
    return callback();
  });

}


module.exports = loadFeed;