// abstracts out source of flight info data stream
// TODO: Make an abstract datasource base class and have this class be a concrete derivation

registerNS("EXL.DS");

// FlightSearch DataSource class - one instance represents all results data on a given flight search
EXL.DS.FlightSearch = Class.create({
  
  outbounds: [],
  inbounds: [],
  trips: [],
  
 _proxyURL: "http://betarequests.expedia.com/daily/exl/bfswrap.aspx",   // base url of proxy
  _proxyTimeout: 60,                                            // number of seconds to wait for proxy to load
  _proxyTran: null,
  _proxyTimer: null,
  
  // instance constructor
  initialize: function() {
    this._dataDS = new YAHOO.util.DataSource(true, {
      dataType: YAHOO.util.DataSource.TYPE_XML,
      responseType: YAHOO.util.DataSource.TYPE_XML
    });
    
    // yui-overridden xml parser for bfs feed
    this._dataDS.parseXMLData = this._xmlParser.bind(this);
  },

  // set the query up and pass the request to yui
  query: function(requestParams, successCallback, failedCallback) {
      this._getBfsData(requestParams, successCallback, failedCallback);
  },
  
  // get the response from bfs (once!)
  _getBfsData: function(reqParms, successCallback, failedCallback) {
    // map request params to proxy
    var bfsParms = {
      "FrAirport" : reqParms.FromAirport,
      "ToAirport" : reqParms.ToAirport,
      "FromDate" : reqParms.FromDate,
      "ToDate" : reqParms.ToDate,
      "NumAdult" : reqParms.NumAdult,
      "NumSenior" : reqParms.NumSenior,
      "NumYouth" : reqParms.NumYouth,
      "NumChild" : reqParms.NumChild,
      "NumInfant" : reqParms.NumInfant,
      "InfantInSeat" : reqParms.InfantInSeat,
      "Class" : reqParms.Class,
      "EAPID" : EAPID,
      "TPID" : TPID,
      "rfrr" : REFERRER_ID
    };

    // prep for the proxy call
    bfsParms = Object.toQueryString(bfsParms);
    var urlStr = bfsParms ? (this._proxyURL + "?" + bfsParms) : this._proxyURL;
    window.onerror = function(msg, url, line) {
        window.onerror = null;
        YAHOO.log('Error message= '+msg+'\nURL= '+url+'\nLine Number= '+line);
        failedCallback(this);
        return true
      }.bind(this);
    
    // load the proxy dynamically
    this._proxyTran = YAHOO.util.Get.script(urlStr, {
      onSuccess: function(o) {   // proxy returned OK
          clearTimeout(this._proxyTimer);
          o.purge();  // free script from memory - we no longer need it
          try
          {
            this._dataDS.liveData = loadXmlDoc(bfsResp);
            this._getDataNodes(successCallback);            
          }
          catch(e)
          {
            YAHOO.log("Proxy didn't return or returned an invalid string.  JS exception: (" + e.message + ").", "error");
            if( typeof(bfsResp) != "undefined" )
              YAHOO.log("bfsResp contents: " + bfsResp, "error");
            this.errorMsg = "Cannot retrieve a valid response from the flight service.  Please try again.";
            failedCallback(this);            
          }
      },
      onFailure: function(o) {   // proxy call failed
        clearTimeout(this._proxyTimer);
        this.errorMsg = "Failed to connect to the flight service.  Please try again.";
        failedCallback(this);
      },
      scope: this,
      varName: ["bfsResp"]
    });
    
    // setup a timeout for the script load
    this._proxyTimer = setTimeout(function() {
        YAHOO.util.Get.abort(this._proxyTran);
        YAHOO.util.Get._finalize(this._proxyTran.tId);  // small hack around yui bug 1791548
      }.bind(this), this._proxyTimeout * 1000);
  },
  
  // call custom xml parser and save data in internal state
  _getDataNodes: function(callback) {
    this._dataDS.sendRequest(null, {
      success: function(oRequest, oResponse, oPayload) { 
        this.trips = oResponse.results.trips;
        this.outbounds = oResponse.results.outbounds;
        this.inbounds = oResponse.results.inbounds;
        callback(this);
      },
      failure: function(oRequest, oResponse, oPayload) {
        throw new Error("Could not locate itineraries in xml response");
      },
      scope: this
    });
  },
  
  // custom xml parser for bfs data
  _xmlParser: function(oRequest, oFullResponse) {
    oParsedResponse = {results: {trips: [], inbounds: [], outbounds: []}, meta:{}, error: false};

    // parse outbound flights
    for(var nodeSet = oFullResponse.documentElement.childNodes[0].getElementsByTagName("FltInf"), iLoop = 0; iLoop < nodeSet.length; iLoop++)
    {
      oParsedResponse.results.outbounds.push(this._parseFlight(nodeSet[iLoop]));
    }
    
    // parse inbound flights
    for(var nodeSet = oFullResponse.documentElement.childNodes[1].getElementsByTagName("FltInf"), iLoop = 0; iLoop < nodeSet.length; iLoop++)
    {
      oParsedResponse.results.inbounds.push(this._parseFlight(nodeSet[iLoop]));
    }
    
    // parse through itineraries
    for(var nodeSet = oFullResponse.documentElement.childNodes[2].getElementsByTagName("Itin"), iLoop = 0; iLoop < nodeSet.length; iLoop++)
    {
      // calc attributes
      var basePrice = getTextNodeValue(nodeSet[iLoop].getElementsByTagName("TotPrice")[0]).replace(".", "");
      var outbound = oParsedResponse.results.outbounds[parseInt(getTextNodeValue(nodeSet[iLoop].getElementsByTagName("FltInfID")[0]))];
      var inbound = oParsedResponse.results.inbounds[parseInt(getTextNodeValue(nodeSet[iLoop].getElementsByTagName("FltInfID")[1]))];
      var carriers = outbound.legs.pluck('marketingCarrier').concat(inbound.legs.pluck('marketingCarrier')).uniq();
      var carrier = (carriers.length == 1) ? carriers.first().strip() : 'MULT';
      var currency = getTextNodeValue(nodeSet[iLoop].getElementsByTagName("CyCode")[0]);
      var purchaseParams = {
        wizardSearchDataSet: getTextNodeValue(oFullResponse.documentElement.childNodes[3].getElementsByTagName("WSDS")[0]),
        travelObjectVersion: getTextNodeValue(oFullResponse.documentElement.childNodes[3].getElementsByTagName("TOVR")[0])
      };
      var numPass = getTextNodeValue(oFullResponse.documentElement.childNodes[3].getElementsByTagName("NumPax")[0]);
      
      // construct returned object
      oParsedResponse.results.trips.push({
        "price":  basePrice / numPass,
        "total_price" : basePrice,
        "currencyCode" : currency,
        "outbound": outbound,
        "inbound": inbound,
        "carrier" : carrier,
        "purchaseParams" : purchaseParams,
        "totalDuration": inbound.duration + outbound.duration
      })
    }
    
    return oParsedResponse;
  },

  // flight info parser
  _parseFlight : function(flight) {
    oFlight = {legs: []};
    var flightStops = 0;
    
    // parse the flight legs
    for(var nodeSet = flight.getElementsByTagName("FltLeg"), iLoop = 0; iLoop < nodeSet.length; iLoop++)
    {
      var legStops = parseInt(getTextNodeValue(nodeSet[iLoop].getElementsByTagName("NumStops")[0]));
      flightStops += legStops;
      
      oFlight.legs.push({
        "departure" : this._parseEndpoint(nodeSet[iLoop].getElementsByTagName("Dept")[0]),
        "arrival" :   this._parseEndpoint(nodeSet[iLoop].getElementsByTagName("Arr")[0]),
        "marketingCarrier" : getTextNodeValue(nodeSet[iLoop].getElementsByTagName("MktgCxrCode")[0]).strip(),
        "equipment" : getTextNodeValue(nodeSet[iLoop].getElementsByTagName("Eqp")[0]),
        "flightNumber" : getTextNodeValue(nodeSet[iLoop].getElementsByTagName("FltNum")[0]),
        "stops" : legStops
      });
    }

    // parse the flight duration
    var hours = parseInt(getTextNodeValue(flight.getElementsByTagName("hours")[0]));
    var minutes = parseInt(getTextNodeValue(flight.getElementsByTagName("minutes")[0]));
    oFlight.duration = (hours * 60) + minutes;
    
    // add remainder of flight attributes
    oFlight.id = flight.getAttribute("id");
    oFlight.totalStops = flightStops + (oFlight.legs.length-1);
    oFlight.departure = oFlight.legs.first().departure;
    oFlight.arrival = oFlight.legs.last().arrival;
    oFlight.blob = getTextNodeValue(flight.getElementsByTagName("FltBlob")[0]);
    
    return oFlight;
  },
  
  // parse flight endpoint info
  _parseEndpoint : function(endpoint) {
    return({
      "airport" : getTextNodeValue(endpoint.getElementsByTagName("AirpCode")[0]),
      "time" : Date.parse(getTextNodeValue(endpoint.getElementsByTagName("Date-iso")[0]))
    });
  }
});

