/*******************************************************************************
*   Copyright 2012 Analog Devices, Inc.
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
********************************************************************************/

package com.analog.lyric.dimple.schedulers.schedule;

import static java.util.Objects.*;

import java.util.ArrayList;
import java.util.Iterator;

import org.eclipse.jdt.annotation.Nullable;

import cern.colt.map.OpenLongObjectHashMap;

import com.analog.lyric.dimple.model.core.FactorGraph;
import com.analog.lyric.dimple.model.core.INode;
import com.analog.lyric.dimple.schedulers.IScheduler;
import com.analog.lyric.dimple.schedulers.scheduleEntry.BlockScheduleEntry;
import com.analog.lyric.dimple.schedulers.scheduleEntry.EdgeScheduleEntry;
import com.analog.lyric.dimple.schedulers.scheduleEntry.IScheduleEntry;
import com.analog.lyric.dimple.schedulers.scheduleEntry.NodeScheduleEntry;
import com.analog.lyric.dimple.schedulers.scheduleEntry.SubgraphScheduleEntry;


/**
 * A schedule with a fixed list of schedule entries.
 * <p>
 * Schedule has a fixed update order, and does not change dynamically as the solver runs.
 * <p>
 * @author jeffb
 */
public class FixedSchedule extends ScheduleBase implements IGibbsSchedule
{
	// TODO - schedule will not actually be serializable until schedule entries and nodes are serializable!
	private static final long serialVersionUID = 1L;

	/*-------
	 * State
	 */
	
	protected ArrayList<IScheduleEntry> _schedule = new ArrayList<IScheduleEntry>();
	
	/*--------------
	 * Construction
	 */
	
	/**
	 * @deprecated instead use {@link #FixedSchedule(FactorGraph)}
	 * 
	 * @since 0.08
	 */
	@Deprecated
	public FixedSchedule()
	{
		super(null, null);
	}
	
	public FixedSchedule(FactorGraph fg)
	{
		super(null, fg);
	}
	
	public FixedSchedule(@Nullable IScheduler scheduler, FactorGraph fg)
	{
		super(scheduler, fg);
	}
	
	/**
	 * @deprecated instead use {@link #FixedSchedule(FactorGraph, IScheduleEntry[])}
	 */
	@Deprecated
	public FixedSchedule(IScheduleEntry[] entries)
	{
		this();
		add(entries);
	}
	
	public FixedSchedule(FactorGraph fg, IScheduleEntry[] entries)
	{
		this(fg);
		add(entries);
	}

	public FixedSchedule(@Nullable IScheduler scheduler, FactorGraph fg, IScheduleEntry[] entries)
	{
		super(scheduler, fg);
		add(entries);
	}

	public FixedSchedule(@Nullable IScheduler scheduler, FactorGraph fg, Iterable<IScheduleEntry> entries)
	{
		super(scheduler, fg);
		add(entries);
	}

	/**
	 * @deprecated instead use {@link #FixedSchedule(IScheduler, FactorGraph, Iterable)}
	 */
	@Deprecated
	public FixedSchedule(Iterable<IScheduleEntry> entries)
	{
		this();
		add(entries);
	}
	
	public ArrayList<IScheduleEntry> getSchedule()
	{
		return _schedule;
	}
	
	@Deprecated
	public FixedSchedule(ISchedule s)
	{
		this();
		add(s);
	}
	
	@Override
	public Iterator<IScheduleEntry> iterator()
	{
		return _schedule.iterator();
	}
	
	public void add(INode node)
	{
		final FactorGraph fg = node.asFactorGraph();
		add(fg != null ? new SubgraphScheduleEntry(fg) : new NodeScheduleEntry(node));
	}
	
	public void add(INode node, int index)
	{
		add(new EdgeScheduleEntry(node, index));
	}
	
	public void add(INode ... nodes)
	{
		for (int i = 0; i < nodes.length; i++)
			add(nodes[i]);
	}
	
	/**
	 * @deprecated this method cannot correctly handle multiple connections between the same
	 * factor and variable. Instead use {@link #add(INode, int)} method.
	 */
	@Deprecated
	public void add(INode from, INode to)
	{
		add(new EdgeScheduleEntry(from,to));
	}
	
	// Add one schedule entry
	public void add(IScheduleEntry entry)
	{
		requireNonNull(entry);
		_schedule.add(entry);
		++_version;
	}
	
	// Add a series of schedule entries
	public void add(@Nullable IScheduleEntry[] entries)
	{
		if (entries != null) for (IScheduleEntry entry : entries) add(entry);
	}
	public void add(@Nullable Iterable<IScheduleEntry> entries)
	{
		if (entries != null)
		{
			for (IScheduleEntry entry : entries)
				add(entry);
		}
	}
	
	// Add a sub-schedule
	@Deprecated
	public void add(@Nullable ISchedule s)
	{
		if (s != null)
		{
			add(new com.analog.lyric.dimple.schedulers.scheduleEntry.SubScheduleEntry(s));
		}
	}
	
	// Remove node or edge schedule entries containing a specified node
	private final void removeAllEntriesContaining(Iterable<? extends INode> nodes)
	{
		final OpenLongObjectHashMap nodemap = new OpenLongObjectHashMap();
		for (INode node : nodes)
		{
			nodemap.put(node.getGlobalId(), node);
		}
		
		// Use iterator to avoid concurrent modification
		for (final Iterator<IScheduleEntry> iterator = _schedule.iterator(); iterator.hasNext(); )
		{
			IScheduleEntry s = iterator.next();
			switch (s.type())
			{
			case NODE:
				if (nodemap.containsKey(((NodeScheduleEntry)s).getNode().getGlobalId()))
				{
					iterator.remove();
					++_version;
				}
				break;
				
			case EDGE:
				if (nodemap.containsKey(((EdgeScheduleEntry)s).getNode().getGlobalId()))
				{
					iterator.remove();
					++_version;
				}
				break;
			default:
			}
		}
	}
	
	// Get a particular entry by index (this is used, for example, for randomly selecting entries)
	public final IScheduleEntry get(int index)
	{
		return _schedule.get(index);
	}
	
	
	
	
	// For IGibbsSchedule interface...
	// Add a block schedule entry, which will replace individual node updates included in the block
	@Override
	public void addBlockScheduleEntry(BlockScheduleEntry blockScheduleEntry)
	{
		// Remove any node entries associated with the nodes in the block entry
		removeAllEntriesContaining(blockScheduleEntry.getBlock());
		
		// Add the block entry
		add(blockScheduleEntry);
	}
	// Return the number of entries
	@Override
	public final int size()
	{
		return _schedule.size();
	}
	
	@Override
	public boolean isCustom()
	{
		IScheduler scheduler = _scheduler;
		return scheduler == null || scheduler.isCustomScheduler();
	}
	
	@Override
	public String toString()
	{
		StringBuilder sb = new StringBuilder("FixedSchedule ");
		sb.append(Integer.toString(_schedule.size()));
		sb.append("\n");
		for(IScheduleEntry entry : _schedule)
		{
			sb.append("\t" + entry.toString() + "\n");
		}
		return sb.toString();
	}
}
