package com.drew.metadata.apple;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import com.drew.lang.annotations.NotNull;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.makernotes.AppleMakernoteDirectory;
import com.drew.metadata.exif.makernotes.AppleRunTimeMakernoteDirectory;
import com.drew.metadata.plist.BplistReader;

/**
 * Reads the <tt>AppleRunTime</tt> data and adds {@link AppleRunTimeMakernoteDirectory} to the
 * parent {@link AppleMakernoteDirectory} if it can be parsed with no errors.
 */
public class AppleRunTimeReader
{
    public void extract(@NotNull byte[] bytes, @NotNull final Metadata metadata, @NotNull final Directory parentDirectory)
    {
        if (!BplistReader.isValid(bytes)) {
            parentDirectory.addError("Input array is not a bplist");
            return;
        }

        AppleRunTimeMakernoteDirectory directory = new AppleRunTimeMakernoteDirectory();
        directory.setParent(parentDirectory);

        try {
            processAppleRunTime(directory, bytes);

            if (directory.getTagCount() > 0) {
                metadata.addDirectory(directory);
            }
        } catch (IOException e) {
            parentDirectory.addError("Error processing TAG_RUN_TIME: " + e.getMessage());
        }
    }

    /**
     * Process the BPLIST containing the RUN_TIME tag. The directory will only be populated with values
     * if the <tt>flag</tt> indicates that the CMTime structure is &quot;valid&quot;.
     *
     * @param directory The <tt>AppleRunTimeMakernoteDirectory</tt> to set values onto.
     * @param bplist The BPLIST
     * @throws IOException Thrown if an error occurs parsing the BPLIST as a CMTime structure.
     */
    private static void processAppleRunTime(@NotNull final AppleRunTimeMakernoteDirectory directory, @NotNull final byte[] bplist) throws IOException
    {
        final BplistReader.PropertyListResults results = BplistReader.parse(bplist);

        final Set<Map.Entry<Byte, Byte>> entrySet = results.getEntrySet();

        if (entrySet != null) {
            HashMap<String, Object> values = new HashMap<String, Object>(entrySet.size());

            for (Map.Entry<Byte, Byte> entry : entrySet) {
                String key = (String)results.getObjects().get(entry.getKey());
                Object value = results.getObjects().get(entry.getValue());

                values.put(key, value);
            }

            // https://developer.apple.com/documentation/coremedia/cmtime-u58

            Object flagsObject = values.get("flags");
            if (flagsObject instanceof Byte) {
                byte flags = (Byte) flagsObject;
                if ((flags & 0x1) == 0x1) {
                    directory.setInt(AppleRunTimeMakernoteDirectory.CMTimeFlags, flags);
                    directory.setInt(AppleRunTimeMakernoteDirectory.CMTimeEpoch, (Byte) values.get("epoch"));
                    directory.setLong(AppleRunTimeMakernoteDirectory.CMTimeScale, (Long) values.get("timescale"));
                    directory.setLong(AppleRunTimeMakernoteDirectory.CMTimeValue, (Long) values.get("value"));
                }
            } else if (flagsObject instanceof String) {
                byte flags = Byte.parseByte((String) flagsObject);
                if ((flags & 0x1) == 0x1) {
                    directory.setInt(AppleRunTimeMakernoteDirectory.CMTimeFlags, flags);
                    directory.setInt(AppleRunTimeMakernoteDirectory.CMTimeEpoch, Byte.parseByte((String) values.get("epoch")));
                    directory.setLong(AppleRunTimeMakernoteDirectory.CMTimeScale, Long.parseLong((String) values.get("timescale")));
                    directory.setLong(AppleRunTimeMakernoteDirectory.CMTimeValue, Long.parseLong((String) values.get("value")));
                }
            }
        }
    }
}
