<?php

declare(strict_types=1);

namespace lsolesen\pel;

/**
 * Class for holding signed shorts.
 *
 * This class can hold shorts, either just a single short or an array
 * of shorts. The class will be used to manipulate any of the Exif
 * tags which has format {@link PelFormat::SHORT} like in this
 * example:
 *
 * <code>
 * $w = $ifd->getEntry(PelTag::IMAGE_WIDTH);
 * $w->setValue($w->getValue() / 2);
 * $h = $ifd->getEntry(PelTag::IMAGE_LENGTH);
 * $h->setValue($h->getValue() / 2);
 * </code>
 *
 * Here the width and height is updated to 50% of their original
 * values.
 */
class PelEntryShort extends PelEntryNumber
{
    private const IFD_TYPE_TRANSLATIONS = [
        PelIfd::CANON_SHOT_INFO => [
            PelTag::CANON_SI_WHITE_BALANCE => [
                0 => 'Auto',
                1 => 'Daylight',
                2 => 'Cloudy',
                3 => 'Tungsten',
                4 => 'Fluorescent',
                5 => 'Flash',
                6 => 'Custom',
                7 => 'Black & White',
                8 => 'Shade',
                9 => 'Manual Temperature (Kelvin)',
                10 => 'PC Set1',
                11 => 'PC Set2',
                12 => 'PC Set3',
                14 => 'Daylight Fluorescent',
                15 => 'Custom 1',
                16 => 'Custom 2',
                17 => 'Underwater',
                18 => 'Custom 3',
                19 => 'Custom 4',
                20 => 'PC Set4',
                21 => 'PC Set5',
                23 => 'Auto (ambience priority)',
            ],
            PelTag::CANON_SI_SLOW_SHUTTER => [
                0 => 'Off',
                1 => 'Night Scene',
                2 => 'On',
                3 => 'None',
            ],
            PelTag::CANON_SI_AF_POINT_USED => [
                0x3000 => 'None (MF)',
                0x3001 => 'Right',
                0x3002 => 'Center',
                0x3003 => 'Center+Right',
                0x3004 => 'Left',
                0x3005 => 'Left+Right',
                0x3006 => 'Left+Center',
                0x3007 => 'All',
            ],
            PelTag::CANON_SI_AUTO_EXPOSURE_BRACKETING => [
                - 1 => 'On',
                0 => 'Off',
                1 => 'On (shot 1)',
                2 => 'On (shot 2)',
                3 => 'On (shot 3)',
            ],
            PelTag::CANON_SI_CAMERA_TYPE => [
                248 => 'EOS High-end',
                250 => 'Compact',
                252 => 'EOS Mid-range',
                255 => 'DV Camera',
            ],
            PelTag::CANON_SI_AUTO_ROTATE => [
                0 => 'None',
                1 => 'Rotate 90 CW',
                2 => 'Rotate 180',
                3 => 'Rotate 270 CW',
            ],
            PelTag::CANON_SI_ND_FILTER => [
                0 => 'Off',
                1 => 'On',
            ],
        ],
        PelIfd::CANON_PANORAMA => [
            PelTag::CANON_PA_PANORAMA_DIRECTION => [
                0 => 'Left to Right',
                1 => 'Right to Left',
                2 => 'Bottom to Top',
                3 => 'Top to Bottom',
                4 => '2x2 Matrix (Clockwise)',
            ],
        ],
    ];

    private const PEL_TAG_TRANSLATIONS = [
        PelTag::METERING_MODE => [
            0 => 'Unknown',
            1 => 'Average',
            2 => 'Center-Weighted Average',
            3 => 'Spot',
            4 => 'Multi Spot',
            5 => 'Pattern',
            6 => 'Partial',
            255 => 'Other',
        ],
        PelTag::COMPRESSION => [
            1 => 'Uncompressed',
            6 => 'JPEG compression',
        ],
        PelTag::PLANAR_CONFIGURATION => [
            1 => 'chunky format',
            2 => 'planar format',
        ],
        PelTag::SENSING_METHOD => [
            1 => 'Not defined',
            2 => 'One-chip color area sensor',
            3 => 'Two-chip color area sensor',
            4 => 'Three-chip color area sensor',
            5 => 'Color sequential area sensor',
            7 => 'Trilinear sensor',
            8 => 'Color sequential linear sensor',
        ],
        PelTag::LIGHT_SOURCE => [
            0 => 'Unknown',
            1 => 'Daylight',
            2 => 'Fluorescent',
            3 => 'Tungsten (incandescent light)',
            4 => 'Flash',
            9 => 'Fine weather',
            10 => 'Cloudy weather',
            11 => 'Shade',
            12 => 'Daylight fluorescent',
            13 => 'Day white fluorescent',
            14 => 'Cool white fluorescent',
            15 => 'White fluorescent',
            17 => 'Standard light A',
            18 => 'Standard light B',
            19 => 'Standard light C',
            20 => 'D55',
            21 => 'D65',
            22 => 'D75',
            24 => 'ISO studio tungsten',
            255 => 'Other',
        ],
        PelTag::FOCAL_PLANE_RESOLUTION_UNIT => [
            2 => 'Inch',
            3 => 'Centimeter',
        ],
        PelTag::RESOLUTION_UNIT => [
            2 => 'Inch',
            3 => 'Centimeter',
        ],
        PelTag::EXPOSURE_PROGRAM => [
            0 => 'Not defined',
            1 => 'Manual',
            2 => 'Normal program',
            3 => 'Aperture priority',
            4 => 'Shutter priority',
            5 => 'Creative program (biased toward depth of field)',
            6 => 'Action program (biased toward fast shutter speed)',
            7 => 'Portrait mode (for closeup photos with the background out of focus',
            8 => 'Landscape mode (for landscape photos with the background in focus',
        ],
        PelTag::ORIENTATION => [
            1 => 'top - left',
            2 => 'top - right',
            3 => 'bottom - right',
            4 => 'bottom - left',
            5 => 'left - top',
            6 => 'right - top',
            7 => 'right - bottom',
            8 => 'left - bottom',
        ],
        PelTag::YCBCR_POSITIONING => [
            1 => 'centered',
            2 => 'co-sited',
        ],
        PelTag::PHOTOMETRIC_INTERPRETATION => [
            2 => 'RGB',
            6 => 'YCbCr',
        ],
        PelTag::COLOR_SPACE => [
            1 => 'sRGB',
            2 => 'Adobe RGB',
            0xffff => 'Uncalibrated',
        ],
        PelTag::FLASH => [
            0x0000 => 'Flash did not fire.',
            0x0001 => 'Flash fired.',
            0x0005 => 'Strobe return light not detected.',
            0x0007 => 'Strobe return light detected.',
            0x0009 => 'Flash fired, compulsory flash mode.',
            0x000d => 'Flash fired, compulsory flash mode, return light not detected.',
            0x000f => 'Flash fired, compulsory flash mode, return light detected.',
            0x0010 => 'Flash did not fire, compulsory flash mode.',
            0x0018 => 'Flash did not fire, auto mode.',
            0x0019 => 'Flash fired, auto mode.',
            0x001d => 'Flash fired, auto mode, return light not detected.',
            0x001f => 'Flash fired, auto mode, return light detected.',
            0x0020 => 'No flash function.',
            0x0041 => 'Flash fired, red-eye reduction mode.',
            0x0045 => 'Flash fired, red-eye reduction mode, return light not detected.',
            0x0047 => 'Flash fired, red-eye reduction mode, return light detected.',
            0x0049 => 'Flash fired, compulsory flash mode, red-eye reduction mode.',
            0x004d => 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected.',
            0x004f => 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected.',
            0x0058 => 'Flash did not fire, auto mode, red-eye reduction mode.',
            0x0059 => 'Flash fired, auto mode, red-eye reduction mode.',
            0x005d => 'Flash fired, auto mode, return light not detected, red-eye reduction mode.',
            0x005f => 'Flash fired, auto mode, return light detected, red-eye reduction mode.',
        ],
        PelTag::CUSTOM_RENDERED => [
            0 => 'Normal process',
            1 => 'Custom process',
        ],
        PelTag::EXPOSURE_MODE => [
            0 => 'Auto exposure',
            1 => 'Manual exposure',
            2 => 'Auto bracket',
        ],
        PelTag::WHITE_BALANCE => [
            0 => 'Auto white balance',
            1 => 'Manual white balance',
        ],
        PelTag::SCENE_CAPTURE_TYPE => [
            0 => 'Standard',
            1 => 'Landscape',
            2 => 'Portrait',
            3 => 'Night scene',
        ],
        PelTag::GAIN_CONTROL => [
            0 => 'Normal',
            1 => 'Low gain up',
            2 => 'High gain up',
            3 => 'Low gain down',
            4 => 'High gain down',
        ],
        PelTag::SATURATION => [
            0 => 'Normal',
            1 => 'Low saturation',
            2 => 'High saturation',
        ],
        PelTag::CONTRAST => [
            0 => 'Normal',
            1 => 'Soft',
            2 => 'Hard',
        ],
        PelTag::SHARPNESS => [
            0 => 'Normal',
            1 => 'Soft',
            2 => 'Hard',
        ],
        PelTag::SUBJECT_DISTANCE_RANGE => [
            0 => 'Unknown',
            1 => 'Macro',
            2 => 'Close view',
            3 => 'Distant view',
        ],
    ];

    /**
     * Make a new entry that can hold an unsigned short.
     *
     * The method accept several integer arguments. The {@link getValue} method will always return
     * an array except for when a single integer argument is given here.
     *
     * This means that one can conveniently use objects like this:
     * <code>
     * $a = new PelEntryShort(PelTag::EXIF_IMAGE_HEIGHT, 42);
     * $b = $a->getValue() + 314;
     * </code>
     * where the call to {@link getValue} will return an integer
     * instead of an array with one integer element, which would then
     * have to be extracted.
     *
     * @param int $tag
     *   The tag which this entry represents. This should be one of the constants defined in
     *   {@link PelTag}, e.g., {@link PelTag::IMAGE_WIDTH}, {@link PelTag::ISO_SPEED_RATINGS}, or
     *   any other tag with format {@link PelFormat::SHORT}.
     * @param int ...$value
     *   The short(s) that this entry will represent. The argument passed must obey the same rules
     *   as the argument to {@link setValue}, namely that it should be within range of an unsigned
     *   short, that is between 0 and 65535 (inclusive). If not, then a
     *   {@link PelOverFlowException} will be thrown.
     */
    public function __construct(int $tag, int ...$value)
    {
        $this->tag = $tag;
        $this->min = 0;
        $this->max = 65535;
        $this->format = PelFormat::SHORT;
        $this->setValueArray($value);
    }

    /**
     * Convert a number into bytes.
     *
     * @param int $number
     *            the number that should be converted.
     * @param bool $order
     *            one of {@link PelConvert::LITTLE_ENDIAN} and
     *            {@link PelConvert::BIG_ENDIAN}, specifying the target byte order.
     *
     * @return string bytes representing the number given.
     */
    public function numberToBytes(int $number, bool $order): string
    {
        return PelConvert::shortToBytes($number, $order);
    }

    /**
     * Get the value of an entry as text.
     *
     * The value will be returned in a format suitable for presentation,
     * e.g., instead of returning '2' for a {@link PelTag::METERING_MODE} tag, 'Center-Weighted Average' is
     * returned.
     *
     * @param bool $brief
     *            some values can be returned in a long or more
     *            brief form, and this parameter controls that.
     *
     * @return string the value as text.
     */
    public function getText(bool $brief = false): string
    {
        if (array_key_exists($this->ifd_type, self::IFD_TYPE_TRANSLATIONS)) {
            if (array_key_exists($this->value[0], self::IFD_TYPE_TRANSLATIONS[$this->ifd_type])) {
                // @todo this is buggy and not working - it's not passing a string
                // but an array of strings.
                // @phpstan-ignore-next-line
                return Pel::tra(self::IFD_TYPE_TRANSLATIONS[$this->ifd_type][$this->value[0]]);
            }
            return (string) $this->value[0];
        }
        if ($this->tag === PelTag::YCBCR_SUB_SAMPLING) {
            if ($this->value[0] === 2 && $this->value[1] === 1) {
                return 'YCbCr4:2:2';
            }
            if ($this->value[0] === 2 && $this->value[1] === 2) {
                return 'YCbCr4:2:0';
            }
            return $this->value[0] . ', ' . $this->value[1];
        }
        if ($this->tag === PelTag::SUBJECT_AREA) {
            return match ($this->components) {
                2 => Pel::fmt('(x,y) = (%d,%d)', $this->value[0], $this->value[1]),
                3 => Pel::fmt('Within distance %d of (x,y) = (%d,%d)', $this->value[0], $this->value[1], $this->value[2]),
                4 => Pel::fmt('Within rectangle (width %d, height %d) around (x,y) = (%d,%d)', $this->value[0], $this->value[1], $this->value[2], $this->value[3]),
                default => Pel::fmt('Unexpected number of components (%d, expected 2, 3, or 4).', $this->components),
            };
        }
        if (array_key_exists($this->tag, self::PEL_TAG_TRANSLATIONS)) {
            if (array_key_exists($this->value[0], self::PEL_TAG_TRANSLATIONS[$this->tag])) {
                return Pel::tra(self::PEL_TAG_TRANSLATIONS[$this->tag][$this->value[0]]);
            }
            return (string) $this->value[0];
        }
        return parent::getText($brief);
    }
}
