//===================================================================================
//                                                           FFCal_mini.mq4
//                                           ver.0.4 20121110  fai_hatena
//===================================================================================
//                                                                FFCal.mq4
//                                            Copyright © 2006, Derk Wehler
//                                                   (derkwehler@gmail.com)
// Written in cooperation with:                 http://www.forexfactory.com
//
//                                            $Workfile:: FFCal.mq4                 $
//                                            $Revision:: 20                        $
//                                              $Author:: Derk                      $
//                                                $Date:: 7/07/09 5:40a             $
//
// This "indicator" calls DLLs to fetch a special XML file from the
// ForexFactory web site.  It then parses it and writes it out as a .CSV
// file, which it places in the folder: experts/files so that IsNewsTime()
// can use that file to tell if it is near announcement time.
//
// It does this once when it starts up and once per 6 hours in case there
// have been any updates to the annoucement calendar.  In order to lessen
// sudden traffic on the FF site, it refreshes every 6 hours on a random
// minute.
//
// SAMPLE CALLS TO THE INDICATOR:
//
//     int minutesSincePrevEvent =
//            iCustom(NULL, 0, "FFCal", true, true, false, true, true, 1, 0);
//
//     int minutesUntilNextEvent =
//            iCustom(NULL, 0, "FFCal", true, true, false, true, true, 1, 1);
//
//     // Use this call to get ONLY impact of previous event
//     int impactOfPrevEvent =
//            iCustom(NULL, 0, "FFCal", true, true, false, true, true, 2, 0);
//
//     // Use this call to get ONLY impact of nexy event
//     int impactOfNextEvent =
//            iCustom(NULL, 0, "FFCal", true, true, false, true, true, 2, 1);
//
//
// EXAMPLE CODE FOR USE IN AN EA:
// (NOTE I HAVE PUT IN CODE TO CALL THE INDICATOR ONLY ONCE PER MINUTE)
//
// // EA Setting variables
// extern int MinsBeforeNews = 60; // mins before an event to stay out of trading
// extern int MinsafterNews  = 60; // mins after  an event to stay out of trading
//
// // Global variable at top of file
// bool NewsTime;
//
// // Function to check if it is news time
// void NewsHandling()
// {
//     static int PrevMinute = -1;
//
//     if (Minute() != PrevMinute)
//     {
//         PrevMinute = Minute();
//
//         int minutesSincePrevEvent =
//             iCustom(NULL, 0, "FFCal", true, true, false, true, true, 1, 0);
//
//         int minutesUntilNextEve nt =
//             iCustom(NULL, 0, "FFCal", true, true, false, true, true, 1, 1);
//
//         NewsTime = false;
//         if ((minutesUntilNextEvent <= MinsBeforeNews) ||
//             (minutesSincePrevEvent <= MinsAfterNews))
//         {
//             NewsTime = true;
//         }
//     }
// }//newshandling
//
//=============================================================================
//
//                             OTHER CREDIT DUE
//      (For GrebWeb and LogUtils functionality (see end of this flie)
//
//  2/14/2007:  Robert Hill added code for using text objects instead
//              of Comment() for easier reading
//
//  2/25/2007:  Paul Hampton-Smith for his TimeZone DLL code
//  3/31/2007:  Code replaces by simpler method from BurgerKing
//
//  2/26/2007:  Mike Nguyen added the following things:
//              (search text for "Added by MN")
//
//            - Connection test so that MT4 doesnt hang when
//              there is no server connection
//            - Fixed some minor syntax because was getting
//              "too many files open error..."
//            - Added vertical lines and vertical news text so that
//              we have a visual reference of when the news happened.
//            - Now supports and correctly draws two simultaneous news
//              announcements
//            - Added text to say "xx min SINCE event..." after the
//              event as past
//            - Clean up old Objects left behind on old news annoumcents
//            - Now "Back Draws" Old news headlines onto the chart
//            - Now can choose which corner of the chart to place the
//              headlines
//
//  4/2/2007:  Mike Nguyen added the following things:
//             (search text for "Added by MN")
//           - Added ability to disable Web/URL updates. This is so that the
//             multiple instances of the indicator used by other charts or EAs
//             dont fight with each other (Error code 4103)
//           - Fixed deleting OBJ_TREND, only deletes its own Trend Lines
//             objects instead of all OBJ_TREND objects
//           - Fixed deleting OBJ_TEXT, only deletes its own Headlines
//             instead of all text
//           - Made file name global and indicator now deletes the xml file
//             each time the indicator is put on or removed from chart. Fixed
//             one case of divide by zero where multiple charts indicator is
//             on was trying to overwrite the same file (forces a new
//             download of the xml)
//
// 4/29/2007:  Derk Wehler
//           - Fixed problem where indicator returns zero for "Minutes Until
//             Next Event" when there are no more events for the week for
//             that currency pair.  That caused EAs calling it to think that
//             it was always new time.  Instead, we now set it to a flag
//             value, which EAs can test for.
//
// 5/16/2007:  Derk Wehler
//           - Added sample code to header
//           - Changed variable name from DispOldNews to DispVertNews
//
// 6/02/2007:  Derk Wehler (thanks to "Flourishing")
//           - Changed how often it updates the file from the FF site web
//             page.  Now it uses a global variable, so that when you have
//             it running on multiple charts, it should only update once
//             every 4 hours for all of them.
//
// 6/05/2007:  Derk Wehler
//           - Fixed logHandle error by resetting to -1 when closed
//           - Added "NeedToGetFile" variable for getting the XML if it
//             does not already exist
//           - Added extern "SaveXmlFiles", so that when FFCal de-inits,
//             the user can choose whether or not to delete old XML files
//
// 4/06/2014:  Zoltan Murai (filewalker)
//           - Fixed compatibility problem with MT4 build >=600
//
//
//=============================================================================
//+---------------------------------------------------------------------------+
//|                                                               WinInet.mqh |
//|                                                        Paul Hampton-Smith |
//|                                                        paul1000@pobox.com |
//|                                                                           |
//| This include has been adapted from the groundbreaking GrabWeb script      |
//| developed by Adbi that showed me how to import functions from wininet.dll |
//|---------------------------------------------------------------------------|
//|                                                               grabweb.mq4 |
//|                                                    Copyright © 2006, Abhi |
//|                                         http://www.megadelfi.com/experts/ |
//|                                     E-mail: grabwebexpert{Q)megadelfi.com |
//|                                fix my e-mail address before mailing me ;) |
//+---------------------------------------------------------------------------+


#property copyright "Copyright © 2006, Derk Wehler"
#property link      "http://www.forexfactory.com"

#property indicator_chart_window
#property indicator_buffers 3
#property indicator_color1 CLR_NONE
#property indicator_color2 CLR_NONE
#property indicator_color3 CLR_NONE


#define TITLE     0
#define COUNTRY   1
#define DATE      2
#define TIME      3
#define IMPACT    4
#define FORECAST  5
#define PREVIOUS  6

#define EVENTMAX 256

//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================
//====================================================================================

extern bool 	IncludeHigh 		= true;
extern bool 	IncludeMedium 		= true;
extern bool 	IncludeLow 			= false;
extern bool 	IncludeSpeaks 		= true; 		// news items with "Speaks" in them have different characteristics
extern bool		IsEA_Call			= false;
extern int		OffsetHours			= 0;
extern bool		AllowWebUpdates     = true;	// Set this to false when using in another EA or Chart, so that the multiple instances of the indicator dont fight with each other
extern int		Alert1MinsBefore	=  5;			// Set to -1 for no Alert
extern int		Alert2MinsBefore	= 10;			// Set to -1 for no Alert
extern bool		ReportAllPairs		= true;
extern bool     SkipSameTimeNews    = false;
extern bool 	EnableLogging 		= false; 		// Perhaps remove this from externs once its working well
extern int		ShowEventsNum	    = 4;
extern bool     ShowTextOneLine     = true;
extern int 		TxtSize             =  6;
extern color 	TxtColorNews 		= DeepSkyBlue;// Vaild if TextOneLine = false
extern color 	TxtColorOldNews     = Gray;

extern color 	TxtColorImpactHigh  = Red;
extern color 	TxtColorImpactMed   = Orange;
extern color 	TxtColorImpactLow   = Khaki;

extern int		NewsCorner 			= 2;			// Choose which corner to place headlines 0=Upper Left, 1=Upper Right, 2=lower left , 3=lower right
extern bool		SaveXmlFiles		= false;		// If true, this will keep the daily XML files

extern bool     UseDailyFXData      = true;

int		DebugLevel = 5;


double 	minsBuffer0[];    // Contains (minutes until) each news event
double 	EAminBuffer1[];   // Contains only most recent and next news event ([0] & [1])
double 	ImpactBuffer2[];  // Contains impact value for most recent and next news event


string	sUrl = "http://www.forexfactory.com/ff_calendar_thisweek.xml";
int 	logHandle = -1;

string 	mainData[EVENTMAX][7];
datetime mainDataGMT[EVENTMAX];

string 	sTags[7] = { "<title>", "<country>", "<date>", "<time>", "<impact>", "<forecast>", "<previous>" };
string 	eTags[7] = { "</title>", "</country>", "</date>", "</time>", "</impact>", "</forecast>", "</previous>" };

string  ObjPrefix = "mFFCal_";
datetime CurrentGMT;
//=================================================================================================
int init()
{
  // If we are not logging, then do not output debug statements either
  // moved by euclid
  if ( !EnableLogging )
    DebugLevel = 0;

  // Open the log file (will not open if logging is turned off)
  // Filename changed by euclid
  OpenLog( StringConcatenate( "FFCal", Symbol(), Period() ) );

  SetIndexStyle ( 0, DRAW_NONE );
  SetIndexBuffer( 0, minsBuffer0 );

  SetIndexStyle ( 1, DRAW_NONE );
  SetIndexBuffer( 1, EAminBuffer1 );

  SetIndexStyle ( 2, DRAW_NONE );
  SetIndexBuffer( 2, ImpactBuffer2 );

  IndicatorShortName( "FFCal" );
  SetIndexLabel( 0, NULL );
  SetIndexLabel( 1, NULL );
  SetIndexLabel( 2, NULL );

  return( 0 );
}
//=================================================================================================
int deinit()
{
  if ( logHandle > 0 ) //added by euclid
    FileClose( logHandle );

  if( IsEA_Call ) return( 0 );

  for( int i = ObjectsTotal() - 1; i >= 0; i-- ) {
    string ObjName = ObjectName( i );
    if ( StringFind( ObjName, ObjPrefix ) == 0 ) ObjectDelete( ObjName );
  }

  return( 0 );
}
//=================================================================================================
string GetXmlFileName()
{
  return ( Month() + "-" + Day() + "-" + Year() + "-" + Symbol() + Period() + "-" + "FFCal.xml" );
}
//=================================================================================================
int start()
{
  static int newsIdx = 0;

  static datetime LastExecuteGMTTime = D'31.12.2037';

  // check to make sure we are connected, otherwise exit. Added by MN
  if ( !IsConnected() ) {
    Print( "News Indicator is disabled because NO CONNECTION to Broker!" );
    return( 0 );
  }
  //PlaySound("tick");
  // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
  static datetime PrevLocaltime = 0;
  if ( !IsEA_Call && TimeLocal() - PrevLocaltime <= 30 ) return ( true );
  PrevLocaltime = TimeLocal();


  // Init the buffer array to zero just in case
  ArrayInitialize( minsBuffer0 , 0 );
  ArrayInitialize( EAminBuffer1, 0 );

  CurrentGMT = TimeGMT() + ( OffsetHours * 3600 );

  static datetime PrevReadTime = 0;
  if( newsIdx == 0 || TimeLocal() - PrevReadTime > 60 * 60 ) {

    if( !UseDailyFXData ) {
      string sXMLData = ReadXMLFile();
      if( sXMLData == "" ) return( 0 );
      newsIdx = ParseXML( sXMLData );
    } else {
      int ret = DownloadCSVFile();
      newsIdx = ParseCSV();
    }

  PrevReadTime = TimeLocal();
  }



  int tmpMins = 10080;	// (a week)
  int idxOfNext = newsIdx - 1;

  for( int id = 0; id < newsIdx; id++ ) {
    // If we got this far then we need to calc the minutes until this event

    // Now calculate the minutes until this announcement (may be negative)
    int minsTillNews = MathFloor( ( mainDataGMT[id] - CurrentGMT ) / 60.0 );
    if ( DebugLevel > 0 ) {
      Log( "FOREX FACTORY\nTitle: " + mainData[id][TITLE] + "\n" + minsTillNews + "\n\n" );
    }

    // Keep track of the most recent news announcement.
    // Do that by saving each one until we get to the
    // first annoucement that isn't in the past; i.e.
    // minsTillNews > 0.  Then, keep this one instead for
    // display, but only once the minutes until the next
    // news is SMALLER than the minutes since the last.
//			Print("Mins till event: ", minsTillNews);
    if ( minsTillNews < 0 || MathAbs( tmpMins ) > minsTillNews ) {
      idxOfNext = id;
      tmpMins	= minsTillNews;
    }

    // Do alert if user has enabled
    if ( !IsEA_Call && Alert1MinsBefore != -1 && minsTillNews <= Alert1MinsBefore && minsTillNews > 0
         && LastExecuteGMTTime + Alert1MinsBefore * 60 + 60  <= mainDataGMT[id] ) {
      Alert( Alert1MinsBefore, "(", minsTillNews, ") mins until news for ",
             mainData[id][COUNTRY], ": ", mainData[id][TITLE], MakeForecastPreviousTEXT( mainData[id][FORECAST], mainData[id][PREVIOUS] ) );
    }

    if ( !IsEA_Call && Alert2MinsBefore != -1 && minsTillNews <= Alert2MinsBefore && minsTillNews > 0
         && LastExecuteGMTTime + Alert2MinsBefore * 60 + 60 <= mainDataGMT[id] ) {
      Alert( Alert2MinsBefore, "(", minsTillNews, ") mins until news for ",
             mainData[id][COUNTRY], ": ", mainData[id][TITLE], MakeForecastPreviousTEXT( mainData[id][FORECAST], mainData[id][PREVIOUS] ) );
    }

    // Buffers are set up as so:
    // minsBuffer0 contains the time UNTIL each announcement (can be negative)
    // e.g. [0] = -372; [1] = 25; [2] = 450; [3] = 1768 (etc.)
    // EAminBuffer1[0] has the mintutes since the last annoucement.
    // EAminBuffer1[1] has the mintutes until the next annoucement.
    minsBuffer0[id] = minsTillNews;
  }

  LastExecuteGMTTime = CurrentGMT;

  // Cycle through the events array and pick out the most recent
  // past and the next coming event to put into EAminBuffer1.
  // Put the corresponding impact for these two into ImpactBuffer2.
  bool first = true;
  EAminBuffer1[0]  = 999999;
  EAminBuffer1[1]  = 999999;
  ImpactBuffer2[0] = 0;
  ImpactBuffer2[1] = 0;

  string outNews = "Minutes until news events for " + Symbol() + " : ";
  for ( int i = 0; i < newsIdx; i++ ) {
    outNews = outNews + minsBuffer0[i] + ", ";
    if ( minsBuffer0[i] >= 0 && first ) {
      first = false;

      // Put the relevant info into the indicator buffers...

      // Minutes SINCE - - - - - - - - - - - - - - - - - - - - - - - - -
      // (does not apply if the first event of the week has not passed)
      if ( i > 0 ) {
        EAminBuffer1[0] = MathAbs( minsBuffer0[i - 1] );
        ImpactBuffer2[0] = ImpactToNumber( mainData[i - 1][IMPACT] );
      }

      // Minutes UNTIL - - - - - - - - - - - - - - - - - - - - - - - - -
      // Check if past the last event.
      if ( minsBuffer0[i] > 0 || ( minsBuffer0[i] == 0 && minsBuffer0[i + 1] > 0 ) ) {
        EAminBuffer1[1] = minsBuffer0[i];
      }
      ImpactBuffer2[1] = ImpactToNumber( mainData[i][IMPACT] );
    }
  }

  // If we are past all news events, then neither one will have been
  // set, so set the past event to the last (negative) minutes
  if ( first ) {
    EAminBuffer1[0]  = MathAbs( minsBuffer0[i - 1] );
    EAminBuffer1[1]  = 999999;
    ImpactBuffer2[0] = ImpactToNumber( mainData[i - 1][IMPACT] );
    minsBuffer0[idxOfNext] = 999999;
  }


  // For debugging...Print the tines until news events, as a "Comment"
  if ( DebugLevel > 0 ) {
    Print( outNews );
    Print( "LastMins (EAminBuffer1[0]) = ", EAminBuffer1[0] );
    Print( "NextMins (EAminBuffer1[1]) = ", EAminBuffer1[1] );
  }

  if ( !IsEA_Call ) {
    if( ShowTextOneLine )
      for( i = 0; i < ShowEventsNum; i++ ) OutputToChartTextOneLine( i, idxOfNext );
    else
      for( i = 0; i < ShowEventsNum; i++ ) OutputToChartText( i, idxOfNext );
  }

  return ( 0 );
}

//=================================================================================================
int OutputToChartText( int id, int idxOfNext )
{
  static int curY = 0;
  if( id == 0 ) {
    curY = TxtSize + 4;
  } else {
    curY = curY + TxtSize * 2 + 2;
  }

  color  TxtColorNews_  = TxtColorNews;
  color TxtColorImpact_ = TxtColorImpactHigh;

  string title   = mainData[idxOfNext + id][TITLE];
  string country = mainData[idxOfNext + id][COUNTRY];
  string impact  = mainData[idxOfNext + id][IMPACT];
  string forecast = mainData[idxOfNext + id][FORECAST];
  string previous = mainData[idxOfNext + id][PREVIOUS];
  int    minutes = minsBuffer0[idxOfNext + id];

  string times   = TimeToStr( mainDataGMT[idxOfNext + id] + ( TimeLocal() - CurrentGMT ), TIME_MINUTES );
  if( mainDataGMT[idxOfNext + id] - CurrentGMT > 60 * 60 * 24 ) {
    times   = TimeToStr( mainDataGMT[idxOfNext + id] + ( TimeLocal() - CurrentGMT ), TIME_DATE | TIME_MINUTES );
    times   = StringSubstr( times, 5 );
  }

  if( impact == "Low" )   TxtColorImpact_ = TxtColorImpactLow;
  if( impact == "Medium" )TxtColorImpact_ = TxtColorImpactMed;

  string ImpactObj  = ObjPrefix + "Impact" + id;
  string MinutesObj = ObjPrefix + "Minutes" + id;
  ObjectDelete( ImpactObj );
  ObjectDelete( MinutesObj );


  if( id != 0 && StringLen( title ) == 0 ) return( 0 );

  if ( minutes < 0 ) TxtColorNews_ = TxtColorOldNews;

  ObjectCreate( MinutesObj, OBJ_LABEL, 0, 0, 0 );
  ObjectSet( MinutesObj, OBJPROP_CORNER, NewsCorner );
  ObjectSet( MinutesObj, OBJPROP_XDISTANCE, 10 );
  ObjectSet( MinutesObj, OBJPROP_YDISTANCE, curY );
  if ( minutes == 999999 ) {
    ObjectSetText( MinutesObj, " (No more events this week)", TxtSize, "Arial Bold", TxtColorOldNews );
  } else {
    ObjectSetText( MinutesObj, country + ": " + title + MakeForecastPreviousTEXT( forecast, previous ), TxtSize, "Arial Bold", TxtColorNews_ );
    curY = curY + TxtSize + 6;
    ObjectCreate( ImpactObj, OBJ_LABEL, 0, 0, 0 );
    ObjectSetText( ImpactObj, times + " " + impact, TxtSize, "Arial Bold", TxtColorImpact_ );

    ObjectSet( ImpactObj, OBJPROP_CORNER, NewsCorner );
    ObjectSet( ImpactObj, OBJPROP_XDISTANCE, 10 );
    ObjectSet( ImpactObj, OBJPROP_YDISTANCE, curY );
  }

  return( 0 );
}
//=================================================================================================
int OutputToChartTextOneLine( int id, int idxOfNext )
{
  static int curY = 0;
  if( id == 0 ) {
    curY = TxtSize + 4;
  } else {
    curY = curY + TxtSize * 2 + 2;
  }

  color  TxtColorNews_  = TxtColorNews;
  color TxtColorImpact_ = TxtColorImpactHigh;

  string title   = mainData[idxOfNext + id][TITLE];
  string country = mainData[idxOfNext + id][COUNTRY];
  string impact  = mainData[idxOfNext + id][IMPACT];
  string forecast = mainData[idxOfNext + id][FORECAST];
  string previous = mainData[idxOfNext + id][PREVIOUS];
  int    minutes = minsBuffer0[idxOfNext + id];

  string times   = TimeToStr( mainDataGMT[idxOfNext + id] + ( TimeLocal() - CurrentGMT ), TIME_MINUTES );
  if( mainDataGMT[idxOfNext + id] - CurrentGMT > 60 * 60 * 24 ) {
    times   = TimeToStr( mainDataGMT[idxOfNext + id] + ( TimeLocal() - CurrentGMT ), TIME_DATE | TIME_MINUTES );
    times   = StringSubstr( times, 5 );
  }

  if( impact == "High" )   TxtColorNews_ = TxtColorImpactHigh;
  if( impact == "Low" )    TxtColorNews_ = TxtColorImpactLow;
  if( impact == "Medium" ) TxtColorNews_ = TxtColorImpactMed;

  string MinutesObj = ObjPrefix + "Minutes" + id;
  ObjectDelete( MinutesObj );

  if( id != 0 && StringLen( title ) == 0 ) return( 0 );

  if ( minutes < 0 ) TxtColorNews_ = TxtColorOldNews;

  ObjectCreate( MinutesObj, OBJ_LABEL, 0, 0, 0 );
  ObjectSet( MinutesObj, OBJPROP_CORNER, NewsCorner );
  ObjectSet( MinutesObj, OBJPROP_XDISTANCE, 10 );
  ObjectSet( MinutesObj, OBJPROP_YDISTANCE, curY );
  if ( minutes == 999999 ) {
    ObjectSetText( MinutesObj, " (No more events this week)", TxtSize, "Arial Bold", TxtColorOldNews );
  } else {
    ObjectSetText( MinutesObj, times + " " + country + " " + title + MakeForecastPreviousTEXT( forecast, previous ), TxtSize, "Arial Bold", TxtColorNews_ );
  }

  return( 0 );
}



//=================================================================================================
string MakeForecastPreviousTEXT( string f, string p )
{
  if( StringLen( f ) == 0 && StringLen( p ) == 0 ) return( "" );
  if( StringLen( f ) > 0 && StringLen( p ) == 0 ) return( "  (" + f + "/-.-)" );
  if( StringLen( f ) == 0 && StringLen( p ) >  0 ) return( "  (-.-/" + p + ")" );
  return( "  (" + f + "/" + p + ")" );
}
//=================================================================================================
double ImpactToNumber( string impact )
{
  if ( impact == "High" )  return ( 3 );
  if ( impact == "Medium" )return ( 2 );
  if ( impact == "Low" )   return ( 1 );
  return ( 0 );
}
//=================================================================================================
string MakeDateTime( string strDate, string strTime )
{
  // Print("Converting Forex Factory Time into Metatrader time..."); //added by MN
  // Converts forexfactory time & date into yyyy.mm.dd hh:mm
  int n1stDash = StringFind( strDate, "-" );
  int n2ndDash = StringFind( strDate, "-", n1stDash + 1 );

  string strMonth = StringSubstr( strDate, 0, 2 );
  string strDay = StringSubstr( strDate, 3, 2 );
  string strYear = StringSubstr( strDate, 6, 4 );
//	strYear = "20" + strYear;

  int nTimeColonPos = StringFind( strTime, ":" );
  string strHour = StringSubstr( strTime, 0, nTimeColonPos );
  string strMinute = StringSubstr( strTime, nTimeColonPos + 1, 2 );
  string strAM_PM = StringSubstr( strTime, StringLen( strTime ) - 2 );

  int nHour24 = StrToInteger( strHour );
  if ((strAM_PM == "pm" || strAM_PM == "PM") && nHour24 != 12) {nHour24 += 12;}
  if ((strAM_PM == "am" || strAM_PM == "AM") && nHour24 == 12) {nHour24 = 0;}
  
  string strHourPad = "";
  if ( nHour24 < 10 )
    strHourPad = "0";

  return( StringConcatenate( strYear, ".", strMonth, ".", strDay, " ", strHourPad, nHour24, ":", strMinute ) );
}

//=================================================================================================
string ReadXMLFile()
{
  string tmpData = "";
  bool NeedToGetFile = false;
  // Added this section to check if the XML file already exists.
  // If it does NOT, then we need to set a flag to go get it
  string xmlFileName = GetXmlFileName();
  int xmlHandle = FileOpen( xmlFileName, FILE_BIN | FILE_READ );

  // File does not exist if FileOpen return -1 or if GetLastError = ERR_CANNOT_OPEN_FILE (4103)
  if ( xmlHandle >= 0 ) {
    // Since file exists, close what we just opened
    if( FileSize( xmlHandle ) < 70 ) NeedToGetFile = true;
    FileClose( xmlHandle );
  } else {
    NeedToGetFile = true;
  }

  //added by MN. Set this to false when using in another EA or Chart, so that the multiple
  //instances of the indicator dont fight with each other
  if ( AllowWebUpdates ) {
    // New method: Use global variables so that when put on multiple charts, it
    // will not update overly often; only first time and every 4 hours
    if ( DebugLevel > 1 )
      Print( GlobalVariableGet( "LastUpdateTime" ) + " " + ( TimeLocal() - GlobalVariableGet( "LastUpdateTime" ) ) );

    if ( NeedToGetFile || GlobalVariableCheck( "LastUpdateTime" ) == false ||
         ( TimeLocal() - GlobalVariableGet( "LastUpdateTime" ) ) > 14400 ) {

      if ( DebugLevel > 1 ) Print( "sUrl == ", sUrl );
      if ( DebugLevel > 0 ) Print( "Grabbing Web, url = ", sUrl );

      // THIS CALL WAS DONATED BY PAUL TO HELP FIX THE RESOURCE ERROR
      GrabWeb( sUrl, tmpData ); //PlaySound("tick");

      if ( DebugLevel > 0 ) {
        Print( "Opening XML file...\n" );
        Print( tmpData );
      }

      // Write the contents of the ForexFactory page to an .htm file
      // If it is still open from the above FileOpen call, close it.
      xmlHandle = FileOpen( xmlFileName, FILE_BIN | FILE_WRITE );
      if ( xmlHandle < 0 ) {
        if ( DebugLevel > 0 )
          Print( "Can\'t open new xml file, the last error is ", GetLastError() );
        return( "" );
      }
      FileWriteString( xmlHandle, tmpData, StringLen( tmpData ) );
      FileClose( xmlHandle );

      if ( DebugLevel > 0 ) Print( "Wrote XML file...\n" );

      // THIS BLOCK OF CODE DONATED BY WALLY TO FIX THE RESOURCE ERROR
      //--- Look for the end XML tag to ensure that a complete page was downloaded ---//
      int end = StringFind( tmpData, "</weeklyevents>", 0 );

      if ( end <= 0 ) {
        Alert( "FFCal Error - Web page download was not complete!" );
        return( "" );
      } else {
        // set global to time of last update
        GlobalVariableSet( "LastUpdateTime", TimeLocal() );
        //
      }

      //-------------------------------------------------------------------------------//
    }
  } //end of allow web updates


  if( tmpData != "" ) return( tmpData );

  // Open the XML file
  //xmlFileName = "test.xml";
  xmlHandle = FileOpen( xmlFileName, FILE_BIN | FILE_READ );
  if ( xmlHandle < 0 ) {
    Print( "Can\'t open xml file: ", xmlFileName, ".  The last error is ", GetLastError() );
    return( "" );
  }
  if ( DebugLevel > 0 ) Print( "XML file open must be okay" );

  // Read in the whole XML file
  // Workaround for FileReadString limitation - added by euclid
  tmpData = "";
  while ( StringLen( tmpData ) < NormalizeDouble(FileSize(xmlHandle),0) )
    tmpData = StringConcatenate( tmpData, FileReadString( xmlHandle, FileSize( xmlHandle ) - StringLen( tmpData ) ) );
  //changed to StringConcatenate (string + string is not reliable) - euclid

  if ( StringLen( tmpData ) < NormalizeDouble(FileSize(xmlHandle),0) )
    Print( "Error: can\'t Read in the whole XML file ", GetLastError( ) );

  // Because MT4 build 202 complained about too many files open and MT4 hung. Added by MN
  FileClose( xmlHandle );



  static string OLDxmlFileName = "";
  if( !SaveXmlFiles && AllowWebUpdates )
    if( xmlFileName != OLDxmlFileName &&  OLDxmlFileName != "" ) FileDelete( OLDxmlFileName );
  OLDxmlFileName = xmlFileName;


  return( tmpData );
}

//=================================================================================================
//=================================================================================================
//====================================   GrabWeb Functions   ======================================
//=================================================================================================
//=================================================================================================
// Main Webscraping function
// ~~~~~~~~~~~~~~~~~~~~~~~~~
// bool GrabWeb(string strUrl, string& strWebPage)
// returns the text of any webpage. Returns false on timeout or other error
//
// Parsing functions
// ~~~~~~~~~~~~~~~~~
// string GetData(string strWebPage, int nStart, string strLeftTag, string strRightTag, int& nPos)
// obtains the text between two tags found after nStart, and sets nPos to the end of the second tag
//
// void Goto(string strWebPage, int nStart, string strTag, int& nPos)
// Sets nPos to the end of the first tag found after nStart

bool bWinInetDebug = false;
int  hSession_IEType;
int  hSession_Direct;
int  Internet_Open_Type_Preconfig = 0;
int  Internet_Open_Type_Direct = 1;
int  Internet_Open_Type_Proxy = 3;
int  Buffer_LEN = 80;

#import "wininet.dll"
//Forces the request to be resolved by the origin server, even if a cached copy exists on the proxy.
#define INTERNET_FLAG_PRAGMA_NOCACHE    0x00000100 

//Does not add the returned entity to the cache. 
#define INTERNET_FLAG_NO_CACHE_WRITE    0x04000000 
 
//Forces a download of the requested file, object, or directory listing from the origin server, not from the cache.
#define INTERNET_FLAG_RELOAD            0x80000000 

#define HTTP_QUERY_CONTENT_LENGTH 0x00000005
#define HTTP_QUERY_FLAG_NUMBER    0x20000000
//----
#define INTERNET_OPEN_TYPE_DIRECT       0
#define INTERNET_OPEN_TYPE_PRECONFIG    0x00000000   // use registry configuration

   //int InternetConnectW(int,string,int,string,string,int,int,int);

int InternetOpenW( string sAgent, int lAccessType, string sProxyName, string sProxyBypass, int lFlags );
int InternetOpenUrlW(int 	hInternetSession,string 	sUrl,string 	sHeaders="",int 	lHeadersLength=0,int 	lFlags=0,int 	lContext=0);	
int InternetReadFile(int hFile, uchar &sBuffer[],int lNumBytesToRead,int &lNumberOfBytesRead[]); 
int InternetCloseHandle(int 	hInet);	
#import

int hSession(bool Direct)
   {
	string InternetAgent;
	if (hSession_IEType == 0)
	   {
		InternetAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Q312461)";
		hSession_IEType = InternetOpenW(InternetAgent, Internet_Open_Type_Preconfig, "0", "0", 0);
		hSession_Direct = InternetOpenW(InternetAgent, Internet_Open_Type_Direct, "0", "0", 0);
	   }
	if (Direct) {return(hSession_Direct);}
	else {return(hSession_IEType);}
   }

bool GrabWeb(string strUrl, string& strWebPage)
   {
	uint   	hInternet;
	int		iResult;
	int   	lReturn[]	= {1};
	//string 	sBuffer		= "                                                                                                                                                                                                                                                               ";	// 255 spaces
	uchar sBuffer[1024];
	int   	bytes;
	uint flags=INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD;
	
	hInternet = InternetOpenUrlW(hSession(FALSE), strUrl, NULL, 0,flags);
					
								
	if (bWinInetDebug) Log("hInternet: " + hInternet);   
	if (hInternet == 0) return(false);

	Print("Reading URL: " + strUrl);	   //added by MN	
	iResult = InternetReadFile(hInternet, sBuffer, Buffer_LEN, lReturn);
	
	if (bWinInetDebug) Log("iResult: " + iResult);
	if (bWinInetDebug) Log("lReturn: " + lReturn[0]);
	if (bWinInetDebug) Log("iResult: " + iResult);
	if (bWinInetDebug) Log("sBuffer: " +  CharArrayToString(sBuffer, 0, lReturn[0]));
	if (iResult == 0)  return(false);
	bytes = lReturn[0];
	strWebPage = CharArrayToString(sBuffer, 0, lReturn[0]);
	
	//If there's more data then keep reading it into the buffer
	while (lReturn[0] != 0)
	   {
		iResult = InternetReadFile(hInternet, sBuffer, Buffer_LEN, lReturn);
		if (lReturn[0]==0) break;
		bytes = bytes + lReturn[0];
		strWebPage = strWebPage + CharArrayToString(sBuffer, 0, lReturn[0]);
   	}
	Print("Closing URL web connection");   //added by MN
	iResult = InternetCloseHandle(hInternet);
	if (iResult == 0) return(false);		
	return(true);
   }


//=================================================================================================
//=================================================================================================
//===================================   LogUtils Functions   ======================================
//=================================================================================================
//=================================================================================================

void OpenLog( string strName )
{
  if ( !EnableLogging ) return;

  if ( logHandle <= 0 ) {
    string strMonthPad = "";
    string strDayPad = "";
    if ( Month() < 10 ) strMonthPad = "0";
    if ( Day() < 10 )   strDayPad   = "0";

    string strFilename = StringConcatenate( strName, "_", Year(), strMonthPad, Month(), strDayPad, Day(), "_log.txt" );

    logHandle = FileOpen( strFilename, FILE_CSV | FILE_READ | FILE_WRITE );
    Print( "logHandle =================================== ", logHandle );
  }
  if ( logHandle > 0 ) {
    FileFlush( logHandle );
    FileSeek( logHandle, 0, SEEK_END );
  }
}


void Log( string msg )
{
  if ( !EnableLogging ) return;
  if ( logHandle <= 0 ) return;

  msg = TimeToStr( TimeCurrent(), TIME_DATE | TIME_MINUTES | TIME_SECONDS ) + " " + msg;
  FileWrite( logHandle, msg );
}

//=================================================================================================
//=================================================================================================
//===================================   Timezone Functions   ======================================
//=================================================================================================
//=================================================================================================


#import "kernel32.dll"
int  GetTimeZoneInformation( int& TZInfoArray[] );
#import

#define TIME_ZONE_ID_UNKNOWN   0
#define TIME_ZONE_ID_STANDARD  1
#define TIME_ZONE_ID_DAYLIGHT  2

int TZInfoArray[43];

datetime timeGMT()  //modified by euclid
{
  int ret = GetTimeZoneInformation( TZInfoArray );
  int bias = TZInfoArray[0];
  if ( ret == TIME_ZONE_ID_STANDARD ) bias += TZInfoArray[21];
  if ( ret == TIME_ZONE_ID_DAYLIGHT ) bias += TZInfoArray[42];
  return( TimeLocal() + bias * 60 );
}


//=================================================================================================
//=================================================================================================
//=================================   END IMPORTED FUNCTIONS  =====================================
//=================================================================================================
//=================================================================================================

int ParseXML( string sData )
{
  //Print("Parse XML");
  // Get the currency pair, and split it into the two countries
  string pair = Symbol();
  string cntry1 = StringSubstr( pair, 0, 3 );
  string cntry2 = StringSubstr( pair, 3, 3 );
  if ( DebugLevel > 0 ) Print( "cntry1 = ", cntry1, "    cntry2 = ", cntry2 );
  if ( DebugLevel > 0 ) Log( "Weekly calendar for " + pair + "\n\n" );

  // -------------------------------------------------
  // Parse the XML file looking for an event to report
  // -------------------------------------------------
  int newsIdx = 0;
  int BoEvent = 0;
  int begin, next, end;
  string PrevNewsTime = "";
  while ( newsIdx < EVENTMAX ) {
    BoEvent = StringFind( sData, "<event>", BoEvent );
    if ( BoEvent == -1 ) break;

    BoEvent += 7;
    next = StringFind( sData, "</event>", BoEvent );
    if ( next == -1 ) break;

    string myEvent = StringSubstr( sData, BoEvent, next - BoEvent );
    BoEvent = next;

    begin = 0;
    bool skip = false;
    for ( int i = 0; i < 7; i++ ) {
      mainData[newsIdx][i] = "";
      next = StringFind( myEvent, sTags[i], begin );

      // Within this event, if tag not found, then it must be missing; skip it
      if ( next == -1 )
        continue;
      else {
        // We must have found the sTag okay...
        begin = next + StringLen( sTags[i] );			// Advance past the start tag
        end = StringFind( myEvent, eTags[i], begin );	// Find start of end tag
        if ( end > begin && end != -1 ) {
          // Get data between start and end tag
          //mainData[newsIdx][i] = StringSubstr(myEvent, begin, end - begin);


          // Get data between start and end tag
          mainData[newsIdx][i] = StringSubstr( myEvent, begin, end - begin );
          //check for CDATA tag - added by euclid
          if ( StringSubstr( mainData[newsIdx][i], 0, 9 ) == "<![CDATA[" ) {
            mainData[newsIdx][i] = StringSubstr( mainData[newsIdx][i], 9, StringLen( mainData[newsIdx][i] ) - 12 );
          }
          if ( StringSubstr( mainData[newsIdx][i], 0, 4 ) == "&lt;" ) {
            mainData[newsIdx][i] = "<" + StringSubstr( mainData[newsIdx][i], 4 );
          }
          if ( StringSubstr( mainData[newsIdx][i], 0, 4 ) == "&gt;" ) {
            mainData[newsIdx][i] = ">" + StringSubstr( mainData[newsIdx][i], 4 );
          }

          //also needs to check for HTML entities here... (euclid)
        }
      }
    }

//		for (i=6; i >= 0; i--)
//			Print(sTags[i], "  =  ", mainData[newsIdx][i]);
//Print(sTags[0], "  =  ", mainData[newsIdx][0]," ",newsIdx);

    // = - =   = - =   = - =   = - =   = - =   = - =   = - =   = - =   = - =   = - =
    // Test against filters that define whether we want to
    // skip this particular annoucement
    if ( cntry1 != mainData[newsIdx][COUNTRY] && cntry2 != mainData[newsIdx][COUNTRY] &&
         ( !ReportAllPairs ) )
      skip = true;

    if ( !IncludeHigh && mainData[newsIdx][IMPACT]   == "High" )   skip = true;
    if ( !IncludeMedium && mainData[newsIdx][IMPACT] == "Medium" ) skip = true;
    if ( !IncludeLow && mainData[newsIdx][IMPACT]    == "Low" )    skip = true;
    if ( mainData[newsIdx][IMPACT] == "Holiday" )                  skip = true;

    if ( !IncludeSpeaks && ( StringFind( mainData[newsIdx][TITLE], "speaks" ) != -1 ||
                             StringFind( mainData[newsIdx][TITLE], "Speaks" ) != -1 ) )
      skip = true;
    if ( mainData[newsIdx][TIME] == "All Day" ||
         mainData[newsIdx][TIME] == "Tentative" ||
         mainData[newsIdx][TIME] == "" )
      skip = true;


    if( SkipSameTimeNews && PrevNewsTime == mainData[newsIdx][DATE] + mainData[newsIdx][TIME] )
      skip = true;

    PrevNewsTime = mainData[newsIdx][DATE] + mainData[newsIdx][TIME];

    // = - =   = - =   = - =   = - =   = - =   = - =   = - =   = - =   = - =   = - =

    // If not skipping this event, then log it into the draw buffers
    if ( !skip ) {
      mainDataGMT[newsIdx] = StrToTime( MakeDateTime( mainData[newsIdx][DATE], mainData[newsIdx][TIME] ) );
      Log( "Weekly calendar for " + pair + "\n\n" );
      if ( DebugLevel > 0 ) {
        Log( "FOREX FACTORY\nTitle: " + mainData[newsIdx][TITLE] +
             "\nCountry: " + mainData[newsIdx][COUNTRY] +
             "\nDate: " + mainData[newsIdx][DATE] +
             "\nTime: " + mainData[newsIdx][TIME] +
             "\nImpact: " + mainData[newsIdx][IMPACT] +
             "\nForecast: " + mainData[newsIdx][FORECAST] +
             "\nPrevious: " + mainData[newsIdx][PREVIOUS] + "\n\n" );
      }
      newsIdx++;
    } else {
      // delete skipped data.
      mainData[newsIdx][TITLE] = "";
    }
  }//while

  return( newsIdx );
}



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// These code was  added for DailyFX.com.
//=================================================================================================
int DownloadCSVFile()
{
  string tmpData;
  string file = GetFileName( TimeLocal() );
  sUrl = "http://www.dailyfx.com/files/" + file;
  GrabWeb( sUrl, tmpData ); // Get HTTP Data..


  int Handle = FileOpen( file, FILE_BIN | FILE_WRITE );
  if ( Handle < 0 ) {
    Print( "Can\'t open new  file, the last error is ", GetLastError() );
    return( -2 );
  }

  FileWriteString( Handle, tmpData, StringLen( tmpData ) );
  FileClose( Handle );

  if( StringSubstr( tmpData, 0, 20 ) != "Date,Time,Time Zone," ) {
    Print( "[" + StringSubstr( tmpData, 0, 20 ) + "]" );
    tmpData = "";
    return( -1 );
  } else {
    return( 0 );
  }
}

//=================================================================================================
string GetFileName( datetime tm )
{
  tm = tm - TimeDayOfWeek( tm ) * 24 * 60 * 60;
  string day = TimeDay( tm );
  string month = TimeMonth( tm );
  string year = TimeYear( tm );

  if( StringLen( day ) == 1 ) day = "0" + day;
  if( StringLen( month ) == 1 ) month = "0" + month;
  return( "Calendar-" + month + "-" + day + "-" + year + ".csv" );

}
//=================================================================================================
int ParseCSV()
{
  CurrentGMT = timeGMT();
  string file = GetFileName( TimeLocal() );
  int handle = FileOpen( file, FILE_CSV | FILE_READ, "," );
  if ( handle < 0 ) {
    Print( "Can\'t open ", file, ".. the last error is ", GetLastError() );
    return( 0 );
  }
  int i = 0;
  string dmy;
  // skip first line.
  mainData[i][DATE]     = FileReadString( handle );
  mainData[i][TIME]     = FileReadString( handle );
  dmy                   = FileReadString( handle );
  mainData[i][COUNTRY]  = FileReadString( handle );
  mainData[i][TITLE]    = FileReadString( handle );
  mainData[i][IMPACT]   = FileReadString( handle );
  dmy                   = FileReadString( handle );
  mainData[i][FORECAST] = FileReadString( handle );
  mainData[i][PREVIOUS] = FileReadString( handle );

  i = 0;
  while( !FileIsEnding( handle ) ) {
    //Date	Time	TimeZone	Currency	Event	Importance	Actual	Forecast	Previous

    mainData[i][DATE]     = FileReadString( handle );
    mainData[i][TIME]     = FileReadString( handle );
    dmy                   = FileReadString( handle );
    mainData[i][COUNTRY]  = toUpper( FileReadString( handle ) );
    mainData[i][TITLE]    = DelSymbolHeader( FileReadString( handle ) );
    mainData[i][IMPACT]   = FileReadString( handle );
    dmy                   = FileReadString( handle );
    mainData[i][FORECAST] = FileReadString( handle );
    mainData[i][PREVIOUS] = FileReadString( handle );

    string DateArray[3];
    Explode( mainData[i][DATE], " ", DateArray );
    string t = StringConcatenate( TimeYear( CurrentGMT ), ".", ConvMonth( DateArray[1] ), ".", DateArray[2], " ", mainData[i][TIME] );
    mainDataGMT[i] = StrToTime( t );


    if( StringLen( mainData[i][TITLE] ) > 0 ) i++;
  }
  FileClose( handle );
  return( i );
}
//=================================================================================================
string DelSymbolHeader( string m )
{
  string Sym[] = {"AUD ", "JPY ", "NZD ", "CHF ", "USD ", "EUR ", "GBP ", "CAD ", "CNY "};
  for( int i = 0; i < ArraySize( Sym ); i++ ) {
    if ( StringSubstr( m, 0, 4 ) == Sym[i] ) {
      m = StringSubstr( m, 4 );
      return( m );
    }
  }
  return( m );
}
//=================================================================================================
int ConvMonth( string m )
{
  int mon = 0;
  string Mon[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  for( int i = 0; i < ArraySize( Mon ); i++ )
    if( m == Mon[i] ) return( i + 1 );

}
//=================================================================================================
string toUpper( string s )
{
  int n = StringLen( s );
  for ( int i = 0; i < n; i++ ) {
    int c = StringGetChar( s, i );
    if ( c >= 'a' && c <= 'z' ) {
      c += 'A' - 'a';
      s = StringSetChar( s, i, c );
    }
  }

  return( s );
}
//=================================================================================================
int Explode( string str, string delimiter, string& arr[] )
{
  int i = 0;
  int pos = StringFind( str, delimiter );
  while( pos != -1 ) {
    if( pos == 0 ) arr[i] = "";
    else arr[i] = StringSubstr( str, 0, pos );
    i++;
    str = StringSubstr( str, pos + StringLen( delimiter ) );
    pos = StringFind( str, delimiter );
    if( pos == -1 || str == "" ) break;
  }
  arr[i] = str;

  return( i + 1 );
}
//=================================================================================================