/*
 * Copyright 2011 castLabs, Berlin
 *
 * 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
 *
 *     http://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.
 */

package com.googlecode.mp4parser.boxes.mp4.objectdescriptors;

import com.coremedia.iso.IsoTypeReader;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/* class tag values of 14496-1
0x00 Forbidden
0x01 ObjectDescrTag
0x02 InitialObjectDescrTag
0x03 ES_DescrTag
0x04 DecoderConfigDescrTag
0x05 DecSpecificInfoTag
0x06 SLConfigDescrTag
0x07 ContentIdentDescrTag
0x08 SupplContentIdentDescrTag
0x09 IPI_DescrPointerTag
0x0A IPMP_DescrPointerTag
0x0B IPMP_DescrTag
0x0C QoS_DescrTag
0x0D RegistrationDescrTag
0x0E ES_ID_IncTag
0x0F ES_ID_RefTag
0x10 MP4_IOD_Tag
0x11 MP4_OD_Tag
0x12 IPL_DescrPointerRefTag
0x13 ExtensionProfileLevelDescrTag
0x14 profileLevelIndicationIndexDescrTag
0x15-0x3F Reserved for ISO use
0x40 ContentClassificationDescrTag
0x41 KeyWordDescrTag
0x42 RatingDescrTag
0x43 LanguageDescrTag
0x44 ShortTextualDescrTag
0x45 ExpandedTextualDescrTag
0x46 ContentCreatorNameDescrTag
0x47 ContentCreationDateDescrTag
0x48 OCICreatorNameDescrTag
0x49 OCICreationDateDescrTag
0x4A SmpteCameraPositionDescrTag
0x4B SegmentDescrTag
0x4C MediaTimeDescrTag
0x4D-0x5F Reserved for ISO use (OCI extensions)
0x60 IPMP_ToolsListDescrTag
0x61 IPMP_ToolTag
0x62 M4MuxTimingDescrTag
0x63 M4MuxCodeTableDescrTag
0x64 ExtSLConfigDescrTag
0x65 M4MuxBufferSizeDescrTag
0x66 M4MuxIdentDescrTag
0x67 DependencyPointerTag
0x68 DependencyMarkerTag
0x69 M4MuxChannelDescrTag
0x6A-0xBF Reserved for ISO use
0xC0-0xFE User private
0xFF Forbidden
 */

/* objectTypeIndication as of 14496-1
0x00 Forbidden
0x01 Systems ISO/IEC 14496-1 a
0x02 Systems ISO/IEC 14496-1 b
0x03 Interaction Stream
0x04 Systems ISO/IEC 14496-1 Extended BIFS Configuration c
0x05 Systems ISO/IEC 14496-1 AFX d
0x06 Font Data Stream
0x07 Synthesized Texture Stream
0x08 Streaming Text Stream
0x09-0x1F reserved for ISO use
0x20 Visual ISO/IEC 14496-2 e
0x21 Visual ITU-T Recommendation H.264 | ISO/IEC 14496-10 f
0x22 Parameter Sets for ITU-T Recommendation H.264 | ISO/IEC 14496-10 f
0x23-0x3F reserved for ISO use
0x40 Audio ISO/IEC 14496-3 g
0x41-0x5F reserved for ISO use
0x60 Visual ISO/IEC 13818-2 Simple Profile
0x61 Visual ISO/IEC 13818-2 Main Profile
0x62 Visual ISO/IEC 13818-2 SNR Profile
0x63 Visual ISO/IEC 13818-2 Spatial Profile
0x64 Visual ISO/IEC 13818-2 High Profile
0x65 Visual ISO/IEC 13818-2 422 Profile
0x66 Audio ISO/IEC 13818-7 Main Profile
0x67 Audio ISO/IEC 13818-7 LowComplexity Profile
0x68 Audio ISO/IEC 13818-7 Scaleable Sampling Rate Profile
0x69 Audio ISO/IEC 13818-3
0x6A Visual ISO/IEC 11172-2
0x6B Audio ISO/IEC 11172-3
0x6C Visual ISO/IEC 10918-1
0x6D reserved for registration authority
0x6E Visual ISO/IEC 15444-1
0x6F - 0x9F reserved for ISO use
0xA0 - 0xBF reserved for registration authority i
0xC0 - 0xE0 user private
0xE1 reserved for registration authority i
0xE2 - 0xFE user private
0xFF no object type specified h
 */
public class ObjectDescriptorFactory {
    protected static Logger log = Logger.getLogger(ObjectDescriptorFactory.class.getName());

    protected static Map<Integer, Map<Integer, Class<? extends BaseDescriptor>>> descriptorRegistry = new HashMap<Integer, Map<Integer, Class<? extends BaseDescriptor>>>();

    static {
        Set<Class<? extends BaseDescriptor>> annotated = new HashSet<Class<? extends BaseDescriptor>>();

        annotated.add(DecoderSpecificInfo.class);
        annotated.add(SLConfigDescriptor.class);
        annotated.add(BaseDescriptor.class);
        annotated.add(ExtensionDescriptor.class);
        annotated.add(ObjectDescriptorBase.class);
        annotated.add(ProfileLevelIndicationDescriptor.class);
        annotated.add(AudioSpecificConfig.class);
        annotated.add(ExtensionProfileLevelDescriptor.class);
        annotated.add(ESDescriptor.class);
        annotated.add(DecoderConfigDescriptor.class);
        //annotated.add(ObjectDescriptor.class);

        for (Class<? extends BaseDescriptor> clazz : annotated) {
            final Descriptor descriptor = clazz.getAnnotation(Descriptor.class);
            final int[] tags = descriptor.tags();
            final int objectTypeInd = descriptor.objectTypeIndication();

            Map<Integer, Class<? extends BaseDescriptor>> tagMap = descriptorRegistry.get(objectTypeInd);
            if (tagMap == null) {
                tagMap = new HashMap<Integer, Class<? extends BaseDescriptor>>();
            }
            for (int tag : tags) {
                tagMap.put(tag, clazz);
            }
            descriptorRegistry.put(objectTypeInd, tagMap);
        }
    }

    public static BaseDescriptor createFrom(int objectTypeIndication, ByteBuffer bb) throws IOException {
        int tag = IsoTypeReader.readUInt8(bb);

        Map<Integer, Class<? extends BaseDescriptor>> tagMap = descriptorRegistry.get(objectTypeIndication);
        if (tagMap == null) {
            tagMap = descriptorRegistry.get(-1);
        }
        Class<? extends BaseDescriptor> aClass = tagMap.get(tag);

//    if (tag == 0x00) {
//      log.warning("Found illegal tag 0x00! objectTypeIndication " + Integer.toHexString(objectTypeIndication) +
//              " and tag " + Integer.toHexString(tag) + " using: " + aClass);
//      aClass = BaseDescriptor.class;
//    }

        BaseDescriptor baseDescriptor;
        if (aClass == null || aClass.isInterface() || Modifier.isAbstract(aClass.getModifiers())) {
            log.warning("No ObjectDescriptor found for objectTypeIndication " + Integer.toHexString(objectTypeIndication) +
                    " and tag " + Integer.toHexString(tag) + " found: " + aClass);
            baseDescriptor = new UnknownDescriptor();
        } else {
            try {
                baseDescriptor = aClass.newInstance();
            } catch (Exception e) {
                log.log(Level.SEVERE, "Couldn't instantiate BaseDescriptor class " + aClass + " for objectTypeIndication " + objectTypeIndication + " and tag " + tag, e);
                throw new RuntimeException(e);
            }
        }

        //ByteBuffer orig = bb.slice();
        baseDescriptor.parse(tag, bb);
        //byte[] b1 = new byte[baseDescriptor.sizeOfInstance + baseDescriptor.sizeBytes];
        //orig.get(b1);
        //byte[] b2 = baseDescriptor.serialize().array();
        //System.err.println(baseDescriptor.getClass().getName() + " orig: " + Hex.encodeHex(b1) + " serialized: " + Hex.encodeHex(b2));

        return baseDescriptor;
    }
}
