    // @(#)root/netx:$Name:  $:$Id: TXUrl.cxx,v 1.5 2004/12/16 19:23:18 rdm Exp $
    // Author: Alvise Dorigo, Fabrizio Furano

    /*************************************************************************
     * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
     * All rights reserved.                                                  *
     *                                                                       *
     * For the licensing terms see $ROOTSYS/LICENSE.                         *
     * For the list of contributors see $ROOTSYS/README/CREDITS.             *
     *************************************************************************/

    #include "TXUrl.h"
    #include "TXDebug.h"
    #include "TString.h"
    #include "TError.h"
    #include "TSystem.h"
    #include "TInetAddress.h"

    #include <ctype.h>               // needed by isdigit()
    #ifndef R__WIN32
    #include <netdb.h>
    #endif

    using namespace std;

    ClassImp(TXUrl);

    //_____________________________________________________________________________
     TXUrl::TXUrl(TString Urls) : fIsValid(kTRUE)
    {
       // A container for multiple urls.
       // It creates an array of multiple urls parsing the argument Urls and
       //  resolving the DNS aliases
       //
       // Urls MUST be in the form:
       //
       //    root://[username1@]server1:port1[,[username2@]server2:port2, ... ,
       //           [usernameN@]serverN:portN]/pathfile
       //
       // Using the method GetNextUrl() the user can obtain the next TUrl object pointer in the array
       // (the array is cyclic).
       // Using the method GetARandomUrl() the user can obtain a random TUrl from the array

       UrlArray urlArray;
       TString listOfMachines;
       TString protoToMatch(PROTO);

       protoToMatch += "://";

       // Init of the random number generator with the internal clock as a seed
       fRndgen.SetSeed(gSystem->GetPid());

       //
       // We assume the protol is "root://", because this
       // must be the protocol for TXNetFile
       //

       if ( !Urls.BeginsWith(protoToMatch) ) {
          Error("TXUrl", "This is not a %s protocol.", protoToMatch.Data() );
          fIsValid = kFALSE;
       } else
          // remove leading 'root://' from the string
          // Now sUrls will contain [user1@]machine1[:port1],...,
          //                        [userN]machineN[:portN]/pathfile
          Urls.Remove( 0, protoToMatch.Length() );

       //
       // Save the list of comma separated servers:ports, assuming they are
       // separated by a '/' char from the pathfile
       //
       listOfMachines = Urls;

       Short_t slashPos = (Short_t)Urls.First('/');
       if( slashPos != kNPOS) {
          listOfMachines.Remove(slashPos);
       }

       // remove trailing "," that would introduce a null host
       while (listOfMachines.EndsWith(","))
          listOfMachines.Remove(listOfMachines.Length()-1);

       // remove leading "," that would introduce a null host
       while (listOfMachines.BeginsWith(","))
          listOfMachines.Remove(0,1);

       if(DebugLevel() >= kUSERDEBUG)
          Info("TXUrl", "List of servers to connect to is [%s]",
                        listOfMachines.Data());

       //
       // Set fPathName
       //
       fPathName = "";

       if( slashPos != kNPOS) {
          fPathName = Urls;
          fPathName.Remove(0, slashPos);
       }

       // If at this point we have a strange pathfile, then it's bad
       if ( fPathName.CompareTo("/") == 0 ) {
          Error("TXUrl", "Malformed pathfile %s", fPathName.Data());
          fIsValid = kFALSE;
       }

       if(DebugLevel() >= kHIDEBUG)
          Info("TXUrl", "Remote file to open is [%s]", fPathName.Data());

       if (fIsValid) {
          ConvertDNSAliases(fUrlArray, listOfMachines, fPathName);

          if (fUrlArray.size() <= 0)
    	 fIsValid = kFALSE;

          if(DebugLevel() >= kUSERDEBUG)
    	 ShowUrls();
       }

    }

    //_____________________________________________________________________________
     TXUrl::~TXUrl()
    {
       fTmpUrlArray.clear();

       for( UShort_t i=0; i < fUrlArray.size(); i++)
          SafeDelete( fUrlArray[i] );

       fUrlArray.clear();
    }

    //_____________________________________________________________________________
     TUrl *TXUrl::GetNextUrl()
    {
       // Returns the next TUrl object pointer in the array.
       // After the last object is returned, the array is rewind-ed.
       // Now implemented as a pick from the tmpUrlArray queue

       TUrl *retval;

       if ( !fTmpUrlArray.size() ) Rewind();

       retval = fTmpUrlArray.back();

       fTmpUrlArray.pop_back();

       return retval;
    }

    //_____________________________________________________________________________
     void TXUrl::Rewind()
    {
       // Rebuilds tmpUrlArray, i..e the urls that have to be picked
       fTmpUrlArray.clear();

       for( UShort_t i=0; i <= fUrlArray.size()-1; i++)
          fTmpUrlArray.push_back( fUrlArray[i] );
    }

    //_____________________________________________________________________________
     TUrl *TXUrl::GetARandomUrl()
    {
       TUrl *retval;
       UInt_t rnd;

       for (int i=0; i < 10; i++)
          rnd = fRndgen.Integer(fTmpUrlArray.size());

       // Returns a random url from the ones that have to be picked
       // When all the urls have been picked, we restart from the full url set

       if ( !fTmpUrlArray.size() ) Rewind();

       UrlArray::iterator it = fTmpUrlArray.begin() + rnd;

       retval = *it;
       fTmpUrlArray.erase(it);

       return retval;
    }

    //_____________________________________________________________________________
     void TXUrl::ShowUrls()
    {
       // Prints the list of urls

       Info("ShowUrls", "The converted URLs count is %d.", fUrlArray.size());

       for(UInt_t i=0; i < fUrlArray.size(); i++)
          Info("ShowUrls", "URL n.%d: %s .", i+1, fUrlArray[i]->GetUrl());

    }

    //_____________________________________________________________________________
     void TXUrl::CheckPort(TString &machine)
    {
       // Checks the validity of port in the given host[:port]
       // Eventually completes the port if specified in the services file
       Short_t commaPos = machine.First(':');

       if(commaPos == kNPOS) {
          // Port not specified

          if(DebugLevel() >= kHIDEBUG)
    	 Warning("checkPort",
    		 "TCP port not specified for host %s. Trying to get it from /etc/services...", machine.Data());

          Int_t prt = gSystem->GetServiceByName("rootd");

          if(prt <= 0) {
    	 if(DebugLevel() >= kHIDEBUG)
    	    Warning("checkPort", "Service %s not specified in /etc/services; using default IANA tcp port 1094", PROTO);
    	 machine += ":1094";
          } else {
             if (DebugLevel() >= kHIDEBUG)
         	    Info("checkPort", "Found tcp port %d in /etc/service", prt);

    	 machine += ":";
    	 machine += prt;
          }

       } else {
          // The port seems there

          TString tmp(machine);
          tmp.Remove(0, commaPos+1);

          TString sPort = tmp;

          if(sPort.CompareTo("") == 0)
    	 Error("checkPort","The specified tcp port is 0 for %s", machine.Data());
          else {
    	 for(Short_t i=0; i<=sPort.Length()-1; i++)
    	    if(isdigit(sPort[i]) == 0) {
    	       Error("checkPort","The specified tcp port is not numeric for %s", machine.Data());
    	    }
          }
       }
    }

    //_____________________________________________________________________________
     Bool_t TXUrl::ConvertSingleDNSAlias(UrlArray& urls, TString hostname,
                                                        TString fname)
    {
       // Converts a single host[:port] into an array of TUrls.
       // The new Turls are appended to the given UrlArray.

       Bool_t specifiedPort;
       Int_t port = 0;

       TString tmpaddr;

       specifiedPort = ( hostname.First(':') != kNPOS );

       hostname.Append("/fakefile");
       TUrl tmp(hostname.Data());

       if(specifiedPort) {
          port = tmp.GetPort();
          if(DebugLevel() >= kHIDEBUG)
    	 Info("ConvertSingleDNSAlias","Resolving %s:%d.", tmp.GetHost(), port);
       } else
          if(DebugLevel() >= kHIDEBUG)
    	 Info("ConvertSingleDNSAlias","Resolving %s.", tmp.GetHost());

       TInetAddress iaddr = gSystem->GetHostByName(tmp.GetHost());

       if(!iaddr.IsValid()) {
    #ifndef R__WIN32
          Error("ConvertSingleDNSAlias","GetHostByName error for host %s (%s)",
    	    tmp.GetHost(), hstrerror(h_errno));
    #else
          Error("ConvertSingleDNSAlias","GetHostByName error for host %s",
    	    tmp.GetHost());
    #endif
          return kFALSE;
       }

       // Let's get the list of the addresses for the host
       TInetAddress::AddressList_t::const_iterator j =
                                        iaddr.GetAddresses().begin();
       for ( ; j != iaddr.GetAddresses().end(); j++) {

          const char *addr = TInetAddress::GetHostAddress(*j);
          TInetAddress c = gSystem->GetHostByName(addr);

          // Set the user for the child urls
          tmpaddr = "";
          if ( strlen(tmp.GetUser()) ) {
    	 tmpaddr = tmp.GetUser();
    	 tmpaddr += "@";
          }

          if (!strcmp(c.GetHostName(), "UnNamedHost"))
    	 tmpaddr += addr;
          else
    	 tmpaddr += c.GetHostName();

          if(DebugLevel() >= kHIDEBUG)
    	 Info("ConvertSingleDNSAlias","Found host %s", tmpaddr.Data() );

          if(specifiedPort) {
    	 char Port[7];
    	 memset((void *)Port, 0, 7);
    	 sprintf((char *)Port, "%d", port);
    	 tmpaddr.Append(':');
    	 tmpaddr.Append(Port);
          }

          if (fname.Length())
    	 tmpaddr.Append(fname);

          urls.push_back( new TUrl(tmpaddr.Data()) );
       }

       return (urls.size() > 0);
    }

    //_____________________________________________________________________________
     void TXUrl::ConvertDNSAliases(UrlArray& urls, TString list, TString fname)
    {
       // Given a list of comma-separated host[:port]
       // every entry is resolved via DNS into its aliases
       // The parameter is overwritten with the processed data
       Short_t colonPos;

       list += ",";

       while(list.Length() > 0) {
          colonPos = list.First(',');
          if (colonPos != kNPOS) {
    	 TString tmp(list);

    	 tmp.Remove(colonPos);
    	 list.Remove(0,colonPos+1);

    	 CheckPort(tmp);
    	 ConvertSingleDNSAlias(urls, tmp, fname);
          }

       }

    }