/***************************************************************************
**
**  This file is part of GeopsyCore.
**
**  GeopsyCore 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.
**
**  GeopsyCore 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: 2009-08-26
**  Copyright: 2009-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <math.h>

#include <QGpCoreTools.h>
#include "SEGYTraceHeader.h"

namespace GeopsyCore {

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

    Format of SEGY trace header (same as the SU trace header)
    The following offsets are read from SEG Y rev 1 (2002)
                    SEG Technical Standards Committee

    This document is available at:
      http://www.seg.org/SEGportalWEBproject/prod/SEG-Publications/Pub-Technical-Standards/Documents/seg_y_rev1.pdf
  */

  SEGYTraceHeader::SEGYTraceHeader()
  {
    TRACE;
    ASSERT(sizeof(SEGYTraceHeader)==240);
    memset(this, 0, 240);
  }

  /*!
    Conversion from IBM floats to IEEE double precision.

    See http://en.wikipedia.org/wiki/IBM_Floating_Point_Architecture for IBM structure.
  */
  double SEGYTraceHeader::IBMFloat2ieee(qint32 val)
  {
    TRACE;
    //val=0xC276A000; // test: -118.625 with IBM floats (see wiki)
    /*
      2^(-24)*16^(t-64)=2^(-24)*2^(4t-256)=2^(4t-280)
      -24 is to set fmant as a fraction
      -256 for ibm float bias
      +1023 to introduce double bias
      (-280+1023)*16=0x2E70
      16 is a 4-bit shift, from short to 12 bit representation of double exp
      0x7f00 & *(reinterpret_cast<unsigned short *>(&val)+1) is the ibm float exp multiplied by 16
      which must be multipled by 4, hence only divided by 4.

      Conversion to IEEE double:
      For a null fraction, val=2^(exp-1023), 2^-1=3fe0 0000 0000 0000  (3fe=1022)
                                             2^-2=3fd0 0000 0000 0000
    */
    double res=0.0;
    if(val) {
      union {
        double val;
        unsigned short s[4];
      } t;
      t.val=0.0;
      double fmant=static_cast<double>(0x00ffffff & val);
  #if Q_BYTE_ORDER==Q_BIG_ENDIAN
      // Never tested on a big endian architecture
      unsigned short& exp=t.s[0];
  #else
      unsigned short& exp=t.s[3];
  #endif
      exp=(0x7f000000 & val) >> 18;
      exp+=0x2E70;
      if(val<0) { // negative number
        res=-fmant*t.val;
      } else {
        res=fmant*t.val;
      }
    }
    return res;
  }

  /*!
    Determination of byter order is based on number of samples read from trace header.
    There is no other fixed and mandatory field that can help us to determine the byte order.
    All traces are read and number of samples are calculated, if it matched the file size,
    than there are good chances that the right byte order has been found.
    For SU format only.
  */
  SignalFileFormat::Format SEGYTraceHeader::determineByteOrderSU(QString fileName, int verbosity)
  {
    TRACE;
    QFile f(fileName);
    if( !f.open(QIODevice::ReadOnly) ) {
      return SignalFileFormat::Unknown;
    }
    QDataStream s(&f);
    s.setByteOrder(QDataStream::LittleEndian);
    bool littleEndian=suCanReadAll(s, f.size());
    f.seek(0);
    s.setByteOrder(QDataStream::BigEndian);
    bool bigEndian=suCanReadAll(s, f.size());
    if((littleEndian && bigEndian) || (!littleEndian && !bigEndian)) {
      App::log(verbosity, tr("Cannot determine byte order for SU format.\n") );
      return SignalFileFormat::Unknown;
    } else if(littleEndian) {
      return SignalFileFormat::SuLittleEndian;
    } else {
      return SignalFileFormat::SuBigEndian;
    }
  }

  bool SEGYTraceHeader::suCanReadAll(QDataStream& s, qint64 fileSize)
  {
    TRACE;
    SEGYTraceHeader h;
    qint64 offset=0;
    while(offset<fileSize) {
      s.device()->seek(offset);
      h.read(s);
      if(s.status()!=QDataStream::Ok) {
        return false;
      }
      offset+=sizeof(SEGYTraceHeader)+4*(qint64)h.field.sampleNumber;
      if(offset>fileSize) {
        return false;
      }
    }
    return true;
  }

  /*!
    Determination of byter order is based on sample type read from file header.
    For SEGY format only.
  */
  SignalFileFormat::Format SEGYTraceHeader::determineByteOrderSEGY(QString fileName, int verbosity)
  {
    TRACE;
    QFile f(fileName);
    if( !f.open(QIODevice::ReadOnly) ) {
      return SignalFileFormat::Unknown;
    }
    QDataStream s(&f);
    qint64 binHeaderOffset=3200;
    // Skip file header: need the number of extended textual file headers and sample coding
    qint16 sampleCoding;
    s.setByteOrder(QDataStream::LittleEndian);
    f.seek(binHeaderOffset + 24);
    s >> sampleCoding;
    bool littleEndian=isSampleCodingValid(sampleCoding);
    s.setByteOrder(QDataStream::BigEndian);
    f.seek(binHeaderOffset + 24);
    s >> sampleCoding;
    bool bigEndian=isSampleCodingValid(sampleCoding);
    if((littleEndian && bigEndian) || (!littleEndian && !bigEndian)) {
      App::log(verbosity, tr("Cannot determine byte order for SEGY format.\n") );
      return SignalFileFormat::Unknown;
    } else if(littleEndian) {
      return SignalFileFormat::SegYLittleEndian;
    } else {
      return SignalFileFormat::SegYBigEndian;
    }
  }

  bool SEGYTraceHeader::isSampleCodingValid(qint16 sampleCoding)
  {
    switch(sampleCoding) {
    case 1:
    case 2:
    case 3:
    case 5:
    case 8:
      return true;
    default:
      return false;
    }
  }

  void SEGYTraceHeader::read(QDataStream& s)
  {
    TRACE;
    for(int i=0;i<7;i++) s >> raw.raw0[i];
    for(int i=0;i<4;i++) s >> raw.raw1[i];
    for(int i=0;i<8;i++) s >> raw.raw2[i];
    for(int i=0;i<2;i++) s >> raw.raw3[i];
    for(int i=0;i<4;i++) s >> raw.raw4[i];
    for(int i=0;i<46;i++) s >> raw.raw5[i];
    for(int i=0;i<5;i++) s >> raw.raw6[i];
    for(int i=0;i<20;i++) s >> raw.raw7[i];
  }

  void SEGYTraceHeader::write(QDataStream& s)
  {
    TRACE;
    for(int i=0;i<7;i++) s << raw.raw0[i];
    for(int i=0;i<4;i++) s << raw.raw1[i];
    for(int i=0;i<8;i++) s << raw.raw2[i];
    for(int i=0;i<2;i++) s << raw.raw3[i];
    for(int i=0;i<4;i++) s << raw.raw4[i];
    for(int i=0;i<46;i++) s << raw.raw5[i];
    for(int i=0;i<5;i++) s << raw.raw6[i];
    for(int i=0;i<20;i++) s << raw.raw7[i];
  }

  double SEGYTraceHeader::decodeCoordinateFactor() const
  {
    if(field.coordinateFactor>0) {
      return field.coordinateFactor;
    } else if(field.coordinateFactor<0) {
      return -1.0/field.coordinateFactor;
    } else {
      return 1.0;
    }
  }

  double SEGYTraceHeader::decodeElevationFactor() const
  {
    if(field.elevationFactor>0) {
      return field.elevationFactor;
    } else if(field.elevationFactor<0) {
      return -1.0/field.elevationFactor;
    } else {
      return 1.0;
    }
  }

  double SEGYTraceHeader::decodeTimeFactor() const
  {
    if(field.timeFactor>0) {
      return field.timeFactor;
    } else if(field.timeFactor<0) {
      return -1.0/field.timeFactor;
    } else {
      return 1.0;
    }
  }

  double SEGYTraceHeader::encodeCoordinateFactor() const
  {
    if(field.coordinateFactor>0) {
      return 1.0/field.coordinateFactor;
    } else if(field.coordinateFactor<0) {
      return -field.coordinateFactor;
    } else {
      return 1.0;
    }
  }

  double SEGYTraceHeader::encodeElevationFactor() const
  {
    if(field.elevationFactor>0) {
      return 1.0/field.elevationFactor;
    } else if(field.elevationFactor<0) {
      return -field.elevationFactor;
    } else {
      return 1.0;
    }
  }

  double SEGYTraceHeader::encodeTimeFactor() const
  {
    if(field.timeFactor>0) {
      return 1.0/field.timeFactor;
    } else if(field.timeFactor<0) {
      return -field.timeFactor;
    } else {
      return 1.0;
    }
  }

  /*!
    Calculate the optimum factor for the range \a min to \a max.
    \a min and \a max are positive values.
  */
  qint16 SEGYTraceHeader::factor(double min, double max, double theoreticalMaximum)
  {
    min=fabs(min);
    max=fabs(max);
    if(max<theoreticalMaximum) {
      double fac;
      if(min>0.0 && min<1.0) {
        if(min<0.0001) {
          return 0;
        }
        fac=1.0/min;
        max*=fac;
      } else {
        fac=1.0;
      }
      fac*=theoreticalMaximum/max;
      double exp=floor(log10(fac));
      if(exp>4.0) {
        exp=4.0;
      }
      return static_cast<qint16>(-pow(10.0, exp));
    } else {
      double fac=max/theoreticalMaximum;
      double exp=ceil(log10(fac));
      if(exp>4.0) {
        return 0;
      }
      return static_cast<qint16>(round(pow(10.0, exp)));
    }
  }

  bool SEGYTraceHeader::setCoordinateFactor(const Point& rec, const Point& src)
  {
    double max=fabs(src.x());
    double min=fabs(src.x());
    if(fabs(src.y())>max)
      max=fabs(src.y());
    else if(fabs(src.y())<min)
      min=fabs(src.y());
    if(fabs(rec.x())>max)
      max=fabs(rec.x());
    else if(fabs(rec.x())<min)
      min=fabs(rec.x());
    if(fabs(rec.y())>max)
      max=fabs(rec.y());
    else if(fabs(rec.y())<min)
      min=fabs(rec.y());

    field.coordinateFactor=factor(min, max, 2147483647.0);
    if(field.coordinateFactor==0) {
      App::log(tr("Coordinate range cannot be represented with SEGY format ([%1, %2]).\n").arg(min).arg(max) );
      return false;
    } else {
      return true;
    }
  }

  bool SEGYTraceHeader::setElevationFactor(const Point& rec, const Point& src)
  {
    double max=fabs(src.z());
    double min=fabs(src.z());
    if(fabs(rec.z())>max)
      max=fabs(rec.z());
    else if(fabs(rec.z())<min)
      min=fabs(rec.z());

    field.elevationFactor=factor(min, max, 2147483647.0);
    return field.elevationFactor!=0;
  }

  void SEGYTraceHeader::setReceiver(const Point& rec)
  {
    double sfxy=encodeCoordinateFactor();
    double sfz=encodeElevationFactor();
    field.receiverXCoordinate=qRound(rec.x()*sfxy);
    field.receiverYCoordinate=qRound(rec.y()*sfxy);
    field.receiverElevation=qRound(rec.z()*sfz);
    field.receiverDatumElevation=0;
    field.coordinateUnit=1; // Length (Meters or feet)
  }

  void SEGYTraceHeader::setSource(const Point& src)
  {
    double sfxy=encodeCoordinateFactor();
    double sfz=encodeElevationFactor();
    field.sourceXCoordinate=qRound(src.x()*sfxy);
    field.sourceYCoordinate=qRound(src.y()*sfxy);
    field.sourceSurfaceElevation=qRound(src.z()*sfz);
    field.sourceDepth=0;
    field.sourceDatumElevation=0;
    field.coordinateUnit=1; // Length (Meters or feet)
  }

  Point SEGYTraceHeader::source() const
  {
    TRACE;
    double sfxy=decodeCoordinateFactor();
    double sfz=decodeElevationFactor();
    return Point(static_cast<double>(field.sourceXCoordinate)*sfxy,
                 static_cast<double>(field.sourceYCoordinate)*sfxy,
                 static_cast<double>(field.sourceSurfaceElevation-field.sourceDepth)*sfz);
  }

  Point SEGYTraceHeader::receiver() const
  {
    TRACE;
    double sfxy=decodeCoordinateFactor();
    double sfz=decodeElevationFactor();
    return Point(static_cast<double>(field.receiverXCoordinate)*sfxy,
                 static_cast<double>(field.receiverYCoordinate)*sfxy,
                 static_cast<double>(field.receiverElevation)*sfz);
  }

  /*!
    T0 according to SEGY standard.
    SU considers all fields after byte 180 differently than SEGY standard.
  */
  DateTime SEGYTraceHeader::startTime(Delay d) const
  {
    TRACE;
    if(field.year<=0 || field.day<=0) {
      App::log(tr("SEGY: invalid year (%1) or day (%2), default to 2020-01-01\n").arg(field.year).arg(field.day));
      return DateTime(QDate(2020, 1, 1), 0.0, 0.0);
    }
    QDateTime t(QDate(field.year , 1, 1), QTime(field.hour, field.minute, field.second));
    t=t.addDays(field.day-1);
    DateTime t0(t);
    switch(d) {
    case NoDelay:
      break;
    case SUDelay:
      t0.addSeconds(field.delayRecordingTime*1e-3);
      break;
    case SEGYDelay: {
        double f=decodeTimeFactor();
        t0.addSeconds(field.delayRecordingTime*1.e-3*f);
      }
      break;
    }
    return t0;
  }

  void SEGYTraceHeader::setStartTime(const DateTime& t, bool su)
  {
    TRACE;
    field.year=static_cast<qint16>(t.date().year());
    field.day=static_cast<qint16>(QDate(field.year, 1, 1).daysTo(t.date())+1);
    field.hour=static_cast<qint16>(t.hour());
    field.minute=static_cast<qint16>(t.minute());
    field.second=static_cast<qint16>(t.second());
    double s=t.fractions()*1e3;  // milliseconds is stored
    if(su) {
      field.delayRecordingTime=static_cast<qint16>(round(s));  // milliseconds is stored
    } else {
      double s=t.fractions()*1e3;  // milliseconds is stored
      field.timeFactor=factor(s, s, 32767.0);
      double f=encodeTimeFactor();
      field.delayRecordingTime=static_cast<qint16>(round(f*s));
    }
  }

  bool SEGYTraceHeader::setNSamples(int n)
  {
    TRACE;
    if(n>65535) {
      App::log(tr("too many samples for SEG-Y traces: maximum=65535, value=%1\n").arg(n) );
      return false;
    } else {
      field.sampleNumber=n;
      return true;
    }
  }

} // namespace GeopsyCore
