/*
 * Copyright (c) 2009-2013 Panxiaobo
 * 
 * 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 pxb.android.axml;

import pxb.android.ResConst;
import pxb.android.StringItems;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;

import static pxb.android.axml.NodeVisitor.TYPE_INT_BOOLEAN;
import static pxb.android.axml.NodeVisitor.TYPE_STRING;

/**
 * a class to read android axml
 *
 * @author <a href="mailto:pxb1988@gmail.com">Panxiaobo</a>
 */
public class AxmlParser implements ResConst {

    public static final int END_FILE = 7;
    public static final int END_NS = 5;
    public static final int END_TAG = 3;
    public static final int START_FILE = 1;
    public static final int START_NS = 4;
    public static final int START_TAG = 2;
    public static final int TEXT = 6;
    // private int attrName[];
    // private int attrNs[];
    // private int attrResId[];
    // private int attrType[];
    // private Object attrValue[];

    private int attributeCount;

    private IntBuffer attrs;

    private int classAttribute;
    private int fileSize = -1;
    private int idAttribute;
    private ByteBuffer in;
    private int lineNumber;
    private int nameIdx;
    private int nsIdx;

    private int prefixIdx;

    private int[] resourceIds;

    private String[] strings;

    private int styleAttribute;

    private int textIdx;

    public AxmlParser(byte[] data) {
        this(ByteBuffer.wrap(data));
    }

    public AxmlParser(ByteBuffer in) {
        super();
        this.in = in.order(ByteOrder.LITTLE_ENDIAN);
    }

    public int getAttrCount() {
        return attributeCount;
    }

    public int getAttributeCount() {
        return attributeCount;
    }

    public String getAttrName(int i) {
        int idx = attrs.get(i * 5 + 1);
        return strings[idx];

    }

    public String getAttrNs(int i) {
        int idx = attrs.get(i * 5 + 0);
        return idx >= 0 ? strings[idx] : null;
    }

    String getAttrRawString(int i) {
        int idx = attrs.get(i * 5 + 2);
        if (idx >= 0) {
            return strings[idx];
        }
        return null;
    }

    public int getAttrResId(int i) {
        if (resourceIds != null) {
            int idx = attrs.get(i * 5 + 1);
            if (idx >= 0 && idx < resourceIds.length) {
                return resourceIds[idx];
            }
        }
        return -1;
    }

    public int getAttrType(int i) {
        return attrs.get(i * 5 + 3) >> 24;
    }

    public Object getAttrValue(int i) {
        int v = attrs.get(i * 5 + 4);

        if (i == idAttribute) {
            return ValueWrapper.wrapId(v, getAttrRawString(i));
        } else if (i == styleAttribute) {
            return ValueWrapper.wrapStyle(v, getAttrRawString(i));
        } else if (i == classAttribute) {
            return ValueWrapper.wrapClass(v, getAttrRawString(i));
        }

        switch (getAttrType(i)) {
            case TYPE_STRING:
                return strings[v];
            case TYPE_INT_BOOLEAN:
                return v != 0;
            default:
                return v;
        }
    }

    public int getLineNumber() {
        return lineNumber;
    }

    public String getName() {
        return strings[nameIdx];
    }

    public String getNamespacePrefix() {
        return strings[prefixIdx];
    }

    public String getNamespaceUri() {
        return nsIdx >= 0 ? strings[nsIdx] : null;
    }

    public String getText() {
        return strings[textIdx];
    }

    public int next() throws IOException {
        if (fileSize < 0) {
            int type = in.getInt() & 0xFFFF;
            if (type != RES_XML_TYPE) {
                throw new RuntimeException();
            }
            fileSize = in.getInt();
            return START_FILE;
        }
        int event = -1;
        for (int p = in.position(); p < fileSize; p = in.position()) {
            int type = in.getInt() & 0xFFFF;
            int size = in.getInt();
            switch (type) {
                case RES_XML_START_ELEMENT_TYPE: {
                    {
                        lineNumber = in.getInt();
                        in.getInt();/* skip, 0xFFFFFFFF */
                        nsIdx = in.getInt();
                        nameIdx = in.getInt();
                        int flag = in.getInt();// 0x00140014 ?
                        if (flag != 0x00140014) {
                            throw new RuntimeException();
                        }
                    }

                    attributeCount = in.getShort() & 0xFFFF;
                    idAttribute = (in.getShort() & 0xFFFF) - 1;
                    classAttribute = (in.getShort() & 0xFFFF) - 1;
                    styleAttribute = (in.getShort() & 0xFFFF) - 1;

                    attrs = in.asIntBuffer();

                    // attrResId = new int[attributeCount];
                    // attrName = new int[attributeCount];
                    // attrNs = new int[attributeCount];
                    // attrType = new int[attributeCount];
                    // attrValue = new Object[attributeCount];
                    // for (int i = 0; i < attributeCount; i++) {
                    // int attrNsIdx = in.getInt();
                    // int attrNameIdx = in.getInt();
                    // int raw = in.getInt();
                    // int aValueType = in.getInt() >>> 24;
                    // int aValue = in.getInt();
                    // Object value = null;
                    // switch (aValueType) {
                    // case TYPE_STRING:
                    // value = strings[aValue];
                    // break;
                    // case TYPE_INT_BOOLEAN:
                    // value = aValue != 0;
                    // break;
                    // default:
                    // value = aValue;
                    // }
                    // int resourceId = attrNameIdx < this.resourceIds.length ?
                    // resourceIds[attrNameIdx] : -1;
                    // attrNs[i] = attrNsIdx;
                    // attrName[i] = attrNameIdx;
                    // attrType[i] = aValueType;
                    // attrResId[i] = resourceId;
                    // attrValue[i] = value;
                    // }
                    event = START_TAG;
                }
                break;
                case RES_XML_END_ELEMENT_TYPE: {
                    in.position(p + size);
                    event = END_TAG;
                }
                break;
                case RES_XML_START_NAMESPACE_TYPE:
                    lineNumber = in.getInt();
                    in.getInt();/* 0xFFFFFFFF */
                    prefixIdx = in.getInt();
                    nsIdx = in.getInt();
                    event = START_NS;
                    break;
                case RES_XML_END_NAMESPACE_TYPE:
                    in.position(p + size);
                    event = END_NS;
                    break;
                case RES_STRING_POOL_TYPE:
                    strings = StringItems.read(in);
                    in.position(p + size);
                    continue;
                case RES_XML_RESOURCE_MAP_TYPE:
                    int count = size / 4 - 2;
                    resourceIds = new int[count];
                    for (int i = 0; i < count; i++) {
                        resourceIds[i] = in.getInt();
                    }
                    in.position(p + size);
                    continue;
                case RES_XML_CDATA_TYPE:
                    lineNumber = in.getInt();
                    in.getInt();/* 0xFFFFFFFF */
                    textIdx = in.getInt();

                    in.getInt();/* 00000008 00000000 */
                    in.getInt();

                    event = TEXT;
                    break;
                default:
                    throw new RuntimeException();
            }
            in.position(p + size);
            return event;
        }
        return END_FILE;
    }
}
