/*
 * Copyright (c) 2009-2012 jMonkeyEngine
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package com.jme3.scene.plugins.blender.file;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme3.scene.plugins.blender.BlenderContext;

/**
 * A class that holds the header data of a file block. The file block itself is not implemented. This class holds its
 * start position in the stream and using this the structure can fill itself with the proper data.
 * @author Marcin Roguski
 */
public class FileBlockHeader {
    private static final Logger LOGGER = Logger.getLogger(FileBlockHeader.class.getName());

    /** Identifier of the file-block [4 bytes]. */
    private BlockCode           code;
    /** Total length of the data after the file-block-header [4 bytes]. */
    private int                 size;
    /**
     * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer
     * size)].
     */
    private long                oldMemoryAddress;
    /** Index of the SDNA structure [4 bytes]. */
    private int                 sdnaIndex;
    /** Number of structure located in this file-block [4 bytes]. */
    private int                 count;
    /** Start position of the block's data in the stream. */
    private int                 blockPosition;

    /**
     * Constructor. Loads the block header from the given stream during instance creation.
     * @param inputStream
     *            the stream we read the block header from
     * @param blenderContext
     *            the blender context
     * @throws BlenderFileException
     *             this exception is thrown when the pointer size is neither 4 nor 8
     */
    public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException {
        inputStream.alignPosition(4);
        code = BlockCode.valueOf(inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte());
        size = inputStream.readInt();
        oldMemoryAddress = inputStream.readPointer();
        sdnaIndex = inputStream.readInt();
        count = inputStream.readInt();
        blockPosition = inputStream.getPosition();
        if (BlockCode.BLOCK_DNA1 == code) {
            blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext));
        } else {
            inputStream.setPosition(blockPosition + size);
            blenderContext.addFileBlockHeader(Long.valueOf(oldMemoryAddress), this);
        }
    }

    /**
     * This method returns the structure described by the header filled with appropriate data.
     * @param blenderContext
     *            the blender context
     * @return structure filled with data
     * @throws BlenderFileException
     */
    public Structure getStructure(BlenderContext blenderContext) throws BlenderFileException {
        blenderContext.getInputStream().setPosition(blockPosition);
        Structure structure = blenderContext.getDnaBlockData().getStructure(sdnaIndex);
        structure.fill(blenderContext.getInputStream());
        return structure;
    }

    /**
     * This method returns the code of this data block.
     * @return the code of this data block
     */
    public BlockCode getCode() {
        return code;
    }

    /**
     * This method returns the size of the data stored in this block.
     * @return the size of the data stored in this block
     */
    public int getSize() {
        return size;
    }

    /**
     * This method returns the sdna index.
     * @return the sdna index
     */
    public int getSdnaIndex() {
        return sdnaIndex;
    }

    /**
     * This data returns the number of structure stored in the data block after this header.
     * @return the number of structure stored in the data block after this header
     */
    public int getCount() {
        return count;
    }

    /**
     * This method returns the start position of the data block in the blend file stream.
     * @return the start position of the data block
     */
    public int getBlockPosition() {
        return blockPosition;
    }

    /**
     * This method indicates if the block is the last block in the file.
     * @return true if this block is the last one in the file nad false otherwise
     */
    public boolean isLastBlock() {
        return BlockCode.BLOCK_ENDB == code;
    }

    /**
     * This method indicates if the block is the SDNA block.
     * @return true if this block is the SDNA block and false otherwise
     */
    public boolean isDnaBlock() {
        return BlockCode.BLOCK_DNA1 == code;
    }

    @Override
    public String toString() {
        return "FILE BLOCK HEADER [" + code.toString() + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]";
    }

    public static enum BlockCode {
        BLOCK_ME00('M' << 24 | 'E' << 16), // mesh
        BLOCK_CA00('C' << 24 | 'A' << 16), // camera
        BLOCK_LA00('L' << 24 | 'A' << 16), // lamp
        BLOCK_OB00('O' << 24 | 'B' << 16), // object
        BLOCK_MA00('M' << 24 | 'A' << 16), // material
        BLOCK_SC00('S' << 24 | 'C' << 16), // scene
        BLOCK_WO00('W' << 24 | 'O' << 16), // world
        BLOCK_TX00('T' << 24 | 'X' << 16), // texture
        BLOCK_IP00('I' << 24 | 'P' << 16), // ipo
        BLOCK_AC00('A' << 24 | 'C' << 16), // action
        BLOCK_IM00('I' << 24 | 'M' << 16), // image
        BLOCK_TE00('T' << 24 | 'E' << 16), 
        BLOCK_WM00('W' << 24 | 'M' << 16), 
        BLOCK_SR00('S' << 24 | 'R' << 16), 
        BLOCK_SN00('S' << 24 | 'N' << 16), 
        BLOCK_BR00('B' << 24 | 'R' << 16), 
        BLOCK_LS00('L' << 24 | 'S' << 16), 
        BLOCK_GR00('G' << 24 | 'R' << 16), 
        BLOCK_AR00('A' << 24 | 'R' << 16), 
        BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'), 
        BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'), 
        BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'), 
        BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'),
        BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'), 
        BLOCK_TEST('T' << 24 | 'E' << 16 | 'S' << 8 | 'T'), 
        BLOCK_UNKN(0);

        private int code;

        private BlockCode(int code) {
            this.code = code;
        }

        public static BlockCode valueOf(int code) {
            for (BlockCode blockCode : BlockCode.values()) {
                if (blockCode.code == code) {
                    return blockCode;
                }
            }
            byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) };
			for (int i = 0; i < codeBytes.length; ++i) {
				if (codeBytes[i] == 0) {
					codeBytes[i] = '0';
				}
			}
			LOGGER.log(Level.WARNING, "Unknown block header: {0}", new String(codeBytes));
            return BLOCK_UNKN;
        }
    }
}
