/***************************************************************************
**
**  This file is part of QGpCoreMath.
**
**  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: 2016-08-30
**  Copyright: 2016-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "Point2D.h"
#include "UtmZone.h"

namespace QGpCoreMath {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  UtmZone::UtmZone(const Point2D& longLat)
  {
    TRACE;
    if(longLat.y()>=-90.0 && longLat.y()<=90.0) {
      _x=qFloor((longLat.x()+180.0)/6.0)+1;
      _y=qFloor((longLat.y()+80.0)/8.0)+1;
      // Norvegian exceptions...
      switch(_y) {
      case -1: // 'A'
        _y=0;
        break;
      case 18: // 'V'
        switch(_x) {
        case 31:
          if(longLat.x()>3.0) {
            _x=32; // Move from 31V to 32V which is 9 degrees wide instead of 6 degrees
          }
          break;
        default:
          break;
        }
        break;
      case 20: // 'X'
        switch(_x) {
        case 32:
          if(longLat.x()<9.0) {
            _x=31;
          } else {
            _x=33;
          }
          break;
        case 34:
          if(longLat.x()<21.0) {
            _x=33;
          } else {
            _x=35;
          }
          break;
        case 36:
          if(longLat.x()<33.0) {
            _x=35;
          } else {
            _x=37;
          }
          break;
        default:
          break;
        }
        break;
      case 21: // 'X' up to 84 deg.
        if(longLat.y()<=84.0) {
          _y=20;
        } // else 'Z'
        break;
      case 22: // 'Z'
        _y=21;
        break;
      default:
        break;
      }
    }
    check();
  }

  void UtmZone::check()
  {
    TRACE;
    if(_x<1 || _x>60 || _y<0 || _y>21) {
      _x=-1;
      _y=-1;
    }
    // Check Norvegian exceptions (unused zones)
    switch(_y) {
    case 20:
      switch(_x) {
      case 32:
      case 34:
      case 36:
        _x=-1;
        _y=-1;
        break;
      default:
        break;
      }
      break;
    default:
      break;
    }
  }

  /*!
    Check that \a p is really inside this zone with \a tolerance (in m).
  */
  bool UtmZone::isValid(const Point2D& p, double tolerance) const
  {
    TRACE;
    if(!isValid()) {
      return false;
    }
    Point2D np=p;
    np.utmToGeographical(*this);
    if(*this==UtmZone(np)) {
      return true;
    } else {
      np=p;
      np+=Point2D(tolerance, tolerance);
      if(*this==UtmZone(np)) {
        return true;
      } else {
        np=p;
        np-=Point2D(tolerance, tolerance);
        return *this==UtmZone(np);
      }
    }
  }

  /*!
    Return UTM zone name (3 characters).
  */
  QString UtmZone::toString() const
  {
    QString zone;
    if(isValid()) {
      zone=QString::number(_x);
      switch(_y) {
      case 0:
        zone+='A';
        break;
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
        zone+=QChar(_y-1+'C');
        break;
      case 7:
      case 8:
      case 9:
      case 10:
      case 11:
        zone+=QChar(_y-7+'J');
        break;
      case 12:
      case 13:
      case 14:
      case 15:
      case 16:
      case 17:
      case 18:
      case 19:
      case 20:
        zone+=QChar(_y-12+'P');
        break;
      case 21:
        zone+="Z";
        break;
      default:
        zone=QString();
        break;
      }
    } else {
      zone="###";
    }
    return zone;
  }

  /*!
    Set UTM zone from a UTM zone name (3 characters).
  */
  void UtmZone::fromString(const QString& zone)
  {
    if(zone.isEmpty()) {
      *this=UtmZone();
      return;
    }
    char l=zone[zone.count()-1].toUpper().toLatin1();
    switch(l) {
    case 'A':
      _y=0;
      break;
    case 'C':  // 1
    case 'D':
    case 'E':
    case 'F':
    case 'G':
    case 'H': // 6
      _y=l-'C'+1;
      break;
    case 'J': // 7
    case 'K':
    case 'L':
    case 'M':
    case 'N': // 11
      _y=l-'J'+7;
      break;
    case 'P': // 12
    case 'Q':
    case 'R':
    case 'S':
    case 'T':
    case 'U':
    case 'V':
    case 'W':
    case 'X': // 20
      _y=l-'P'+12;
      break;
    case 'Z':
      _y=21;
      break;
    default:
      *this=UtmZone();
      return;
    }
    bool ok;
    _x=zone.left(zone.count()-1).toInt(&ok);
    if(!ok || _x<1 || _x>60) {
      *this=UtmZone();
    }
  }

  /*!
    Returns the longitude, latitude reference of UTM zone.
  */
  Point2D UtmZone::reference() const
  {
    Point2D ref;
    if(!isValid()) {
      return ref;
    }
    ref.setX(_x*6.0-183.0);
    switch(_y) {
    case 19: // 'X'
      ref.setY(78.0); // Latitude band 'X' extends to 84 (covering the northernmost land)
      break;
    default:
      ref.setY((_y << 3)-76.0);
    }
    APP_LOG(1, tr("Reference for UTM zone %1 (%2, %3): %4\n")
            .arg(toString())
            .arg(_x).arg(_y)
            .arg(ref.toString()));
    return ref;
  }

  QString UtmZone::parsingErrorMessage(const QString& badUtmZone)
  {
    TRACE;
    return tr("bad utm zone '%1', expected format [1-60][A-Z]").arg(badUtmZone);
  }

  QStringList UtmZone::names()
  {
    TRACE;
    QStringList l;
    l << "###";
    UtmZone z;
    for(int x=1; x<=60; x++) {
      for(int y=0; y<=21; y++) {
        z._x=x;
        z._y=y;
        z.check();
        if(z.isValid()) {
          l << z.toString();
        }
      }
    }
    return l;
  }

} // namespace QGpCoreMath

