/***************************************************************************
**
**  This file is part of QGpGuiTools.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file is distributed in the hope that it will be useful, but WITHOUT
**  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
**  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2017-10-21
**  Copyright: 2017-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include <QtNetwork>
#include <QGpCoreTools.h>

#include "HttpProxyObject.h"

namespace QGpGuiTools {

  /*!
    \class HttpProxyObject HttpProxyObject.h
    \brief Brief description of class still missing

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  HttpProxyObject::HttpProxyObject(QObject * parent)
    : QObject(parent)
  {
    TRACE;
  }

  /*!
    Description of destructor still missing
  */
  HttpProxyObject::~HttpProxyObject()
  {
    TRACE;
  }

  /*!
    isPlainHostName(host)

          * host the hostname from the URL (excluding port number).
          * True if there is no domain name in the hostname (no dots).

      isPlainHostName("www")
        is true.
      isPlainHostName("www.example.com")
        is false.
  */
  bool HttpProxyObject::isPlainHostName(QString host)
  {
    return host.contains(".");
  }

  /*!
    dnsDomainIs(host, domain)

          * host is the hostname from the URL
          * domain is the domain name to test the hostname against.
          * Returns true if the domain of hostname matches

      dnsDomainIs("www.example.com", ".example.com")
          is true.
      dnsDomainIs("www", ".example.com")
          is false.
      dnsDomainIs("www.tcom.com", ".example.com")
          is false.
  */
  bool HttpProxyObject::dnsDomainIs(QString host, QString domain)
  {
    return host.endsWith(domain);
  }

  /*!
    localHostOrDomainIs(host, hostdom)

          * host the hostname from the URL.
          * hostdom fully qualified hostname to match against.
          * Is true if the hostname matches exactly the specified hostname, or if there is
            no domain name part in the hostname, but the unqualified hostname matches.

      localHostOrDomainIs("www.example.com", "www.example.com")
          is true (exact match).
      localHostOrDomainIs("www", "www.example.com")
          is true (hostname match, domain not specified).
      localHostOrDomainIs("www.mcom.com", "www.example.com")
          is false (domain name mismatch).
      localHostOrDomainIs("home.example.com", "www.example.com")
          is false (hostname mismatch).
  */
  bool HttpProxyObject::localHostOrDomainIs(QString host, QString hostDom)
  {
    if(host==hostDom) {
      return true;
    }
    QString unHost=hostDom.section(".",0,0);
    return (host==unHost);
  }


  /*!
    isResolvable(host)

          * host is the hostname from the URL.
          * Tries to resolve the hostname. Returns true if succeeds.

      isResolvable("www.example.com")
          is true (unless DNS fails to resolve it due to a firewall or some other reason).
      isResolvable("bogus.domain.foobar")
          is false.
  */
  bool HttpProxyObject::isResolvable(QString host)
  {
    QHostInfo info=QHostInfo::fromName(host);
    return (info.error()==QHostInfo::NoError);
  }

  /*!
    isInNet(host, pattern, mask)

          * host a DNS hostname, or IP address. If a hostname is passed, it will be
            resolved into an IP address by this function.
          * pattern an IP address pattern in the dot-separated format
          * mask for the IP address pattern informing which parts of the IP address
            should be matched against. 0 means ignore, 255 means match.
          * True if the IP address of the host matches the specified IP address pattern.
            Pattern and mask specification is done the same way as for SOCKS configuration.

      isInNet(host, "192.168.249.79", "255.255.255.255")
          is true if the IP address of host matches exactly 192.168.249.79.
      isInNet(host, "192.168.0.0", "255.255.0.0")
          is true if the IP address of the host matches 192.168.*.*.
  */
  bool HttpProxyObject::isInNet(QString host, QString pattern, QString mask)
  {
    // Resolve host name if not an ip number
    QHostInfo info=QHostInfo::fromName(host);
    QList<QHostAddress> ips=info.addresses();
    if(info.error()!=QHostInfo::NoError || ips.isEmpty()) {
      return false;
    }
    QHostAddress hostIp=ips.first();
    QHostAddress subnet(pattern);
    int a=0;
    a+=mask.section(".", 0, 0).toInt() << 24;
    a+=mask.section(".", 1, 1).toInt() << 16;
    a+=mask.section(".", 2, 2).toInt() << 8;
    a+=mask.section(".", 3, 3).toInt();
    int n=0;
    int b=1;
    while(!(a & b)) {
      b=b << 1;
      n++;
    }
    return hostIp.isInSubnet(subnet, n);
  }

  /*!
    dnsResolve(host)

          * host hostname to resolve
          * Resolves the given DNS hostname into an IP address, and returns it in the dot
            separated format as a string.

      dnsResolve("home.example.com")
          returns the string "192.168.249.79".
  */
  QString HttpProxyObject::dnsResolve(QString host)
  {
    QHostInfo info=QHostInfo::fromName(host);
    QList<QHostAddress> ips=info.addresses();
    if(info.error()==QHostInfo::NoError && !ips.isEmpty()) {
      return ips.first().toString();
    } else {
      return "127.0.0.1";
    }
  }


  /*!
    myIpAddress()

          * Returns the IP address of the host that the Navigator is running on, as a string in
            the dot-separated integer format.

      myIpAddress()
          would return the string "192.168.249.79" if you were running the Navigator on that host.
  */
  QString HttpProxyObject::myIpAddress()
  {
    QList<QHostAddress> ips=QNetworkInterface::allAddresses();
    if(ips.count()>0) {
      return ips.first().toString();
    } else {
      return "127.0.0.1";
    }
  }


  /*!
    dnsDomainLevels(host)

          * host is the hostname from the URL.
          * Returns the number (integer) of DNS domain levels (number of dots) in the hostname.

      dnsDomainLevels("www")
          returns 0.
      dnsDomainLevels("www.example.com")
          returns 2.
  */
  int HttpProxyObject::dnsDomainLevels(QString host)
  {
    return host.count(".");
  }

  /*!
    shExpMatch(str, shexp)

          * str is any string to compare (e.g. the URL, or the hostname).
          * shexp is a shell expression to compare against.
          * Returns true if the string matches the specified shell expression.

      shExpMatch("http://home.example.com/people/ari/index.html", "*e/ari/i*")
          is true.
      shExpMatch("http://home.example.com/people/montulli/index.html", "*e/ari/i*")
          is false.
  */
  bool HttpProxyObject::shExpMatch(QString str, QString shexp)
  {
    return str.contains(QRegExp(shexp, Qt::CaseInsensitive, QRegExp::Wildcard));
  }

  static const QString days[]={"MON", "TUE", "WED", "THU", "FRI", "SAT", "SUN"};
  static const QString months[]={"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};

  bool HttpProxyObject::inWeekdayRange(int day, const QString& wd1, const QString& wd2)
  {
    int i1=0;
    while(i1<7 && wd1!=days[i1]) {
      i1++;
    }
    if(i1==7) {
      return false;
    }
    int i2=0;
    while(i2<7 && wd2!=days[i2]) {
      i2++;
    }
    if(i2==7) {
      return false;
    }
    if(i1>i2) {
      qSwap(i1, i2);
    }
    return i1<=day && day<=i2;
  }

  int HttpProxyObject::monthIndex(const QString& mon)
  {
    int i=0;
    while(i<12) {
      if(mon==months[i]) {
        return i;
      }
      i++;
    }
    return -1;
  }

  /*!
    weekdayRange(wd1, wd2, gmt)

          * wd1 and wd2 are one of the weekday strings: SUN MON TUE WED THU FRI SAT
          * gmt is either the string: GMT or is left out.
          * Only the first parameter is mandatory. Either the second, the third, or both may be left out.
            If only one parameter is present, the function yields a true value on the weekday that the
            parameter represents. If the string "GMT" is specified as a second parameter, times are taken
            to be in GMT, otherwise in local timezone. If both wd1 and wd1 are defined, the condition is
            true if the current weekday is in between those two weekdays. Bounds are inclusive. If the
            "GMT" parameter is specified, times are taken to be in GMT, otherwise the local timezone is used.

      weekdayRange("MON", "FRI")
          true Monday through Friday (local timezone).
      weekdayRange("MON", "FRI", "GMT")
          same as above, but GMT timezone.
      weekdayRange("SAT")
          true on Saturdays local time.
      weekdayRange("SAT", "GMT")
          true on Saturdays GMT time.
      weekdayRange("FRI", "MON")
          true Friday through Monday (note, order does matter!).
  */
  bool HttpProxyObject::weekdayRange(QString wd1, QString wd2, QString gmt)
  {
    QDateTime d=QDateTime::currentDateTime();  // local time
    if(gmt=="GMT" || (gmt.isNull() && wd2=="GMT")) {
      d=d.toUTC();
    }
    if(wd2.isNull() || wd2=="GMT") {
      return (wd1==days[d.date().dayOfWeek()-1]);
    } else {
      return (inWeekdayRange(d.date().dayOfWeek()-1, wd1, wd2));
    }
  }

  /*!
    dateRange(day1, month1, day2, month2)

          * day is the day of month between 1 and 31 (as an integer).
          * month is one of the month strings: JAN FEB MAR APR MAY JUN JUL AUG SEP OCT NOV DEC
          * year is the full year number, for example 1995 (but not 95). Integer.
          * gmt is either the string "GMT", which makes time comparison occur in GMT timezone;
            if left unspecified, times are taken to be in the local timezone. Even though the
            above examples don't show, the "GMT" parameter can be specified in any of the 9 different
            call profiles, always as the last parameter.
          * If only a single value is specified (from each category: day, month, year), the function
            returns a true value only on days that match that specification. If both values are specified,
            the result is true between those times, including bounds.

      dateRange(1)
          true on the first day of each month, local timezone.
      dateRange(1, "GMT")
          true on the first day of each month, GMT timezone.
      dateRange(1, 15)
          true on the first half of each month.
      dateRange(24, "DEC")
          true on 24th of December each year.
      dateRange(24, "DEC", 1995)
          true on 24th of December, 1995.
      dateRange("JAN", "MAR")
          true on the first quarter of the year.
      dateRange(1, "JUN", 15, "AUG")
          true from June 1st until August 15th, each year (including June 1st and August 15th).
      dateRange(1, "JUN", 15, 1995, "AUG", 1995)
          true from June 1st, 1995, until August 15th, same year.
      dateRange("OCT", 1995, "MAR", 1996)
          true from October 1995 until March 1996 (including the entire month of October 1995 and March 1996).
      dateRange(1995)
          true during the entire year 1995.
      dateRange(1995, 1997)
          true from beginning of year 1995 until the end of year 1997.
      > dateRange(day)
      > dateRange(day1, day2)
      > dateRange(mon)
      > dateRange(month1, month2)
      > dateRange(year)
      > dateRange(year1, year2)
      > dateRange(day1, month1, day2, month2)
      > dateRange(month1, year1, month2, year2)
      > dateRange(day1, month1, year1, day2, month2, year2)
      > dateRange(day1, month1, year1, day2, month2, year2, gmt)
      > [...]
      > Even though the above examples don't show, the "GMT"
      > parameter can be specified in any of the 9 (sic!)
      > different call profiles, always as the last
      > parameter
  */
  bool HttpProxyObject::dateRange(QString day1, QString month1, QString year1, QString day2, QString month2, QString year2, QString gmt)
  {
    QDateTime d=QDateTime::currentDateTime();  // local time
    QStringList args;
    args << day1;
    if(!month1.isNull()) {
      args << month1;
      if(!year1.isNull()) {
        args << year1;
        if(!day2.isNull()) {
          args << day2;
          if(!month2.isNull()) {
            args << month2;
            if(!year2.isNull()) {
              args << year2;
              if(!gmt.isNull()) {
                args << gmt;
              }
            }
          }
        }
      }
    }
    if(args.last()=="GMT") {
      d=d.toUTC();
      args.removeLast();
    }
    bool ok;
    switch(args.count()) {
    case 1: {
        int v=args[0].toInt(&ok);
        if(ok) {
          if(v>0 && v<32) {
            return (v==d.date().day());
          } else {
            return (v==d.date().year());
          }
        } else {
          return (args[0]==months[d.date().month()-1]);
        }
      }
      break;
    case 2:
    case 4:
    case 6: {
        int nArgs2=args.count() >> 1;
        int day, month, year;
        day=1;
        month=1;
        year=d.date().year();
        for(int i=0; i<nArgs2; i++) {
          int v=args[i].toInt(&ok);
          if(ok) {
            if(v>0 && v<32) {
              if(args.count()==2) {
                month=d.date().month();
              }
              day=v;
            } else {
              year=v;
            }
          } else {
            month=monthIndex(args[i])+1;
          }
        }
        QDate d1(year, month, day);
        day=31;
        month=12;
        year=d.date().year();
        for(int i=nArgs2; i<args.count(); i++) {
          int v=args[i].toInt(&ok);
          if(ok) {
            if(v>0 && v<32) {
              if(args.count()==2) {
                month=d.date().month();
              }
              day=v;
            } else {
              year=v;
            }
          } else {
            month=monthIndex(args[i])+1;
          }
        }
        QDate d2(year, month, day);
        return (d1<=d.date() && d.date()<=d2);
      }
    default:
      break;
    }
    return false;
  }

  /*!
    timeRange(hour1, hour2)

          * hour is the hour from 0 to 23. (0 is midnight, 23 is 11 pm.)
          * min minutes from 0 to 59.
          * sec seconds from 0 to 59.

      timerange(12)
          true from noon to 1pm.
      timerange(12, 13)
          same as above.
      timerange(12, "GMT")
          true from noon to 1pm, in GMT timezone.
      timerange(9, 17)
          true from 9am to 5pm.
      timerange(8, 30, 17, 00)
          true from 8:30am to 5:00pm.
      timerange(0, 0, 0, 0, 0, 30)
          true between midnight and 30 seconds past midnight.
  */
  bool HttpProxyObject::timeRange(QString hour1, QString min1, QString sec1, QString hour2, QString min2, QString sec2, QString gmt)
  {
    QDateTime d=QDateTime::currentDateTime();  // local time
    QStringList args;
    args << hour1;
    if(!min1.isNull()) {
      args << min1;
      if(!sec1.isNull()) {
        args << sec1;
        if(!hour2.isNull()) {
          args << hour2;
          if(!min2.isNull()) {
            args << min2;
            if(!sec2.isNull()) {
              args << sec2;
              if(!gmt.isNull()) {
                args << gmt;
              }
            }
          }
        }
      }
    }
    if(args.last()=="GMT") {
      d=d.toUTC();
      args.removeLast();
    }
    switch(args.count()) {
    case 1:
      return (d.time().hour()==args[0].toInt());
    case 2: {
        int h=d.time().hour();
        return (h>=args[0].toInt() && h<=args[1].toInt());
      }
    case 4: {
        QTime t1(args[0].toInt(), args[1].toInt(),0);
        QTime t2(args[2].toInt(), args[3].toInt(),0);
        return (d.time()>=t1 && d.time()<=t2);
      }
    case 6: {
        QTime t1(args[0].toInt(), args[1].toInt(), args[2].toInt());
        QTime t2(args[3].toInt(), args[4].toInt(), args[5].toInt());
        return (d.time()>=t1 && d.time()<=t2);
      }
    default:
      return false;
    }
  }

} // namespace QGpGuiTools

