/*
 * This software is distributed under following license based on modified BSD
 * style license.
 * ----------------------------------------------------------------------
 * 
 * Copyright 2009 The Nimbus2 Project. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer. 
 * 2. 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.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE NIMBUS PROJECT ``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 NIMBUS PROJECT 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.
 * 
 * The views and conclusions contained in the software and documentation are
 * those of the authors and should not be interpreted as representing official
 * policies, either expressed or implied, of the Nimbus2 Project.
 */
package jp.ossc.nimbus.service.publish.tcp;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectInput;
import java.io.OutputStream;
import java.io.ObjectOutputStream;
import java.io.ByteArrayOutputStream;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.HashSet;
import java.util.Collection;
import java.util.Iterator;

import jp.ossc.nimbus.service.publish.Message;
import jp.ossc.nimbus.service.publish.MessageException;
import jp.ossc.nimbus.service.io.Externalizer;

/**
 * TCPプロトコル用のメッセージクラス。<p>
 * 
 * @author M.Takata
 */
public class MessageImpl implements Message, Externalizable{
    
    private transient String subject;
    private Map<String, String> subjectMap = new LinkedHashMap<String, String>();
    private Object object;
    private transient byte[] bytes;
    private transient long sendTime;
    private transient long receiveTime;
    private boolean isServerClose;
    private transient Set<Object> destinationIds;
    
    public MessageImpl(){
    }
    
    public MessageImpl(boolean isServerClose){
        this.isServerClose = isServerClose;
    }
    
    @Override
    public String getSubject(){
        Set<String> subjects = getSubjects();
        if(subjects.size() == 0){
            return null;
        }else if(subject == null){
            subject = subjects.iterator().next();
        }
        return subject;
    }
    
    @Override
    public Set<String> getSubjects(){
        return subjectMap.keySet();
    }

    @Override
    public void setSubject(String sbj, String key){
        if(subjectMap.size() == 0){
            subject = sbj;
        }
        subjectMap.put(sbj, key);
    }

    @Override
    public String getKey(String sbj){
        return subjectMap.get(sbj);
    }
    
    @Override
    public String getKey(){
        return getKey(getSubject());
    }
    
    @Override
    public void setObject(Object obj) throws MessageException{
        object = obj;
    }
    
    @Override
    public Object getObject() throws MessageException{
        return object;
    }
    
    public long getSendTime(){
        return sendTime;
    }
    
    public void setSendTime(long time){
        sendTime = time;
    }
    
    @Override
    public long getReceiveTime(){
        return receiveTime;
    }
    
    public void setServerClose(boolean isServerClose){
        this.isServerClose = isServerClose;
    }
    
    public boolean isServerClose(){
        return isServerClose;
    }
    
    @Override
    public Set<Object> getDestinationIds(){
        return destinationIds;
    }
    
    @Override
    public void setDestinationIds(Set<Object> ids){
        destinationIds = ids;
    }
    
    @Override
    @SuppressWarnings("unchecked")
    public void addDestinationId(Object id){
        if(id == null){
            return;
        }
        if(destinationIds == null){
            destinationIds = new HashSet<Object>();
        }
        if(id instanceof Collection){
            destinationIds.addAll((Collection<Object>)id);
        }else{
            destinationIds.add(id);
        }
    }
    
    @Override
    public void removeDestinationId(Object id){
        if(destinationIds == null){
            return;
        }
        destinationIds.remove(id);
    }
    
    @Override
    public void clearDestinationIds(){
        if(destinationIds == null){
            return;
        }
        destinationIds.clear();
    }
    
    @Override
    public boolean containsDestinationId(Object id){
        if(destinationIds == null || destinationIds.size() == 0){
            return true;
        }
        if(id instanceof Collection){
            @SuppressWarnings("unchecked")
            Iterator<Object> itr = ((Collection<Object>)id).iterator();
            while(itr.hasNext()){
                if(destinationIds.contains(itr.next())){
                    return true;
                }
            }
            return false;
        }else{
            return destinationIds.contains(id);
        }
    }
    
    public synchronized void write(OutputStream out, Externalizer<Object> ext) throws IOException{
        if(bytes == null){
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            if(ext == null){
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(this);
                oos.flush();
            }else{
                ext.writeExternal(this, baos);
            }
            bytes = baos.toByteArray();
        }
        out.write(bytes);
    }
    
    public String toString(){
        StringBuilder buf = new StringBuilder();
        buf.append(super.toString());
        buf.append('{');
        buf.append("subjectMap=").append(subjectMap);
        buf.append(", object=").append(object);
        buf.append('}');
        return buf.toString();
    }
    
    public void writeExternal(ObjectOutput out) throws IOException{
        out.writeBoolean(isServerClose);
        out.writeObject(subjectMap);
        out.writeObject(object);
    }
    
    @SuppressWarnings("unchecked")
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException{
        isServerClose = in.readBoolean();
        subjectMap = (Map<String, String>)in.readObject();
        object = in.readObject();
        receiveTime = System.currentTimeMillis();
    }
}