/*
 * 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.journal;

import java.util.Date;
import java.util.Map;
import java.util.HashMap;

import jp.ossc.nimbus.core.ServiceBase;
import jp.ossc.nimbus.core.ServiceName;
import jp.ossc.nimbus.core.ServiceManagerFactory;
import jp.ossc.nimbus.daemon.Daemon;
import jp.ossc.nimbus.daemon.DaemonControl;
import jp.ossc.nimbus.daemon.DaemonRunnable;
import jp.ossc.nimbus.service.writer.Category;
import jp.ossc.nimbus.service.writer.MessageWriteException;
import jp.ossc.nimbus.service.queue.QueueHandlerContainer;
import jp.ossc.nimbus.service.queue.QueueHandler;
import jp.ossc.nimbus.service.queue.Queue;
import jp.ossc.nimbus.service.queue.DefaultQueueService;
import jp.ossc.nimbus.service.sequence.Sequence;

/**
 * {@link Journal}インタフェースのデフォルト実装サービス。<p>
 * 
 * @author M.Takata
 */
public class DefaultJournalService extends ServiceBase
 implements Journal, DefaultJournalServiceMBean{
    
    private static final long serialVersionUID = -6452029521438733505L;
    
    protected ServiceName editorFinderServiceName;
    protected EditorFinder editorFinder;
    protected ThreadLocal<JournalRecord> localRecord;
    
    protected ServiceName[] categoryServiceNames;
    protected Category[] categories;
    
    protected ServiceName queueServiceName;
    protected Queue<Object> queue;
    protected Daemon categoryWriterDaemon;
    
    protected ServiceName queueHandlerContainerServiceName;
    protected QueueHandlerContainer<Object> queueHandlerContainer;
    
    protected ServiceName sequenceServiceName;
    protected Sequence sequence;
    
    protected String writableElementKey = "JOURNAL";
    
    @Override
    public void setEditorFinderServiceName(ServiceName name){
        editorFinderServiceName = name;
    }
    @Override
    public ServiceName getEditorFinderServiceName(){
        return editorFinderServiceName;
    }
    
    @Override
    public void setCategoryServiceNames(ServiceName[] names){
        categoryServiceNames = names;
    }
    @Override
    public ServiceName[] getCategoryServiceNames(){
        return categoryServiceNames;
    }
    
    @Override
    public void setQueueServiceName(ServiceName name){
        queueServiceName = name;
    }
    @Override
    public ServiceName getQueueServiceName(){
        return queueServiceName;
    }
    
    @Override
    public void setQueueHandlerContainerServiceName(ServiceName name){
        queueHandlerContainerServiceName = name;
    }
    @Override
    public ServiceName getQueueHandlerContainerServiceName(){
        return queueHandlerContainerServiceName;
    }
    
    @Override
    public void setSequenceServiceName(ServiceName name){
        sequenceServiceName = name;
    }
    @Override
    public ServiceName getSequenceServiceName(){
        return sequenceServiceName;
    }
    
    @Override
    public void setWritableElementKey(String key){
        writableElementKey = key;
    }
    @Override
    public String getWritableElementKey(){
        return writableElementKey;
    }
    
    public void setEditorFinder(EditorFinder finder){
        editorFinder = finder;
    }
    public EditorFinder getEditorFinder(){
        return editorFinder;
    }
    
    public void setCategories(Category[] categories){
        this.categories = categories;
    }
    public Category[] getCategories(){
        return categories;
    }
    
    public void setQueue(Queue<Object> q){
        queue = q;
    }
    public Queue<Object> getQueue(){
        return queue;
    }
    
    public void setQueueHandlerContainer(QueueHandlerContainer<Object> qhc){
        queueHandlerContainer = qhc;
    }
    public QueueHandlerContainer<Object> getQueueHandlerContainer(){
        return queueHandlerContainer;
    }
    
    public void setSequence(Sequence seq){
        sequence = seq;
    }
    public Sequence getSequence(){
        return sequence;
    }
    
    @Override
    public void startService() throws Exception{
        
        localRecord = new ThreadLocal<JournalRecord>();
        
        if(editorFinder == null && editorFinderServiceName == null){
            throw new IllegalArgumentException("EditorFinder is null.");
        }
        if(editorFinderServiceName != null){
            editorFinder = ServiceManagerFactory.getServiceObject(
                editorFinderServiceName
            );
        }
        
        if((categoryServiceNames == null || categoryServiceNames.length == 0)
            && (categories == null || categories.length == 0)
        ){
            throw new IllegalArgumentException("Categories is null.");
        }
        
        if(categoryServiceNames != null){
            categories = new Category[categoryServiceNames.length];
            for(int i = 0; i < categoryServiceNames.length; i++){
                categories[i] = ServiceManagerFactory.getServiceObject(
                    categoryServiceNames[i]
                );
            }
        }
        
        if(queueHandlerContainerServiceName != null){
            queueHandlerContainer = ServiceManagerFactory.getServiceObject(
                queueHandlerContainerServiceName
            );
            queue = queueHandlerContainer;
        }
        
        if(queueHandlerContainer == null && queueServiceName != null){
            queue = ServiceManagerFactory.getServiceObject(
                queueServiceName
            );
        }
        if(queue == null){
            DefaultQueueService<Object> q = new DefaultQueueService<Object>();
            q.create();
            q.start();
            queue = q;
        }
        
        queue.accept();
        
        if(sequenceServiceName != null){
            sequence = ServiceManagerFactory.getServiceObject(
                sequenceServiceName
            );
        }
        
        final CategoryWriter categoryWriter = new CategoryWriter();
        if(queueHandlerContainer != null){
            queueHandlerContainer.setQueueHandler(categoryWriter);
            queueHandlerContainer.start();
        }else{
            categoryWriterDaemon = new Daemon(categoryWriter);
            categoryWriterDaemon.setName("Nimbus JournalWriterDaemon " + getServiceNameObject());
            categoryWriterDaemon.start();
        }
    }
    
    @Override
    public void stopService() throws Exception{
        if(categoryWriterDaemon != null){
            categoryWriterDaemon.stop();
            categoryWriterDaemon = null;
        }
        
        if(queueHandlerContainer != null){
            queueHandlerContainer.stop();
        }
        
        queue.release();
    }
    
    @Override
    public void startJournal(String key){
        startJournal(key, editorFinder);
    }
    
    @Override
    public void startJournal(String key, EditorFinder finder){
        startJournal(key, null, finder);
    }
    
    @Override
    public void startJournal(String key, Date startTime){
        startJournal(key, startTime, null);
    }
    
    @Override
    public void startJournal(String key, Date startTime, EditorFinder finder){
        if(getState() != State.STARTED){
            return;
        }
        if(finder == null){
            finder = editorFinder;
        }
        if(startTime == null){
            startTime = new Date();
        }
        JournalRecord current = localRecord.get();
        JournalRecord newRecord = new JournalRecordImpl(key, finder);
        localRecord.set(newRecord);
        newRecord.start(current, startTime);
        if(current == null && sequence != null){
            newRecord.setRequestId(sequence.increment());
        }
    }
    
    @Override
    public void setRequestId(String requestID){
        JournalRecord current = localRecord.get();
        if(current == null){
            return;
        }
        current.setRequestId(requestID);
    }
    
    @Override
    public void addInfo(String key, Object value){
        JournalRecord current = localRecord.get();
        if(current == null){
            return;
        }
        current.addInfo(new JournalInfoImpl(key, value));
    }
    
    @Override
    public void endJournal(){
        endJournal(null);
    }
    
    @Override
    public void endJournal(Date endTime){
        if(endTime == null){
            endTime = new Date();
        }
        JournalRecord current = localRecord.get();
        if(current == null){
            return;
        }
        localRecord.set(null);
        JournalRecord parent = current.end(endTime);
        if(parent == null){
            queue.push(current.toObject(null));
        }
        localRecord.set(parent);
    }
    
    protected class CategoryWriter
     implements QueueHandler<Object>, DaemonRunnable<Object>{
        
        @Override
        public boolean onStart(){
            return true;
        }
        
        @Override
        public boolean onStop(){
            return true;
        }
        
        @Override
        public boolean onSuspend(){
            return true;
        }
        
        @Override
        public boolean onResume(){
            return true;
        }
        
        @Override
        public Object provide(DaemonControl ctrl) throws Throwable{
            return queue != null ? queue.get(1000) : null;
        }
        
        @Override
        public void consume(Object obj, DaemonControl ctrl) throws Throwable{
            handleDequeuedObject(obj);
        }
        
        @Override
        public void garbage(){
            if(queue != null){
                while(queue.size() > 0){
                    try{
                        consume(queue.get(0), null);
                    }catch(Error e){
                        throw e;
                    }catch(Throwable th){
                    }
                }
            }
        }
        
        @Override
        public void handleDequeuedObject(Object obj) throws Throwable{
            if(obj == null){
                return;
            }
            final Map<String, Object> elements = new HashMap<String, Object>(1);
            elements.put(getWritableElementKey(), obj);
            if(categories != null){
                for(Category category : categories){
                    if(category.isEnabled()){
                        try{
                            category.write(elements);
                        }catch(MessageWriteException e){
                            
                        }
                    }
                }
            }
        }
        
        @Override
        public boolean handleError(Object obj, Throwable th) throws Throwable{
            return true;
        }
        
        @Override
        public void handleRetryOver(Object obj, Throwable th) throws Throwable{
        }
    }
}