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

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Objects;

import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import com.analog.lyric.dimple.exceptions.DimpleException;
import com.analog.lyric.dimple.model.domains.Domain;
import com.analog.lyric.dimple.model.variables.Variable;
import com.analog.lyric.dimple.solvers.interfaces.ISolverVariable;

public abstract class VariableStreamBase<V extends Variable> implements IVariableStreamSlice<V>
{
	private Domain _domain;
	
	private ArrayList<V> _variables = new ArrayList<>();
	private @Nullable IDataSource _dataSource = null;
	private @Nullable IDataSink _dataSink = null;
	private ArrayList<VariableStreamSlice<V>> _slices = new ArrayList<>();
	private VariableStreamSlice<V> _slice;
	private final @Nullable String _namePrefix;

	protected VariableStreamBase(Domain domain, @Nullable String namePrefix)
	{
		_domain = domain;
		_slice = getSlice(0);
		_namePrefix = namePrefix;
	}
	
	/**
	 * Return {@link Class} of variable used by this stream.
	 * @since 0.08
	 */
	protected abstract Class<? extends V> variableType();
	
	public int size()
	{
		return _variables.size();
	}
	
	@SuppressWarnings("unchecked")
	public V [] getVariables()
	{
		return _variables.toArray((V[])Array.newInstance(variableType(), _variables.size()));
	}
	
	public boolean contains(Variable vb)
	{
		return _variables.contains(vb);
	}
	
	@SuppressWarnings("deprecation")
	public void advanceState()
	{
		final IDataSink dataSink = _dataSink;
		if (dataSink != null)
		{
			Object output = _variables.get(0).getBeliefObject();
			dataSink.push(Objects.requireNonNull(output));
		}
		
		for (int i = 0; i < _variables.size()-1; i++)
		{
			Variable var = _variables.get(i), ovar = _variables.get(i+1);
			var.moveInputs(ovar);
			
			final ISolverVariable svar = var.requireSolver("advanceState"), osvar = ovar.requireSolver("advanceState");
			svar.moveNonEdgeSpecificState(osvar);
		}
		
		Variable lastVar = _variables.get(_variables.size()-1);
		lastVar.requireSolver("advanceState").createNonEdgeSpecificState();

		final IDataSource dataSource = _dataSource;
		if (dataSource != null)
		{
			Object input = dataSource.getNext();
			lastVar.setInputObject(input);
			lastVar.requireSolver("advanceState").updatePriorAndCondition();
		}
		else
		{
			lastVar.setPrior(null);
		}
	}
	
	
	public VariableStreamSlice<V> getSlice(int start)
	{
		VariableStreamSlice<V> ss = new VariableStreamSlice<>(start,this);
		_slices.add(ss);
		return ss;
	}
	
	public @Nullable IDataSink getDataSink()
	{
		return _dataSink;
	}
	public @Nullable IDataSource getDataSource()
	{
		return _dataSource;
	}
	
	public void setDataSink(IDataSink sink)
	{
		_dataSink = sink;
	}
	
	@SuppressWarnings("deprecation")
	public void setDataSource(IDataSource source)
	{
		_dataSource = source;
		
		//fill variables with data
		for (Variable vb : _variables)
		{
			if (!source.hasNext())
				throw new DimpleException("not enough data in data source");
			
			vb.setInputObject(source.getNext());
		}
	}
	
	/**
	 * The variable domain of the stream.
	 * @since 0.07
	 */
	public Domain getDomain()
	{
		return _domain;
	}

	
	abstract protected V instantiateVariable(Domain domain) ;
	
	@SuppressWarnings("deprecation")
	protected V createVariable(int index)
	{
		V tmp;
		
		tmp = instantiateVariable(_domain);
		
		if (_namePrefix != null)
		{
			tmp.setName(_namePrefix + index);
		}
		
		final IDataSource dataSource = _dataSource;
		if (dataSource != null)
		{
			
			if (!dataSource.hasNext())
				throw new DimpleException("out of data");
			
			tmp.setInputObject(dataSource.getNext());
		}
		
		return tmp;

	}
	
	public boolean variableAvailableFor(double index)
	{
		if (index < 0)
		{
			return false;
		}

		for (int i = _variables.size(); i <= index; ++i)
		{
			if (!hasNext())
			{
				return false;
			}
			
			_variables.add(createVariable(i));
		}
		
		return index < _variables.size();
	}
	
	
	@NonNull // FIXME - workaround for Eclipse JDT bug (467610?)
	@Override
	public V get(int index)
	{
		return get(index,false);
	}
	
	@NonNull // FIXME - workaround for Eclipse JDT bug (467610?)
	@Override
	public V get(int index,boolean createIfDoesntExist)
	{
		if (index < 0)
			throw new DimpleException("negative indexing not allowed");
		
		if (!createIfDoesntExist && index >= _variables.size())
			throw new DimpleException("A variable has not yet been instantiated for the specified index: " + index);
		
		for (int i = _variables.size(); i <= index; ++i)
		{
			_variables.add(createVariable(i));
		}
		
		return _variables.get(index);

	}

	public boolean hasNext()
	{
		final IDataSource dataSource = _dataSource;
		if (dataSource == null)
			return true;
		else
			return dataSource.hasNext();

	}
	
	public IVariableStreamSlice<V> copy()
	{
		return _slice.copy();
	}
	
	@Override
	public VariableStreamBase<V> getStream()
	{
		return this;
	}
	

	int indexOf(Variable vb)
	{
		return _variables.indexOf(vb);
	}
	
	void cleanupUnusedVariables()
	{

		for(int i =  _variables.size()-1; i >= 0; i--)
		{
			if (_variables.get(i).getParentGraph()==null)
				_variables.remove(i);
		}
	}

}
