#include "StdAfx.h"
#include "BlockItem_Virtual.h"
#include "BlockItem_Real.h"
#include "BlockItem_VirtualProps.h"
#include "BlockItem_Input.h"
#include "BlockItem_Output.h"
#include "SchemeDoc.h"

#include <map>

IMPLEMENT_SERIAL(CBlockItem_Virtual, CBlockItem, 1)

CBlockItem_Virtual::CBlockItem_Virtual(void)
{
	m_pWndProps = new CBlockItem_VirtualProps(NULL, this);
	m_strName = "Virtual block";
	m_strDesc = "Integrates blocks to one";
	m_pInputBlocksArray = NULL;
	m_pOutputBlocksArray = NULL;
	m_blockData.dwLayersArray = NULL;
	m_blockData.pDataArrayArray = NULL;
//	m_blockData.pCriticalSectionArray = NULL;
	m_blockData.dwDataLenArrayArray = NULL;

	SetInputCount(0);
	SetOutputCount(0);
}

CBlockItem_Virtual::~CBlockItem_Virtual(void)
{
	delete (m_pWndProps);
	while ( !m_olBlockList.IsEmpty() )
	{
		CObject* p = m_olBlockList.RemoveHead();
		if ( p->IsKindOf(RUNTIME_CLASS(CBlockItem)) )
			((CBlockItem*)p)->ExitInstance();
		delete(p);
	}

	if ( m_pInputBlocksArray )
		delete[] m_pInputBlocksArray;
	if ( m_pOutputBlocksArray )
		delete[] m_pOutputBlocksArray;

	if ( m_blockData.dwLayersArray )
		delete[] (m_blockData.dwLayersArray);
	if ( m_blockData.pDataArrayArray )
		delete[] (m_blockData.pDataArrayArray);
	if ( m_blockData.dwDataLenArrayArray )
		delete[] (m_blockData.dwDataLenArrayArray);
//	if ( m_blockData.pCriticalSectionArray)
//		delete[] (m_blockData.pCriticalSectionArray);
}

CBlockItem_Virtual* CBlockItem_Virtual::CreateInstance()
{
	return new CBlockItem_Virtual();
}

CString CBlockItem_Virtual::GetName() const
{
	return m_strName;
}

CString CBlockItem_Virtual::GetDescription() const
{
	return m_strDesc;
}

CString CBlockItem_Virtual::GetStatus() const
{
	return L"[No status]";
}

CString CBlockItem_Virtual::GetInputPortName(DWORD dwPort) const
{
	ASSERT(dwPort < GetInputCount());
	if ( m_pInputBlocksArray[dwPort] )
		return m_pInputBlocksArray[dwPort]->GetName();
	return L"?";
}

CString CBlockItem_Virtual::GetOutputPortName(DWORD dwPort) const
{
	ASSERT(dwPort < GetOutputCount());
	if ( m_pOutputBlocksArray[dwPort] )
		return m_pOutputBlocksArray[dwPort]->GetName();
	return L"?";
}

BOOL CBlockItem_Virtual::InitInstance()
{
	return TRUE;
}

BOOL CBlockItem_Virtual::ExitInstance()
{
	return TRUE;
}

BOOL CBlockItem_Virtual::Run()
{
	return TRUE;
}

BOOL CBlockItem_Virtual::Stop()
{
	return TRUE;
}

BOOL CBlockItem_Virtual::InsertBlock(const CBlockItem *pBlock)
{
	CBlockItem *pBlockCopy = pBlock->Clone();
	pBlockCopy->SetDocPtr(m_pDocument); // BACHA / pri loadu se nedeje...Rodi
	pBlockCopy->SetParentBlock(this);
	pBlockCopy->InitInstance();
	m_pDocument->m_setAllBlocks.insert(pBlockCopy);
	m_olBlockList.AddTail(pBlockCopy);
	return TRUE;
}

BOOL CBlockItem_Virtual::DeleteBlock(CBlockItem *pBlock)
{
	POSITION pos;
	
	pos = m_olBlockList.GetHeadPosition();
	while ( pos )
	{
		CBlockItem* ptr = (CBlockItem*)m_olBlockList.GetNext(pos);
		ptr->DeleteReferencesToBlock(pBlock);
	}
	
	pos = m_olBlockList.Find(pBlock);
	if ( pos )
	{
		std::set<CBlockItem*>::iterator it;
		if ( m_pDocument && (it = m_pDocument->m_setRun.find(pBlock)) != m_pDocument->m_setRun.end() )
		{
			m_pDocument->m_setRun.erase(it);
		}

		if ( m_pDocument && (it = m_pDocument->m_setOwnTrigger.find(pBlock)) != m_pDocument->m_setOwnTrigger.end() )
		{
			m_pDocument->m_setOwnTrigger.erase(it);
		}

		if ( m_pDocument && (it = m_pDocument->m_setAllBlocks.find(pBlock)) != m_pDocument->m_setAllBlocks.end() )
		{
			m_pDocument->m_setAllBlocks.erase(it);
		}
		
		//pBlock->ExitInstance();

		pBlock->Release();
		//delete(pBlock);
		m_olBlockList.RemoveAt(pos);
	}

	return TRUE;
}

POSITION CBlockItem_Virtual::GetFirstBlock() const
{
	return m_olBlockList.GetHeadPosition();
}

CBlockItem* CBlockItem_Virtual::GetNextBlock(POSITION &pos) const
{
	return (CBlockItem*)m_olBlockList.GetNext(pos);
}

POSITION CBlockItem_Virtual::GetLastBlock() const
{
	return m_olBlockList.GetTailPosition();
}

CBlockItem* CBlockItem_Virtual::GetPrevBlock(POSITION &pos) const
{
	return (CBlockItem*)m_olBlockList.GetPrev(pos);
}

void CBlockItem_Virtual::Serialize(CArchive& ar)
{
	CBlockItem::Serialize(ar);
	//m_olBlockList.Serialize(ar);
	if (ar.IsStoring())
	{	
		DWORD dwI, dwO;
		dwI = GetInputCount();
		dwO = GetOutputCount();
		ar << m_strName << m_strDesc << dwI << dwO;
		SerializeBlocks(ar);
		StoreConnections(ar);
	}
	else
	{
		DWORD dwI, dwO;
		ar >> m_strName >> m_strDesc >> dwI >> dwO;
		SetInputCount(dwI);
		SetOutputCount(dwO);
		SerializeBlocks(ar);
		LoadConnections(ar);
	}
}

void CBlockItem_Virtual::StoreConnections(CArchive &ar)
{

	POSITION pos = m_olBlockList.GetHeadPosition();
	std::map<CBlockItem*, DWORD> blkmap;
	DWORD i = 1;
	while ( pos )
	{
		CBlockItem* p = (CBlockItem*)m_olBlockList.GetNext(pos);
		blkmap.insert(std::pair<CBlockItem*, DWORD>(p, i));
		i++;
	}

	pos = m_olBlockList.GetHeadPosition();
	while ( pos )
	{
		CBlockItem* p = (CBlockItem*)m_olBlockList.GetNext(pos);
		//ar << p->GetInputCount();
		for ( DWORD i = 0; i < p->GetInputCount(); i++ )
		{
			DWORD dwTgtPort;
			CBlockItem* pTgtBlk;
			DWORD dwTgtBlkId = 0;
			p->GetInConnectorTarget(i, &pTgtBlk, &dwTgtPort);
			if ( pTgtBlk )
			{
				std::map<CBlockItem*, DWORD>::iterator it;
				it = blkmap.find(pTgtBlk);
				if ( it != blkmap.end() )
					dwTgtBlkId = (*it).second;
			}
			ar << dwTgtBlkId << dwTgtPort;
		}
	}
}

void CBlockItem_Virtual::LoadConnections(CArchive &ar)
{
	POSITION pos = m_olBlockList.GetHeadPosition();
	std::map<DWORD, CBlockItem*> blkmap;
	DWORD i = 1;
	while ( pos )
	{
		CBlockItem* p = (CBlockItem*)m_olBlockList.GetNext(pos);
		blkmap.insert(std::pair<DWORD, CBlockItem*>(i, p));
		i++;
	}

	pos = m_olBlockList.GetHeadPosition();
	while ( pos )
	{
		CBlockItem* p = (CBlockItem*)m_olBlockList.GetNext(pos);
		for ( DWORD i = 0; i < p->GetInputCount(); i++ )
		{
			DWORD dwTgtPort;
			DWORD dwTgtBlkId;
			ar >> dwTgtBlkId >> dwTgtPort;
			if ( dwTgtBlkId )
			{
				std::map<DWORD, CBlockItem*>::iterator it;
				it = blkmap.find(dwTgtBlkId);
				if ( it != blkmap.end() )
				{
					p->SetInConnectorTarget(i, it->second, dwTgtPort);
				}
			}
		}		
	}
}

BOOL CBlockItem_Virtual::ShowProperties(BOOL bTest)
{
	if ( bTest )
		return TRUE;
	if ( !m_pWndProps->m_hWnd )
	{
		m_pWndProps->Create(IDD_VIRTUALBLOCKPROPS);
	}
	else
	{
		m_pWndProps->SetForegroundWindow();
		m_pWndProps->ShowWindow(SW_SHOW);
	}
	return TRUE;
}

BOOL CBlockItem_Virtual::SetName(const CString strName)
{
	m_strName = strName;
	return TRUE;
}

BOOL CBlockItem_Virtual::SetDescription(const CString strDesc)
{
	m_strDesc = strDesc;
	return TRUE;
}

BOOL CBlockItem_Virtual::SetDocPtr(CSchemeDoc *ptr)
{
	//m_pDocument = ptr;

	CBlockItem::SetDocPtr(ptr);

	POSITION pos = m_olBlockList.GetHeadPosition();
	while (pos)
	{
		CBlockItem* p = (CBlockItem*)m_olBlockList.GetNext(pos);
		p->SetDocPtr(ptr);
	}
	return TRUE;
}

BOOL CBlockItem_Virtual::SetInputCount(const DWORD dwPorts)
{
	CBlockItem_Input **pInputBlocksArray = new CBlockItem_Input*[dwPorts];
	
	DWORD dwOldPortCount = GetInputCount();
	DWORD dwMinPortCount = (dwOldPortCount < dwPorts) ? dwOldPortCount : dwPorts;
	for ( DWORD i = 0; i < dwMinPortCount; i++ )
		pInputBlocksArray[i] = m_pInputBlocksArray[i];
	for ( DWORD i = dwMinPortCount; i < dwPorts; i++ )
	{
		pInputBlocksArray[i] = NULL;
	}
	for ( DWORD i = dwMinPortCount; i < dwOldPortCount; i++ )
	{
		if ( m_pInputBlocksArray[i] )
			m_pInputBlocksArray[i]->UnAssign();
	}

	if ( m_pInputBlocksArray )
		delete[] m_pInputBlocksArray;
	m_pInputBlocksArray = pInputBlocksArray;

	CBlockItem::SetInputCount(dwPorts);

	RefreshInputBlocks();

	return TRUE;
}

BOOL CBlockItem_Virtual::SetOutputCount(const DWORD dwPorts)
{
	CBlockItem_Output **pOutputBlocksArray = new CBlockItem_Output*[dwPorts];
	
	DWORD *dwLayersArray = new DWORD[dwPorts];
	DWORD **dwDataLenArrayArray = new DWORD*[dwPorts];
	void ***pDataArrayArray = new void**[dwPorts];
	//CRITICAL_SECTION **pCriticalSectionArray = new CRITICAL_SECTION*[dwPorts];

	DWORD dwOldPortCount = GetOutputCount();
	DWORD dwMinPortCount = (dwOldPortCount < dwPorts) ? dwOldPortCount : dwPorts;
	for ( DWORD i = 0; i < dwMinPortCount; i++ )
	{
		pOutputBlocksArray[i] = m_pOutputBlocksArray[i];
		dwLayersArray[i] = m_blockData.dwLayersArray[i];
		pDataArrayArray[i] = m_blockData.pDataArrayArray[i];
		dwDataLenArrayArray[i] = m_blockData.dwDataLenArrayArray[i];
//		pCriticalSectionArray[i] = m_blockData.pCriticalSectionArray[i];
	}
	for ( DWORD i = dwMinPortCount; i < dwPorts; i++ )
	{
		pOutputBlocksArray[i] = NULL;
		dwLayersArray[i] = 0;
		pDataArrayArray[i] = NULL;
		dwDataLenArrayArray[i] = NULL;
		//pCriticalSectionArray[i] = NULL;
	}
	for ( DWORD i = dwMinPortCount; i < dwOldPortCount; i++ )
	{
		if ( m_pOutputBlocksArray[i] )
			m_pOutputBlocksArray[i]->UnAssign();
	}

	if ( m_pOutputBlocksArray )
		delete[] m_pOutputBlocksArray;
	m_pOutputBlocksArray = pOutputBlocksArray;

	CBlockItem::SetOutputCount(dwPorts);
	RefreshOutputBlocks();
	
	if ( m_blockData.dwLayersArray )
		delete[] (m_blockData.dwLayersArray);
	if ( m_blockData.pDataArrayArray )
		delete[] (m_blockData.pDataArrayArray);
	if ( m_blockData.dwDataLenArrayArray )
		delete[] (m_blockData.dwDataLenArrayArray);
//	if ( m_blockData.pCriticalSectionArray )
//		delete[] (m_blockData.pCriticalSectionArray);

	m_blockData.pDataArrayArray = pDataArrayArray;
	m_blockData.dwLayersArray = dwLayersArray;
	m_blockData.dwDataLenArrayArray = dwDataLenArrayArray;
//	m_blockData.pCriticalSectionArray = pCriticalSectionArray;

	return TRUE;
}

BOOL CBlockItem_Virtual::SetInputBlock(DWORD dwPort, CBlockItem_Input* ptr)
{
	// kontrola, jestli nevyzadujeme nastaveni na neplatnem portu
	ASSERT(dwPort < GetInputCount());
	// jestlize je k portu pridruzen jiny vstupni blok, odpojime jej
	if ( m_pInputBlocksArray[dwPort] )
		m_pInputBlocksArray[dwPort]->UnAssign();
	m_pInputBlocksArray[dwPort] = ptr;
	return TRUE;
}

BOOL CBlockItem_Virtual::SetOutputBlock(DWORD dwPort, CBlockItem_Output* ptr)
{
	// kontrola, jestli nevyzadujeme nastaveni na neplatnem portu
	ASSERT(dwPort < GetOutputCount());
	// jestlize je k portu pridruzen jiny vstupni blok, odpojime jej
	if ( m_pOutputBlocksArray[dwPort] )
		m_pOutputBlocksArray[dwPort]->UnAssign();
	m_pOutputBlocksArray[dwPort] = ptr;
	return TRUE;
}

BOOL CBlockItem_Virtual::GetInputBlock(DWORD dwPort, CBlockItem_Input **pptr)
{
	ASSERT(dwPort < GetInputCount());
	*pptr = m_pInputBlocksArray[dwPort];
	return *pptr ? TRUE : FALSE;
}

BOOL CBlockItem_Virtual::GetOutputBlock(DWORD dwPort, CBlockItem_Output **pptr)
{
	ASSERT(dwPort < GetOutputCount());
	*pptr = m_pOutputBlocksArray[dwPort];
	return *pptr ? TRUE : FALSE;
}

void CBlockItem_Virtual::SerializeBlocks(CArchive& ar)
{
	if (ar.IsStoring())
	{
		ar.WriteCount(m_olBlockList.GetCount());
		POSITION pos;
		pos = m_olBlockList.GetHeadPosition();
		while ( pos )
		{
			CObject* p = m_olBlockList.GetNext(pos);
			//p->Serialize(ar);
			//ar << p;//////////////////////////////////////////////////////////////////////////////////////
			ar.WriteClass(p->GetRuntimeClass());
			p->Serialize(ar);
			//////////////////////////
		}
	}
	else
	{
		DWORD_PTR nNewCount = ar.ReadCount();
		CObject* newData;
		while (nNewCount--)
		{
			//ar >> newData;
			CRuntimeClass *pRuntimeClass = ar.ReadClass();
			newData = pRuntimeClass->CreateObject();

			((CBlockItem*)newData)->SetDocPtr(m_pDocument);
			((CBlockItem*)newData)->SetParentBlock(this);

			newData->Serialize(ar);

			if ( m_pDocument ) 
				((CBlockItem*)newData)->InitInstance();

			///////////////////////

			m_pDocument->m_setAllBlocks.insert((CBlockItem*)newData);
			m_olBlockList.AddTail(newData);
		}
	}
}

BOOL CBlockItem_Virtual::GetModifiedFlag()
{
	POSITION pos = m_olBlockList.GetHeadPosition();

	// nejdrive prozkoumame vnitrni realne bloky

	while ( pos )
	{
		CBlockItem* blk = (CBlockItem*)m_olBlockList.GetNext(pos);
		if ( blk->IsKindOf(RUNTIME_CLASS(CBlockItem_Real)) &&
			blk->GetModifiedFlag() )
			return TRUE;
	}

	// pote vnitrni virtualni bloky

	pos = m_olBlockList.GetHeadPosition();
	while ( pos )
	{
		CBlockItem* blk = (CBlockItem*)m_olBlockList.GetNext(pos);
		if ( blk->IsKindOf(RUNTIME_CLASS(CBlockItem_Virtual)) &&
			blk->GetModifiedFlag() )
			return TRUE;
	}
	return FALSE;
}

VOID CBlockItem_Virtual::SetModifiedFlag(BOOL bModified)
{
	POSITION pos = m_olBlockList.GetHeadPosition();

	while ( pos )
	{
		CBlockItem* blk = (CBlockItem*)m_olBlockList.GetNext(pos);
		if ( blk->IsKindOf(RUNTIME_CLASS(CBlockItem)) )
			((CBlockItem*)blk)->SetModifiedFlag(FALSE);
	}
}

BOOL CBlockItem_Virtual::RefreshInputBlocks()
{
	POSITION pos;
	pos = m_olBlockList.GetHeadPosition();
	while ( pos )
	{
		CBlockItem *pItem = (CBlockItem*)m_olBlockList.GetNext(pos);
		if ( pItem->IsKindOf(RUNTIME_CLASS(CBlockItem_Input)) )
		{
			((CBlockItem_Input*)pItem)->UpdatePortList();
		}
	}
	return TRUE;
}

BOOL CBlockItem_Virtual::RefreshOutputBlocks()
{
	POSITION pos;
	pos = m_olBlockList.GetHeadPosition();
	while ( pos )
	{
		CBlockItem *pItem = (CBlockItem*)m_olBlockList.GetNext(pos);
		if ( pItem->IsKindOf(RUNTIME_CLASS(CBlockItem_Output)) )
		{
			((CBlockItem_Output*)pItem)->UpdatePortList();
		}
	}
	return TRUE;
}

BOOL CBlockItem_Virtual::ProcessData(const LPBLOCKDATA pInputData)
{
	return TRUE;
}

BOOL CBlockItem_Virtual::ProcessRev(const DWORD dwProcessId, REVDATAMAP &rRevDataMap)
{
	return TRUE;
}

const PORTDATA CBlockItem_Virtual::GetOutputPortData(DWORD dwPort) const
{
	ASSERT ( dwPort < GetOutputCount() );

	CBlockItem *pTgtBlock;
	DWORD dwTgtPort;
	if ( m_pOutputBlocksArray[dwPort] && m_pOutputBlocksArray[dwPort]->GetInConnectorTarget(0, &pTgtBlock, &dwTgtPort) )
	{
		return pTgtBlock->GetOutputPortData(dwTgtPort);
	}

	PORTDATA ret;
	ZeroMemory(&ret, sizeof(ret));
	return ret;
}

LPCRITICAL_SECTION CBlockItem_Virtual::GetOutputCriticalSectionPtr(DWORD dwPort) const
{
	LPCRITICAL_SECTION ret = NULL;
	ASSERT (dwPort < GetOutputCount());
	if ( m_pOutputBlocksArray[dwPort] )
		ret = m_pOutputBlocksArray[dwPort]->GetOutputCriticalSectionPtr(0);
	return ret;
}

BOOL CBlockItem_Virtual::Process(DWORD dwProcessId, REVDATAMAP &rRevDataMap)
{
	if ( dwProcessId == m_dwLastProcessId ) // Blok byl jiz zpracovan
		return TRUE;
	if ( !ProcessRev(dwProcessId, rRevDataMap) ) // pridat zpetna data
		return FALSE;
	// zavolame process pro vsechny bloky zapojene na vystupu

	DWORD dwOutputPorts = GetOutputCount();
	for ( DWORD i = 0; i < dwOutputPorts; i++ )
	{
		if ( m_pOutputBlocksArray[i] )
			m_pOutputBlocksArray[i]->Process(dwProcessId, rRevDataMap);
	}

	//m_pDocument->UpdateViewer_Safe(this);

	return TRUE;
}