/***************************************************************************
**
**  This file is part of ArrayCore.
**
**  ArrayCore is free software: you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, either version 3 of the License, or
**  (at your option) any later version.
**
**  ArrayCore 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 General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2007-03-23
**  Copyright: 2007-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "ArrayStations.h"

namespace ArrayCore {

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

    Full description of class still missing
  */

  /*!
    Description of constructor still missing
  */
  ArrayStations::ArrayStations()
  {
    TRACE;
    _magneticDeclination=0.0;
  }

  /*!
    Return enclosing rectangle
  */
  Rect ArrayStations::rectangle() const
  {
    TRACE;
    Rect r;
    for(const_iterator it=begin();it!=end();++it) {
      const Point& p=(*it)->coordinates();
      r.add(p);
    }
    return r;
  }

  /*!
    Get array central point
    The center is usefull to use relative coordinates and hence to avoid big number computations

    With array selection, it is not necessarily the exact center of the array, but it does not
    matter, the big number (e.g. from UTM) have bee avoided.
  */
  void ArrayStations::setReference()
  {
    TRACE;
    int n=count();
    _reference=Point2D();
    for(int i=0; i<n; i++) {
      _reference+=at(i)->coordinates();
    }
    if(n>1) {
      _reference/=static_cast<double>(n);
    }
    _relativePos.resize(n); // ensure compatible but empty relative coordinates
    App::log(tr("Relative coordinates with reference at (%1).\n").arg(_reference.toString(2, 'f')));
  }

  /*!
    Geopsy uses UTM coordinates because historically it has been organized around local metric
    coordinates. For 3 component array processing, components are aligned to magnetic north. In
    most areas it is close to geographical north. UTM north may be in some cases far from
    geographical north. Hence, to compute relative coordinates, UTM are transformed to degrees and
    then local metric coordinates.

    Magnetic declination can be for instance calculated at
    https://www.ngdc.noaa.gov/geomag-web/#declination

    gpcoord has an option to get the value from a set of coordinates.

    \a magneticDeclination is in degrees
  */
  void ArrayStations::setRelativePos(double magneticDeclination)
  {
    TRACE;
    _magneticDeclination=magneticDeclination;
    int n=count();
    _relativePos.resize(n);
    bool ok=false;
    _utmZone=utmZone(ok);
    for(int i=0; i<n; i++) {
      _relativePos[i]=toRelative(at(i)->coordinates());
    }
  }

  /*!
    Can be used to get the relative coordinates of any point like a point source.
    \a ref must in geohraphical coordinates if \a utm is valid.
  */
  Point2D ArrayStations::toRelative(Point2D p) const
  {
    if(_utmZone.isValid()) {
      Point2D ref=_reference;
      ref.utmToGeographical(_utmZone);
      p.utmToGeographical(_utmZone);
      p.geographicalToRectangular(ref);
      if(_magneticDeclination!=0.0) {
        Angle a;
        a.setDegrees(-_magneticDeclination);
        p.rotate(a);
      }
    } else {
      p-=_reference;
    }
    return p;
  }

  double ArrayStations::radius() const
  {
    TRACE;
    double r=0;
    for(const_iterator it=begin(); it!=end(); ++it) {
      double d=_reference.distanceTo((*it)->coordinates());
      if(d>r) {
        r=d;
      }
    }
    return r;
  }

  /*!
    \a ok is set to false for empty arrays and if stations belongs
    to more than one UTM zone.

    \a ok can be true and the returned zone can be invalid (e.g. not UTM coordiantes,
    but local metric coordinates).
  */
  UtmZone ArrayStations::utmZone(bool &ok) const
  {
    TRACE;
    UtmZone z;
    const_iterator it=begin();
    if(it==end()) {  // empty array
      ok=false;
      return z;
    }
    z=(*it)->utmZone();
    for(it++; it!=end(); it++) {
      if((*it)->utmZone()!=z) {
        App::log(tr("Station '%1' is not in UTM zone '%2'\n")
                         .arg((*it)->name())
                         .arg(z.toString()));
        ok=false;
        return UtmZone();
      }
    }
    return z;
  }

  ArrayStations::Mode ArrayStations::mode() const
  {
    TRACE;
    StationSignals::Components comps=components();
    if(comps & StationSignals::VerticalComponent) {
      if(comps & StationSignals::HorizontalComponent) {
        return ThreeComponents;
      }
    } else if(comps & StationSignals::HorizontalComponent) {
      return Horizontal;
    }
    return Vertical;
  }

  QString ArrayStations::toString(int precision, char format) const
  {
    TRACE;
    QString s;
    int n=count();
    for(int i=0; i<n; i++) {
      const StationSignals& stat=*at(i);
      s+=stat.name();
      s+=" ";
      s+=stat.coordinates().toString(precision, format);
      s+="\n";
    }
    return s;
  }

} // namespace ArrayCore
