/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

/* $Id$ */
package org.apache.fop.render.afp;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.junit.Assert;

import org.apache.fop.afp.AFPConstants;
import org.apache.fop.afp.ptoca.PtocaBuilder;

public class AFPParser {
    private boolean readText;
    protected boolean readWidths;
    public AFPParser(boolean readText) {
        this.readText = readText;
    }

    public void read(InputStream bis, StringBuilder sb) throws IOException {
        while (bis.available() > 0) {
            readField(bis, sb);
        }
    }

    private void readField(InputStream bis, StringBuilder sb) throws IOException {
        bis.read();
        int len = getLength(bis.read(), bis.read());
        byte[] field = new byte[len - 2];
        bis.read(field);
        InputStream fieldStream = new ByteArrayInputStream(field);
        fieldStream.read();
        byte type = (byte) fieldStream.read();
        byte category = (byte) fieldStream.read();
        fieldStream.skip(3);
        String typeStr = TYPE_MAP.get(type & 0xFF);
        String catStr = CATEGORY_MAP.get(category & 0xFF);
        if (typeStr != null && catStr != null) {
            sb.append(typeStr + " " + catStr);
            if (typeStr.equals("BEGIN") || typeStr.equals("END")) {
                byte[] name = new byte[8];
                fieldStream.read(name);
                sb.append(" " + new String(name, AFPConstants.EBCIDIC_ENCODING));
                fieldStream.skip(2);
                readTriplet(fieldStream, sb);
            } else if (typeStr.equals("MAP")) {
                fieldStream.skip(2);
                readTriplet(fieldStream, sb);
            } else if (typeStr.equals("DESCRIPTOR") && catStr.equals("OBJECT_AREA")) {
                readTriplet(fieldStream, sb);
            } else if (typeStr.equals("DATA") && catStr.equals("PRESENTATION_TEXT") && readText) {
                readData(fieldStream, sb);
            }
            sb.append("\n");
        }
    }

    private void readData(InputStream bis, StringBuilder sb) throws IOException {
        Assert.assertEquals(bis.read(), 0x2B);
        Assert.assertEquals(bis.read(), 0xD3);
        while (bis.available() > 0) {
            int len = bis.read();
            int functionType = bis.read();

            sb.append(" " + PTOCA_MAP.get(functionType));

            if (readWidths && "AMI".equals(PTOCA_MAP.get(functionType))) {
                byte[] data = new byte[len - 2];
                bis.read(data);
                sb.append(" ").append(data[1] & 0xFF);
            } else if ("TRN".equals(PTOCA_MAP.get(functionType))) {
                byte[] data = new byte[len - 2];
                bis.read(data);
                sb.append(" " + new String(data, "UTF-16BE"));
            } else {
                bis.skip(len - 2);
            }
        }
    }

    private void readTriplet(InputStream des, StringBuilder sb) throws IOException {
        if (des.available() > 0) {
            sb.append(" Triplets: ");
        }
        while (des.available() > 0) {
            int len2 = des.read();
            int id = des.read();
            int b = id & 0xFF;
            if (TRIPLET_MAP.containsKey(b)) {
                sb.append(TRIPLET_MAP.get(b) + ",");
            } else {
                sb.append(String.format("0x%02X,", b));
            }
            des.skip(len2 - 2);
        }
    }

    private int getLength(int a, int b) {
        return (a * 256) + b;
    }

    private static final Map<Integer, String> TYPE_MAP = new HashMap<Integer, String>();
    private static final Map<Integer, String> CATEGORY_MAP = new HashMap<Integer, String>();
    private static final Map<Integer, String> TRIPLET_MAP = new HashMap<Integer, String>();
    private static final Map<Integer, String> PTOCA_MAP = new HashMap<Integer, String>();
    static {
        PTOCA_MAP.put(0xC2 | PtocaBuilder.CHAIN_BIT, "SIA");
        PTOCA_MAP.put(0xC4 | PtocaBuilder.CHAIN_BIT, "SVI");
        PTOCA_MAP.put(0xC6 | PtocaBuilder.CHAIN_BIT, "AMI");
        PTOCA_MAP.put(0xC8 | PtocaBuilder.CHAIN_BIT, "RMI");
        PTOCA_MAP.put(0xD2 | PtocaBuilder.CHAIN_BIT, "AMB");
        PTOCA_MAP.put(0xDA | PtocaBuilder.CHAIN_BIT, "TRN");
        PTOCA_MAP.put(0xE4 | PtocaBuilder.CHAIN_BIT, "DIR");
        PTOCA_MAP.put(0xE6 | PtocaBuilder.CHAIN_BIT, "DBR");
        PTOCA_MAP.put(0x80 | PtocaBuilder.CHAIN_BIT, "SEC");
        PTOCA_MAP.put(0xF0 | PtocaBuilder.CHAIN_BIT, "SCFL");
        PTOCA_MAP.put(0xF6 | PtocaBuilder.CHAIN_BIT, "STO");
        PTOCA_MAP.put(0xF8 | PtocaBuilder.CHAIN_BIT, "NOP");

        TYPE_MAP.put(0xA0, "ATTRIBUTE");
        TYPE_MAP.put(0xA2, "COPY_COUNT");
        TYPE_MAP.put(0xA6, "DESCRIPTOR");
        TYPE_MAP.put(0xA7, "CONTROL");
        TYPE_MAP.put(0xA8, "BEGIN");
        TYPE_MAP.put(0xA9, "END");
        TYPE_MAP.put(0xAB, "MAP");
        TYPE_MAP.put(0xAC, "POSITION");
        TYPE_MAP.put(0xAD, "PROCESS");
        TYPE_MAP.put(0xAF, "INCLUDE");
        TYPE_MAP.put(0xB0, "TABLE");
        TYPE_MAP.put(0xB1, "MIGRATION");
        TYPE_MAP.put(0xB2, "VARIABLE");
        TYPE_MAP.put(0xB4, "LINK");
        TYPE_MAP.put(0xEE, "DATA");

        CATEGORY_MAP.put(0x5F, "PAGE_SEGMENT");
        CATEGORY_MAP.put(0x6B, "OBJECT_AREA");
        CATEGORY_MAP.put(0x77, "COLOR_ATTRIBUTE_TABLE");
        CATEGORY_MAP.put(0x7B, "IM_IMAGE");
        CATEGORY_MAP.put(0x88, "MEDIUM");
        CATEGORY_MAP.put(0x8A, "CODED_FONT");
        CATEGORY_MAP.put(0x90, "PROCESS_ELEMENT");
        CATEGORY_MAP.put(0x92, "OBJECT_CONTAINER");
        CATEGORY_MAP.put(0x9B, "PRESENTATION_TEXT");
        CATEGORY_MAP.put(0xA7, "INDEX");
        CATEGORY_MAP.put(0xA8, "DOCUMENT");
        CATEGORY_MAP.put(0xAD, "PAGE_GROUP");
        CATEGORY_MAP.put(0xAF, "PAGE");
        CATEGORY_MAP.put(0xBB, "GRAPHICS");
        CATEGORY_MAP.put(0xC3, "DATA_RESOURCE");
        CATEGORY_MAP.put(0xC4, "DOCUMENT_ENVIRONMENT_GROUP");
        CATEGORY_MAP.put(0xC6, "RESOURCE_GROUP");
        CATEGORY_MAP.put(0xC7, "OBJECT_ENVIRONMENT_GROUP");
        CATEGORY_MAP.put(0xC9, "ACTIVE_ENVIRONMENT_GROUP");
        CATEGORY_MAP.put(0xCC, "MEDIUM_MAP");
        CATEGORY_MAP.put(0xCD, "FORM_MAP");
        CATEGORY_MAP.put(0xCE, "NAME_RESOURCE");
        CATEGORY_MAP.put(0xD8, "PAGE_OVERLAY");
        CATEGORY_MAP.put(0xD9, "RESOURCE_ENVIROMENT_GROUP");
        CATEGORY_MAP.put(0xDF, "OVERLAY");
        CATEGORY_MAP.put(0xEA, "DATA_SUPRESSION");
        CATEGORY_MAP.put(0xEB, "BARCODE");
        CATEGORY_MAP.put(0xEE, "NO_OPERATION");
        CATEGORY_MAP.put(0xFB, "IMAGE");

        TRIPLET_MAP.put(0x02, "FULLY_QUALIFIED_NAME");
        TRIPLET_MAP.put(0x04, "MAPPING_OPTION");
        TRIPLET_MAP.put(0x10, "OBJECT_CLASSIFICATION");
        TRIPLET_MAP.put(0x18, "MODCA_INTERCHANGE_SET");
        TRIPLET_MAP.put(0x1F, "FONT_DESCRIPTOR_SPECIFICATION");
        TRIPLET_MAP.put(0x21, "OBJECT_FUNCTION_SET_SPECIFICATION");
        TRIPLET_MAP.put(0x22, "EXTENDED_RESOURCE_LOCAL_IDENTIFIER");
        TRIPLET_MAP.put(0x24, "RESOURCE_LOCAL_IDENTIFIER");
        TRIPLET_MAP.put(0x25, "RESOURCE_SECTION_NUMBER");
        TRIPLET_MAP.put(0x26, "CHARACTER_ROTATION");
        TRIPLET_MAP.put(0x2D, "OBJECT_BYTE_OFFSET");
        TRIPLET_MAP.put(0x36, "ATTRIBUTE_VALUE");
        TRIPLET_MAP.put(0x43, "DESCRIPTOR_POSITION");
        TRIPLET_MAP.put(0x45, "MEDIA_EJECT_CONTROL");
        TRIPLET_MAP.put(0x46, "PAGE_OVERLAY_CONDITIONAL_PROCESSING");
        TRIPLET_MAP.put(0x47, "RESOURCE_USAGE_ATTRIBUTE");
        TRIPLET_MAP.put(0x4B, "MEASUREMENT_UNITS");
        TRIPLET_MAP.put(0x4C, "OBJECT_AREA_SIZE");
        TRIPLET_MAP.put(0x4D, "AREA_DEFINITION");
        TRIPLET_MAP.put(0x4E, "COLOR_SPECIFICATION");
        TRIPLET_MAP.put(0x50, "ENCODING_SCHEME_ID");
        TRIPLET_MAP.put(0x56, "MEDIUM_MAP_PAGE_NUMBER");
        TRIPLET_MAP.put(0x57, "OBJECT_BYTE_EXTENT");
        TRIPLET_MAP.put(0x58, "OBJECT_STRUCTURED_FIELD_OFFSET");
        TRIPLET_MAP.put(0x59, "OBJECT_STRUCTURED_FIELD_EXTENT");
        TRIPLET_MAP.put(0x5A, "OBJECT_OFFSET");
        TRIPLET_MAP.put(0x5D, "FONT_HORIZONTAL_SCALE_FACTOR");
        TRIPLET_MAP.put(0x5E, "OBJECT_COUNT");
        TRIPLET_MAP.put(0x62, "OBJECT_DATE_AND_TIMESTAMP");
        TRIPLET_MAP.put(0x65, "COMMENT");
        TRIPLET_MAP.put(0x68, "MEDIUM_ORIENTATION");
        TRIPLET_MAP.put(0x6C, "RESOURCE_OBJECT_INCLUDE");
        TRIPLET_MAP.put(0x70, "PRESENTATION_SPACE_RESET_MIXING");
        TRIPLET_MAP.put(0x71, "PRESENTATION_SPACE_MIXING_RULE");
        TRIPLET_MAP.put(0x72, "UNIVERSAL_DATE_AND_TIMESTAMP");
        TRIPLET_MAP.put(0x74, "TONER_SAVER");
        TRIPLET_MAP.put(0x75, "COLOR_FIDELITY");
        TRIPLET_MAP.put(0x78, "FONT_FIDELITY");
        TRIPLET_MAP.put(0x80, "ATTRIBUTE_QUALIFIER");
        TRIPLET_MAP.put(0x81, "PAGE_POSITION_INFORMATION");
        TRIPLET_MAP.put(0x82, "PARAMETER_VALUE");
        TRIPLET_MAP.put(0x83, "PRESENTATION_CONTROL");
        TRIPLET_MAP.put(0x84, "FONT_RESOLUTION_AND_METRIC_TECHNOLOGY");
        TRIPLET_MAP.put(0x85, "FINISHING_OPERATION");
        TRIPLET_MAP.put(0x86, "TEXT_FIDELITY");
        TRIPLET_MAP.put(0x87, "MEDIA_FIDELITY");
        TRIPLET_MAP.put(0x88, "FINISHING_FIDELITY");
        TRIPLET_MAP.put(0x8B, "DATA_OBJECT_FONT_DESCRIPTOR");
        TRIPLET_MAP.put(0x8C, "LOCALE_SELECTOR");
        TRIPLET_MAP.put(0x8E, "UP3I_FINISHING_OPERATION");
        TRIPLET_MAP.put(0x91, "COLOR_MANAGEMENT_RESOURCE_DESCRIPTOR");
        TRIPLET_MAP.put(0x95, "RENDERING_INTENT");
        TRIPLET_MAP.put(0x96, "CMR_TAG_FIDELITY");
        TRIPLET_MAP.put(0x97, "DEVICE_APPEARANCE");
    }
}
