/*******************************************************************************
 * Copyright (c) 2009, 2011 The University of Memphis.  All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the LIDA Software Framework Non-Commercial License v1.0 
 * which accompanies this distribution, and is available at
 * http://ccrg.cs.memphis.edu/assets/papers/2010/LIDA-framework-non-commercial-v1.0.pdf
 *******************************************************************************/
package myagent.modules;

import java.util.Collections;
import java.util.LinkedList;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import edu.memphis.ccrg.lida.framework.FrameworkModuleImpl;
import edu.memphis.ccrg.lida.framework.shared.Link;
import edu.memphis.ccrg.lida.framework.shared.Linkable;
import edu.memphis.ccrg.lida.framework.shared.Node;
import edu.memphis.ccrg.lida.framework.shared.NodeStructure;
import edu.memphis.ccrg.lida.framework.shared.NodeStructureImpl;
import edu.memphis.ccrg.lida.framework.tasks.FrameworkTaskImpl;
import edu.memphis.ccrg.lida.framework.tasks.TaskManager;
import edu.memphis.ccrg.lida.framework.tasks.TaskStatus;
import edu.memphis.ccrg.lida.globalworkspace.BroadcastContent;
import edu.memphis.ccrg.lida.globalworkspace.BroadcastListener;
import edu.memphis.ccrg.lida.workspace.WorkspaceContent;
import edu.memphis.ccrg.lida.workspace.workspacebuffers.WorkspaceBuffer;

public class ImprovedBroadcastQueueImpl extends FrameworkModuleImpl implements
        WorkspaceBuffer, BroadcastListener {

    private static final Logger logger = Logger
            .getLogger(ImprovedBroadcastQueueImpl.class.getCanonicalName());

    private static final int DEFAULT_QUEUE_CAPACITY = 20;
    private int broadcastQueueCapacity = DEFAULT_QUEUE_CAPACITY;
    private LinkedList<NodeStructure> broadcastQueue = new LinkedList<NodeStructure>();


    public ImprovedBroadcastQueueImpl() {
    }

    @Override
    public void init() {
        int desired = (Integer) getParam("workspace.broadcastQueueCapacity",
                DEFAULT_QUEUE_CAPACITY);
        if (desired > 0) {
            broadcastQueueCapacity = desired;
        } else {
            logger.log(Level.WARNING, "Capacity must be greater than 0.",
                    TaskManager.getCurrentTick());
        }
    }

    @Override
    public void receiveBroadcast(BroadcastContent bc) {
        if (bc != null) {
            synchronized (this) {
                ProcessBroadcastTask task = new ProcessBroadcastTask(
                        ((NodeStructure) bc).copy());
                taskSpawner.addTask(task);
            }
        } else {
            logger.log(Level.WARNING, "received null broadcast", TaskManager
                    .getCurrentTick());
        }
    }

    private class ProcessBroadcastTask extends FrameworkTaskImpl {
        private NodeStructure broadcast;

        public ProcessBroadcastTask(NodeStructure broadcast) {
            super();
            this.broadcast = broadcast;
        }

        @Override
        protected synchronized void runThisFrameworkTask() {
            synchronized(broadcastQueue){
                broadcastQueue.addFirst(broadcast);
                // Keep the buffer at a fixed size
                while (broadcastQueue.size() > broadcastQueueCapacity) {
                    broadcastQueue.removeLast();// remove oldest
                }
                setTaskStatus(TaskStatus.FINISHED);
            }
        }
    }

    @Override
    public Object getModuleContent(Object... params) {
        return Collections.unmodifiableList(broadcastQueue);
    }

    @Override
    public void addBufferContent(WorkspaceContent content) {
        broadcastQueue.addFirst(content);
    }

    @Override
    public WorkspaceContent getBufferContent(Map<String, Object> params) {
        if (params != null) {
            Object index = params.get("position");
            if (index instanceof Integer) {
                Integer i = (Integer) index;
                if (i > -1 && i < broadcastQueue.size()) {
                    return (WorkspaceContent) broadcastQueue.get(i);
                }
            }
        }
        return null;
    }

    @Override
    public void decayModule(long ticks) {
        //do not remove nodes due decay
    }

    @Override
    public void learn(BroadcastContent content) {
        // Not applicable
    }
    
    protected float getBroadcastFrequencyForLinkable(Linkable ln){
        
        NodeStructure ns = linkableToNodeStructure(ln);
        return getBroadcastFrequencyForStructure(ns);
    }
        
    protected float getBroadcastFrequencyForStructure(NodeStructure ns){
        
        synchronized(broadcastQueue){
            int occurrences = 0;
            
            for(NodeStructure broadcastedNs : broadcastQueue){
                
                if(compareNodeStructures(broadcastedNs, ns)) {
                    occurrences++;
                }
            }
            return (float) occurrences/this.broadcastQueueCapacity;
        }        
    }
    
    protected boolean compareNodeStructures(NodeStructure a, NodeStructure b){
    
        for(Linkable ln : a.getLinkables()){
            if(!b.containsLinkable(ln)) return false;
        }
        return true;
    }
    
    protected NodeStructure linkableToNodeStructure(Linkable ln){
        NodeStructureImpl ns = new NodeStructureImpl();
        
        if(ln instanceof Node) ns.addDefaultNode((Node) ln);
        else if (ln instanceof Link) ns.addDefaultLink((Link) ln);
        else {
            logger.log(Level.FINEST, "INVALID LINKABLE {0}", ln);
            return null;
        }
        
        return ns;
    }

}