content: /*
   * Copyright 2002-2019 Drew Noakes and contributors
   *
   *    Licensed under the Apache License, Version 2.0 (the "License");
   *    you may not use this file except in compliance with the License.
   *    You may obtain a copy of the License at
   *
   *        https://www.apache.org/licenses/LICENSE-2.0
   *
   *    Unless required by applicable law or agreed to in writing, software
   *    distributed under the License is distributed on an "AS IS" BASIS,
   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *    See the License for the specific language governing permissions and
   *    limitations under the License.
   *
   * More information about this project is available at:
   *
   *    https://drewnoakes.com/code/exif/
   *    https://github.com/drewnoakes/metadata-extractor
   */
  package com.drew.metadata.exif;
  
  import com.drew.lang.GeoLocation;
  import com.drew.lang.Rational;
  import com.drew.lang.annotations.NotNull;
  import com.drew.lang.annotations.Nullable;
  import com.drew.metadata.TagDescriptor;
  
  import java.text.DecimalFormat;
  
  import static com.drew.metadata.exif.GpsDirectory.*;
  
  /**
   * Provides human-readable string representations of tag values stored in a {@link GpsDirectory}.
   *
   * @author Drew Noakes https://drewnoakes.com
   */
  @SuppressWarnings("WeakerAccess")
  public class GpsDescriptor extends TagDescriptor<GpsDirectory>
  {
      public GpsDescriptor(@NotNull GpsDirectory directory)
      {
          super(directory);
      }
  
      @Override
      @Nullable
      public String getDescription(int tagType)
      {
          switch (tagType) {
              case TAG_VERSION_ID:
                  return getGpsVersionIdDescription();
              case TAG_ALTITUDE:
                  return getGpsAltitudeDescription();
              case TAG_ALTITUDE_REF:
                  return getGpsAltitudeRefDescription();
              case TAG_STATUS:
                  return getGpsStatusDescription();
              case TAG_MEASURE_MODE:
                  return getGpsMeasureModeDescription();
              case TAG_DOP:
                  return getGpsDopDescription();
              case TAG_SPEED_REF:
                  return getGpsSpeedRefDescription();
              case TAG_SPEED:
                  return getGpsSpeedDescription();
              case TAG_TRACK_REF:
              case TAG_IMG_DIRECTION_REF:
              case TAG_DEST_BEARING_REF:
                  return getGpsDirectionReferenceDescription(tagType);
              case TAG_TRACK:
              case TAG_IMG_DIRECTION:
              case TAG_DEST_BEARING:
                  return getGpsDirectionDescription(tagType);
              case TAG_DEST_LATITUDE:
                  return getGpsDestLatitudeDescription();
              case TAG_DEST_LONGITUDE:
                  return getGpsDestLongitudeDescription();
              case TAG_DEST_DISTANCE_REF:
                  return getGpsDestinationReferenceDescription();
              case TAG_DEST_DISTANCE:
                  return getGpsDestDistanceDescription();
              case TAG_TIME_STAMP:
                  return getGpsTimeStampDescription();
              case TAG_LONGITUDE:
                  // three rational numbers -- displayed in HH"MM"SS.ss
                  return getGpsLongitudeDescription();
              case TAG_LATITUDE:
                  // three rational numbers -- displayed in HH"MM"SS.ss
                  return getGpsLatitudeDescription();
              case TAG_PROCESSING_METHOD:
                  return getGpsProcessingMethodDescription();
              case TAG_AREA_INFORMATION:
                  return getGpsAreaInformationDescription();
              case TAG_DIFFERENTIAL:
                  return getGpsDifferentialDescription();
              case TAG_H_POSITIONING_ERROR:
                  return getGpsHPositioningErrorDescription();
              default:
                  return super.getDescription(tagType);
          }
      }
  
      @Nullable
      private String getGpsVersionIdDescription()
      {
          return getVersionBytesDescription(TAG_VERSION_ID, 1);
      }
  
      @Nullable
      public String getGpsLatitudeDescription()
      {
          GeoLocation location = _directory.getGeoLocation();
          return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude());
      }
  
      @Nullable
      public String getGpsLongitudeDescription()
      {
          GeoLocation location = _directory.getGeoLocation();
          return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude());
      }
  
      @Nullable
      public String getGpsTimeStampDescription()
      {
          // time in hour, min, sec
          Rational[] timeComponents = _directory.getRationalArray(TAG_TIME_STAMP);
          DecimalFormat df = new DecimalFormat("00.000");
          return timeComponents == null
              ? null
              : String.format("%02d:%02d:%s UTC",
                  timeComponents[0].intValue(),
                  timeComponents[1].intValue(),
                  df.format(timeComponents[2].doubleValue()));
      }
  
      @Nullable
      public String getGpsDestLatitudeDescription()
      {
          return getGeoLocationDimension(TAG_DEST_LATITUDE, TAG_DEST_LATITUDE_REF, "S");
      }
  
      @Nullable
      public String getGpsDestLongitudeDescription()
      {
          return getGeoLocationDimension(TAG_DEST_LONGITUDE, TAG_DEST_LONGITUDE_REF, "W");
      }
  
      @Nullable
      private String getGeoLocationDimension(int tagValue, int tagRef, String positiveRef)
      {
          Rational[] values = _directory.getRationalArray(tagValue);
          String ref = _directory.getString(tagRef);
  
          if (values == null || values.length != 3 || ref == null)
              return null;
  
          Double dec = GeoLocation.degreesMinutesSecondsToDecimal(
              values[0], values[1], values[2], ref.equalsIgnoreCase(positiveRef));
  
          return dec == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(dec);
      }
  
      @Nullable
      public String getGpsDestinationReferenceDescription()
      {
          final String value = _directory.getString(TAG_DEST_DISTANCE_REF);
          if (value == null)
              return null;
          String distanceRef = value.trim();
          if ("K".equalsIgnoreCase(distanceRef)) {
              return "kilometers";
          } else if ("M".equalsIgnoreCase(distanceRef)) {
              return "miles";
          } else if ("N".equalsIgnoreCase(distanceRef)) {
              return "knots";
          } else {
              return "Unknown (" + distanceRef + ")";
          }
      }
  
      @Nullable
      public String getGpsDestDistanceDescription()
      {
          final Rational value = _directory.getRational(TAG_DEST_DISTANCE);
          if (value == null)
              return null;
          final String unit = getGpsDestinationReferenceDescription();
          return String.format("%s %s",
              new DecimalFormat("0.##").format(value.doubleValue()),
              unit == null ? "unit" : unit.toLowerCase());
      }
  
      @Nullable
      public String getGpsDirectionDescription(int tagType)
      {
          Rational angle = _directory.getRational(tagType);
          // provide a decimal version of rational numbers in the description, to avoid strings like "35334/199 degrees"
          String value = angle != null
              ? new DecimalFormat("0.##").format(angle.doubleValue())
              : _directory.getString(tagType);
          return value == null || value.trim().length() == 0 ? null : value.trim() + " degrees";
      }
  
      @Nullable
      public String getGpsDirectionReferenceDescription(int tagType)
      {
          final String value = _directory.getString(tagType);
          if (value == null)
              return null;
          String gpsDistRef = value.trim();
          if ("T".equalsIgnoreCase(gpsDistRef)) {
              return "True direction";
          } else if ("M".equalsIgnoreCase(gpsDistRef)) {
              return "Magnetic direction";
          } else {
              return "Unknown (" + gpsDistRef + ")";
          }
      }
  
      @Nullable
      public String getGpsDopDescription()
      {
          final Rational value = _directory.getRational(TAG_DOP);
          return value == null ? null : new DecimalFormat("0.##").format(value.doubleValue());
      }
  
      @Nullable
      public String getGpsSpeedRefDescription()
      {
          final String value = _directory.getString(TAG_SPEED_REF);
          if (value == null)
              return null;
          String gpsSpeedRef = value.trim();
          if ("K".equalsIgnoreCase(gpsSpeedRef)) {
              return "km/h";
          } else if ("M".equalsIgnoreCase(gpsSpeedRef)) {
              return "mph";
          } else if ("N".equalsIgnoreCase(gpsSpeedRef)) {
              return "knots";
          } else {
              return "Unknown (" + gpsSpeedRef + ")";
          }
      }
  
      @Nullable
      public String getGpsSpeedDescription()
      {
          final Rational value = _directory.getRational(TAG_SPEED);
          if (value == null)
              return null;
          final String unit = getGpsSpeedRefDescription();
          return String.format("%s %s",
              new DecimalFormat("0.##").format(value.doubleValue()),
              unit == null ? "unit" : unit.toLowerCase());
      }
  
      @Nullable
      public String getGpsMeasureModeDescription()
      {
          final String value = _directory.getString(TAG_MEASURE_MODE);
          if (value == null)
              return null;
          String gpsSpeedMeasureMode = value.trim();
          if ("2".equalsIgnoreCase(gpsSpeedMeasureMode)) {
              return "2-dimensional measurement";
          } else if ("3".equalsIgnoreCase(gpsSpeedMeasureMode)) {
              return "3-dimensional measurement";
          } else {
              return "Unknown (" + gpsSpeedMeasureMode + ")";
          }
      }
  
      @Nullable
      public String getGpsStatusDescription()
      {
          final String value = _directory.getString(TAG_STATUS);
          if (value == null)
              return null;
          String gpsStatus = value.trim();
          if ("A".equalsIgnoreCase(gpsStatus)) {
              return "Active (Measurement in progress)";
          } else if ("V".equalsIgnoreCase(gpsStatus)) {
              return "Void (Measurement Interoperability)";
          } else {
              return "Unknown (" + gpsStatus + ")";
          }
      }
  
      @Nullable
      public String getGpsAltitudeRefDescription()
      {
          return getIndexedDescription(TAG_ALTITUDE_REF, "Sea level", "Below sea level");
      }
  
      @Nullable
      public String getGpsAltitudeDescription()
      {
          final Rational value = _directory.getRational(TAG_ALTITUDE);
          return value == null ? null : new DecimalFormat("0.##").format(value.doubleValue()) + " metres";
      }
  
      @Nullable
      public String getGpsProcessingMethodDescription()
      {
          return getEncodedTextDescription(TAG_PROCESSING_METHOD);
      }
  
      @Nullable
      public String getGpsAreaInformationDescription()
      {
          return getEncodedTextDescription(TAG_AREA_INFORMATION);
      }
  
      @Nullable
      public String getGpsDifferentialDescription()
      {
          return getIndexedDescription(TAG_DIFFERENTIAL, "No Correction", "Differential Corrected");
      }
  
      @Nullable
      public String getGpsHPositioningErrorDescription()
      {
          final Rational value = _directory.getRational(TAG_H_POSITIONING_ERROR);
          return value == null ? null : new DecimalFormat("0.##").format(value.doubleValue()) + " metres";
      }
  
      @Nullable
      public String getDegreesMinutesSecondsDescription()
      {
          GeoLocation location = _directory.getGeoLocation();
          return location == null ? null : location.toDMSString();
      }
  }
  