/* Copyright (C) 2009  CSE,IIT Bombay  http://www.cse.iitb.ac.in

This file is part of the ConStore open source storage facility for concept-nets.

ConStore is free software and distributed under the 
Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License;
you can copy, distribute and transmit the work
with the work attribution in the manner specified by the author or licensor.
You may not use this work for commercial purposes and may not alter, 
transform, or build upon this work.

Please refer the legal code of the license, available at
http://creativecommons.org/licenses/by-nc-nd/3.0/legalcode

ConStore is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  */

package iitb.con.ds;

import iitb.con.core.Attribute;
import iitb.con.core.Entity;
import iitb.con.core.Relation;
import iitb.con.core.Type;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * TypeSerializer serializes <tt>Type</tt> to bytes and vice versa
 * 
 * @author Prathab K
 * @see Type
 */
public class TypeSerializer implements ItemSerializer<Type> {
    
    /**
     * Serializes <tt>Type</tt> to bytes
     * @param type Type
     * @return serialized <tt>Type</tt> as {@link ByteBuffer}
     */
    public ByteBuffer serialize(Type type) {

        int typeElementSize = computeSize(type);
        ByteBuffer buf = ByteBuffer.allocate(typeElementSize);
        
        //Type Element Size
        buf.putInt(typeElementSize);
        
        if(type instanceof Entity) {
            buf.put((byte)0); //for category 0-entity; 1-relation 
        }else {
            Relation r = (Relation) type;
            buf.put((byte)1); //for category
            buf.putShort(r.getLeftEntityId()); //left entity id
            buf.putShort(r.getRightEntityId()); //right entity id
            buf.put(r.getDirection()); //direction 
            if(r.leftRole != null) {
                buf.putShort((short) r.leftRole.length()); //left role length
                buf.put(r.leftRole.getBytes()); //left role length
            }else{
                buf.putShort((short)0);//left role length
            }
            if(r.rightRole != null) {
                buf.putShort((short) r.rightRole.length()); //right role length
                buf.put(r.rightRole.getBytes()); //right role length
            }else{
                buf.putShort((short)0);//right role length
            }
        }
        
        buf.putShort(type.id); //type id
        buf.putShort((short) type.name.length()); //type name length
        buf.put(type.name.getBytes()); //type name
        
        buf.putShort((short)type.getAttributesCount());
        
        List<Attribute> attributes = type.getAllAttributes();
        
        for(Attribute attribute : attributes) {
            buf.putShort(attribute.id);
            if(attribute.repeating)
                buf.put((byte)1);
            else
                buf.put((byte)0);
            buf.put(attribute.dataType);
            //buf.put(attribute.position);
            buf.putShort((short)attribute.name.length());
            buf.put(attribute.name.getBytes());
            if(attribute.defaultValue != null) {
                buf.putShort((short)attribute.defaultValue.length());
                buf.put(attribute.defaultValue.getBytes());
            }else{
                buf.putShort((short)0);
            }
        }
        
        buf.rewind();
        return buf;
    }
    
    /**
     * Deserializes the given bytes as <tt>Type</tt>
     * @param buf bytes as {@link ByteBuffer}
     * @return deserialized bytes as <tt>Type</tt>
     */
    public Type deSerialize(ByteBuffer buf) {
        Type type = null;
        //buf.position(0);
        buf.getInt(); // getting the total element size
        Byte b = buf.get();
        if(b == 0) { // checking for category 0-entity; 1-relation 
            type = deSerializeEntityType(buf);
        }else {
            type = deSerializeRelationType(buf);
        }
        
        return type;
    }
    
    /**
     * Deserializes the specified attribute to object value
     * @param buf bytes as {@link ByteBuffer}
     * @param name attribute name
     * @return deserialized attribute value as <tt>Object</tt>
     */
    public Object attributeDeSerialize(ByteBuffer buf, Object name) {
        return null;
    }
    
    /**
     * Deserializes the Entity type
     * @param buf bytes as {@link ByteBuffer}
     * @return Entity Type
     * @see Entity
     */
    private Type deSerializeEntityType(ByteBuffer buf) {
        Entity type = new Entity("");
        
        type.id = buf.getShort();
        
        short size = buf.getShort();
        byte[] tempBuf = new byte[size];

        buf.get(tempBuf);
        type.name = new String(tempBuf);
        
        type.setAllAttributes(deSerializeAttributes(buf));
        
        return type;
    }
    
    /**
     * Deserializes the Relation type
     * @param buf bytes as {@link ByteBuffer}
     * @return Relation Type
     * @see Relation
     */
    private Type deSerializeRelationType(ByteBuffer buf) {
        Relation type = new Relation("");
        
        type.setLeftEntityId(buf.getShort());
        type.setRightEntityId(buf.getShort());
        type.setDirection(buf.get());
        
        short size = buf.getShort();
        byte[] tempBuf = new byte[size];
        buf.get(tempBuf);
        type.leftRole = new String(tempBuf);
        
        size = buf.getShort();
        tempBuf = new byte[size];
        buf.get(tempBuf);
        type.rightRole = new String(tempBuf);
        
        type.id = buf.getShort();
        
        size = buf.getShort();
        tempBuf = new byte[size];
        buf.get(tempBuf);
        type.name = new String(tempBuf);
        
        type.setAllAttributes(deSerializeAttributes(buf));
        
        return type;
    }
    
    /**
     * Deserializes the type attributes
     * @param buf bytes as {@link ByteBuffer}
     * @return Attribute List
     * @see Attribute
     */
    private List<Attribute> deSerializeAttributes(ByteBuffer buf) {
        
        short attribute_count = buf.getShort();
        
        List<Attribute> attributes = new ArrayList<Attribute>();
        for(short i = 0; i < attribute_count ; i++ ) {
            Attribute attribute = new Attribute();
            attribute.id = buf.getShort();
            if (buf.get() == 0)
                attribute.repeating = false;
            else
                attribute.repeating = true;
            
            attribute.dataType = buf.get();
            //attribute.position = buf.get();
            
            short size = buf.getShort();
            byte[] tempBuf = new byte[size];
            buf.get(tempBuf);
            attribute.name = new String(tempBuf);
            
            size = buf.getShort(); 
            if(size > 0) {
                tempBuf = new byte[size];
                buf.get(tempBuf);
                attribute.defaultValue = new String(tempBuf);
            }
            
            attributes.add(attribute);
        }
        return attributes;
    }
    
    /**
     * Computes the size of the given Type in terms of bytes
     * @param type Type
     * @return size in bytes
     */
    private int computeSize(Type type) {
        int size = 0;
        
        size += 2; // no. of attributes size 
        
        //id(2) + repeating(1) + data type(1) + position (1) + name size(2) + value(2) = 9 bytes
        short attributeStaticHeaderSize = 9;
        List<Attribute> attributes = type.getAllAttributes();
        
        for(Attribute attribute : attributes) {
            size += attribute.name.length();
            if(attribute.defaultValue !=null)
                size += attribute.defaultValue.length();
            size += attributeStaticHeaderSize;
        }
        
        //type id(2) + type name size(2) = 4 bytes
        size += 4;
        
        size += type.name.length();
        
        if(type instanceof Entity) {
            size++; //for category 
        }else {
            Relation r = (Relation) type;
            //catg(1) + left entity id(2) + right entity id (2) 
            //direction(1) + left role size(2) + right role size(2) = 10 bytes
            size += 10;
            
            if(r.leftRole != null)
                size += r.leftRole.length();
            if(r.rightRole != null)
                size += r.rightRole.length();
        }
        
        //for type element size (4 bytes)
        size += 4;
        return size;
    }
}