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.makernotes;
  
  import com.drew.lang.Rational;
  import com.drew.lang.annotations.NotNull;
  import com.drew.lang.annotations.Nullable;
  import com.drew.metadata.TagDescriptor;
  
  import java.nio.ByteBuffer;
  import java.text.DecimalFormat;
  
  import static com.drew.metadata.exif.makernotes.NikonType2MakernoteDirectory.*;
  
  /**
   * Provides human-readable string representations of tag values stored in a {@link NikonType2MakernoteDirectory}.
   *
   * Type-2 applies to the E990 and D-series cameras such as the D1, D70 and D100.
   *
   * @author Drew Noakes https://drewnoakes.com
   */
  @SuppressWarnings("WeakerAccess")
  public class NikonType2MakernoteDescriptor extends TagDescriptor<NikonType2MakernoteDirectory>
  {
      public NikonType2MakernoteDescriptor(@NotNull NikonType2MakernoteDirectory directory)
      {
          super(directory);
      }
  
      @Override
      @Nullable
      public String getDescription(int tagType)
      {
          switch (tagType)
          {
              case TAG_PROGRAM_SHIFT:
                  return getProgramShiftDescription();
              case TAG_EXPOSURE_DIFFERENCE:
                  return getExposureDifferenceDescription();
              case TAG_LENS:
                  return getLensDescription();
              case TAG_CAMERA_HUE_ADJUSTMENT:
                  return getHueAdjustmentDescription();
              case TAG_CAMERA_COLOR_MODE:
                  return getColorModeDescription();
              case TAG_AUTO_FLASH_COMPENSATION:
                  return getAutoFlashCompensationDescription();
              case TAG_FLASH_EXPOSURE_COMPENSATION:
                  return getFlashExposureCompensationDescription();
              case TAG_FLASH_BRACKET_COMPENSATION:
                  return getFlashBracketCompensationDescription();
              case TAG_EXPOSURE_TUNING:
                  return getExposureTuningDescription();
              case TAG_LENS_STOPS:
                  return getLensStopsDescription();
              case TAG_COLOR_SPACE:
                  return getColorSpaceDescription();
              case TAG_ACTIVE_D_LIGHTING:
                  return getActiveDLightingDescription();
              case TAG_VIGNETTE_CONTROL:
                  return getVignetteControlDescription();
              case TAG_ISO_1:
                  return getIsoSettingDescription();
              case TAG_DIGITAL_ZOOM:
                  return getDigitalZoomDescription();
              case TAG_FLASH_USED:
                  return getFlashUsedDescription();
              case TAG_AF_FOCUS_POSITION:
                  return getAutoFocusPositionDescription();
              case TAG_FIRMWARE_VERSION:
                  return getFirmwareVersionDescription();
              case TAG_LENS_TYPE:
                  return getLensTypeDescription();
              case TAG_SHOOTING_MODE:
                  return getShootingModeDescription();
              case TAG_NEF_COMPRESSION:
                  return getNEFCompressionDescription();
              case TAG_HIGH_ISO_NOISE_REDUCTION:
                  return getHighISONoiseReductionDescription();
              case TAG_POWER_UP_TIME:
                  return getPowerUpTimeDescription();
              default:
                  return super.getDescription(tagType);
          }
      }
  
      @Nullable
      public String getPowerUpTimeDescription()
      {
          // this is generally a byte[] of length 8 directly representing a date and time.
          // the format is : first 2 bytes together are the year, and then each byte after
          //                 is month, day, hour, minute, second with the eighth byte unused
          // e.g., 2011:04:25 01:54:58
  
          byte[] values = _directory.getByteArray(TAG_POWER_UP_TIME);
          if (values == null) {
              return null;
          }
          short year = ByteBuffer.wrap(new byte[]{values[0], values[1]}).getShort();
          return String.format("%04d:%02d:%02d %02d:%02d:%02d", year, values[2], values[3],
                                                          values[4], values[5], values[6]);
      }
  
      @Nullable
      public String getHighISONoiseReductionDescription()
      {
          return getIndexedDescription(TAG_HIGH_ISO_NOISE_REDUCTION,
              "Off",
              "Minimal",
              "Low",
              null,
              "Normal",
              null,
              "High"
          );
      }
  
      @Nullable
      public String getFlashUsedDescription()
      {
          return getIndexedDescription(TAG_FLASH_USED,
              "Flash Not Used",
              "Manual Flash",
              null,
              "Flash Not Ready",
              null,
              null,
              null,
              "External Flash",
              "Fired, Commander Mode",
              "Fired, TTL Mode"
          );
      }
  
      @Nullable
      public String getNEFCompressionDescription()
      {
          return getIndexedDescription(TAG_NEF_COMPRESSION,
              1,
              "Lossy (Type 1)",
              null,
              "Uncompressed",
              null,
              null,
              null,
              "Lossless",
              "Lossy (Type 2)"
          );
      }
  
      @Nullable
      public String getShootingModeDescription()
      {
          return getBitFlagDescription(TAG_SHOOTING_MODE,
              // LSB [low label, high label]
              new String[]{"Single Frame", "Continuous"},
              "Delay",
              null,
              "PC Control",
              "Exposure Bracketing",
              "Auto ISO",
              "White-Balance Bracketing",
              "IR Control"
          );
      }
  
      @Nullable
      public String getLensTypeDescription()
      {
          return getBitFlagDescription(TAG_LENS_TYPE,
              // LSB [low label, high label]
              new String[]{"AF", "MF"},
              "D",
              "G",
              "VR"
          );
      }
  
      @Nullable
      public String getColorSpaceDescription()
      {
          return getIndexedDescription(TAG_COLOR_SPACE,
              1,
              "sRGB",
              "Adobe RGB"
          );
      }
  
      @Nullable
      public String getActiveDLightingDescription()
      {
          Integer value = _directory.getInteger(TAG_ACTIVE_D_LIGHTING);
          if (value==null)
              return null;
          switch (value) {
              case 0: return "Off";
              case 1: return "Light";
              case 3: return "Normal";
              case 5: return "High";
              case 7: return "Extra High";
              case 65535: return "Auto";
              default: return "Unknown (" + value + ")";
          }
      }
  
      @Nullable
      public String getVignetteControlDescription()
      {
          Integer value = _directory.getInteger(TAG_VIGNETTE_CONTROL);
          if (value==null)
              return null;
          switch (value) {
              case 0: return "Off";
              case 1: return "Low";
              case 3: return "Normal";
              case 5: return "High";
              default: return "Unknown (" + value + ")";
          }
      }
  
      @Nullable
      public String getAutoFocusPositionDescription()
      {
          int[] values = _directory.getIntArray(TAG_AF_FOCUS_POSITION);
          if (values==null)
              return null;
          if (values.length != 4 || values[0] != 0 || values[2] != 0 || values[3] != 0) {
              return "Unknown (" + _directory.getString(TAG_AF_FOCUS_POSITION) + ")";
          }
          switch (values[1]) {
              case 0:
                  return "Centre";
              case 1:
                  return "Top";
              case 2:
                  return "Bottom";
              case 3:
                  return "Left";
              case 4:
                  return "Right";
              default:
                  return "Unknown (" + values[1] + ")";
          }
      }
  
      @Nullable
      public String getDigitalZoomDescription()
      {
          Rational value = _directory.getRational(TAG_DIGITAL_ZOOM);
          if (value==null)
              return null;
          return value.intValue() == 1
                  ? "No digital zoom"
                  : value.toSimpleString(true) + "x digital zoom";
      }
  
      @Nullable
      public String getProgramShiftDescription()
      {
          return getEVDescription(TAG_PROGRAM_SHIFT);
      }
  
      @Nullable
      public String getExposureDifferenceDescription()
      {
          return getEVDescription(TAG_EXPOSURE_DIFFERENCE);
      }
  
      @Nullable
      public String getAutoFlashCompensationDescription()
      {
          return getEVDescription(TAG_AUTO_FLASH_COMPENSATION);
      }
  
      @Nullable
      public String getFlashExposureCompensationDescription()
      {
          return getEVDescription(TAG_FLASH_EXPOSURE_COMPENSATION);
      }
  
      @Nullable
      public String getFlashBracketCompensationDescription()
      {
          return getEVDescription(TAG_FLASH_BRACKET_COMPENSATION);
      }
  
      @Nullable
      public String getExposureTuningDescription()
      {
          return getEVDescription(TAG_EXPOSURE_TUNING);
      }
  
      @Nullable
      public String getLensStopsDescription()
      {
          return getEVDescription(TAG_LENS_STOPS);
      }
  
      @Nullable
      private String getEVDescription(int tagType)
      {
          int[] values = _directory.getIntArray(tagType);
          if (values == null || values.length < 2)
              return null;
          if (values.length < 3 || values[2] == 0)
              return null;
          final DecimalFormat decimalFormat = new DecimalFormat("0.##");
          double ev = values[0] * values[1] / (double)values[2];
          return decimalFormat.format(ev) + " EV";
      }
  
      @Nullable
      public String getIsoSettingDescription()
      {
          int[] values = _directory.getIntArray(TAG_ISO_1);
          if (values == null)
              return null;
          if (values[0] != 0 || values[1] == 0)
              return "Unknown (" + _directory.getString(TAG_ISO_1) + ")";
          return "ISO " + values[1];
      }
  
      @Nullable
      public String getLensDescription()
      {
          return getLensSpecificationDescription(TAG_LENS);
      }
  
      @Nullable
      public String getLensFocusDistance()
      {
          int[] values = _directory.getDecryptedIntArray(TAG_LENS_DATA);
  
          if (values == null || values.length < 11)
              return null;
  
          return String.format("%.2fm", getDistanceInMeters(values[10]));
      }
  
      @Nullable
      public String getHueAdjustmentDescription()
      {
          return getFormattedString(TAG_CAMERA_HUE_ADJUSTMENT, "%s degrees");
      }
  
      @Nullable
      public String getColorModeDescription()
      {
          String value = _directory.getString(TAG_CAMERA_COLOR_MODE);
          return value == null ? null : value.startsWith("MODE1") ? "Mode I (sRGB)" : value;
      }
  
      @Nullable
      public String getFirmwareVersionDescription()
      {
          return getVersionBytesDescription(TAG_FIRMWARE_VERSION, 2);
      }
  
      private double getDistanceInMeters(int val)
      {
          if (val < 0)
              val += 256;
          return 0.01 * Math.pow(10, val / 40.0f);
      }
  }
  