// CaplUnitExchangeManager.cpp: implementation of the CaplUnitExchangeManager class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include <aplNetStepData.h>
#include "apl_api.h"


const int prefixCount=16;//число префиксов

struct PREFIX
{
	TCHAR *name;
	double exponent;
}prefix[prefixCount]= {
			{_T("atto"),1e-18},
			{_T("femto"),1e-15},
			{_T("pico"),1e-12},
			{_T("nano"),1e-9},
			{_T("micro"),1e-6},
			{_T("milli"),1e-3},
			{_T("centi"),1e-2},
			{_T("deci"),1e-1},
			{_T("deca"),10},
			{_T("hecto"),10e1},
			{_T("kilo"),10e2},
			{_T("mega"),10e5},
			{_T("giga"),10e8},
			{_T("tera"),10e11},
			{_T("peta"),10e14},
			{_T("exa"),10e17}
			};

CaplUnitExchangeManager::BASE::BASE()
{
	cUnit=0;
	conv_factor=0;
	length=0;
	mass=0;
	time=0;
	electric_cur=0;
	therm_temp=0;
	am_of_sub=0;
	lum_intens=0;
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////


CaplUnitExchangeManager::CaplUnitExchangeManager()
{
	attached=false;
}

CaplUnitExchangeManager::~CaplUnitExchangeManager()
{

}

bool CaplUnitExchangeManager::FindUnitsWithSameBase(CaplInstance *unit, aplExtent *units)
{
	if(0==units) return false;
	units->Clear();
	if(!attached) 
		return false;
	if(!m_data->IsKindOf(unit,m_api->m_charact_mgr.e_apl_step_unit))
		return false;
	int i;
	BASE *base=0;
	if(!(base=GetBase(unit)))
		return false;
	units->Clear();
	for(i=0;i<all_unit_bases.GetSize();i++)
	{
		if(IsSameBase(&all_unit_bases[i],base))
			units->Add(all_unit_bases[i].cUnit);

	}
	return true;
}

bool CaplUnitExchangeManager::Attach(CaplAPI *api)
{
	m_ext_prd_unit.Clear();

	attached=false;

	if(api==0) {Detach();return false;}
	CaplStepManager::Attach(api);
	
	if(!(a_apl_unit_name=m_data->GetAttrDefinition(m_api->m_charact_mgr.e_apl_step_unit,_T("name"))))
		return false;
	if(!(a_apl_unit_id=m_data->GetAttrDefinition(m_api->m_charact_mgr.e_apl_step_unit,_T("id"))))
		return false;
	if(!(a_apl_named_unit_dimensions=m_data->GetAttrDefinition(m_api->m_charact_mgr.e_apl_named_unit,_T("dimensions"))))
		return false;
	if(!(a_apl_conv_base_unit_conv_factor=m_data->GetAttrDefinition(m_api->m_charact_mgr.e_apl_conv_base_unit,_T("conversion_factor"))))
		return false;
	if(!(e_apl_dimensional_exponents=m_data->GetEntityBN(_T("dimensional_exponents"))))
		return false;
	if(!(a_apl_dim_exp_length_exponent=m_data->GetAttrDefinition(e_apl_dimensional_exponents,_T("length_exponent"))))
		return false;
	if(!(a_apl_dim_exp_mass_exponent=m_data->GetAttrDefinition(e_apl_dimensional_exponents,_T("mass_exponent"))))
		return false;
	if(!(a_apl_dim_exp_time_exponent=m_data->GetAttrDefinition(e_apl_dimensional_exponents,_T("time_exponent"))))
		return false;
	if(!(a_apl_dim_exp_electric_current_exponent=m_data->GetAttrDefinition(e_apl_dimensional_exponents,_T("electric_current_exponent"))))
		return false;
	if(!(a_apl_dim_exp_thermodynamic_temperature_exponent=m_data->GetAttrDefinition(e_apl_dimensional_exponents,_T("thermodynamic_temperature_exponent"))))
		return false;
	if(!(a_apl_dim_exp_amount_of_substance_exponent=m_data->GetAttrDefinition(e_apl_dimensional_exponents,_T("amount_of_substance_exponent"))))
		return false;
	if(!(a_apl_dim_exp_luminous_intensity_exponent=m_data->GetAttrDefinition(e_apl_dimensional_exponents,_T("luminous_intensity_exponent"))))
		return false;

	if(!(a_apl_conv_base_unit_conv_factor=m_data->GetAttrDefinition(m_api->m_charact_mgr.e_apl_conv_base_unit,_T("conversion_factor"))))
		return false;
	if(!(a_apl_derived_unit_elements=m_data->GetAttrDefinition(m_api->m_charact_mgr.e_apl_derived_unit,_T("elements"))))
		return false;
	if(!(e_apl_measure_with_unit=m_data->GetEntityBN(_T("measure_with_unit"))))
		return false;
	if(!(a_apl_measure_with_unit_unit_comp=m_data->GetAttrDefinition(e_apl_measure_with_unit,_T("unit_component"))))
		return false;
	if(!(a_apl_measure_with_unit_value_comp=m_data->GetAttrDefinition(e_apl_measure_with_unit,_T("value_component"))))
		return false;
	if(!(e_apl_derived_unit_element=m_data->GetEntityBN(_T("derived_unit_element"))))
		return false;
	if(!(a_apl_derived_unit_element_exponent=m_data->GetAttrDefinition(e_apl_derived_unit_element,_T("exponent"))))
		return false;
	if(!(a_apl_derived_unit_element_unit=m_data->GetAttrDefinition(e_apl_derived_unit_element,_T("unit"))))
		return false;
	if(!(a_apl_si_unit_prefix=m_data->GetAttrDefinition(m_api->m_charact_mgr.e_apl_si_unit,_T("prefix"))))
		return false;

	all_unit_bases.RemoveAll();
	attached=true;
	return true;
}
	
bool CaplUnitExchangeManager::InitUnitsBases()
{
	all_unit_bases.RemoveAll();
	if(!GetSiAndContextUnitsBases()) return false;
	GetAllOtherUnitsBases();
	return true;
}

bool CaplUnitExchangeManager::GetSiAndContextUnitsBases()
{
	aplExtent ext;
	BASE tmp;
	CaplInstance *inst;
	for(int j=0;j<2;j++)
	{
		ext.Clear();
		if(j==0)
			m_data->GetEntityExtent(m_api->m_charact_mgr.e_apl_si_unit,ext);
		else
			m_data->GetEntityExtent(m_api->m_charact_mgr.e_apl_cont_dep_unit,ext);
		for(int i=0;i<ext.GetSize();i++)
		{
			if(m_data->GetAttr(ext[i],a_apl_named_unit_dimensions,inst))
			{
				tmp.cUnit=ext[i];
				m_data->GetAttr(ext[i],a_apl_unit_name,tmp.name);
				m_data->GetAttr(ext[i],a_apl_unit_id,tmp.id);
				if(inst!=0)
				{
					m_data->GetAttr(inst,a_apl_dim_exp_length_exponent,tmp.length);
					m_data->GetAttr(inst,a_apl_dim_exp_mass_exponent,tmp.mass);
					m_data->GetAttr(inst,a_apl_dim_exp_time_exponent,tmp.time);
					m_data->GetAttr(inst,a_apl_dim_exp_electric_current_exponent,tmp.electric_cur);
					m_data->GetAttr(inst,a_apl_dim_exp_thermodynamic_temperature_exponent,tmp.therm_temp);
					m_data->GetAttr(inst,a_apl_dim_exp_amount_of_substance_exponent,tmp.am_of_sub);
					m_data->GetAttr(inst,a_apl_dim_exp_luminous_intensity_exponent,tmp.lum_intens);
				}
				else tmp.length=tmp.mass=tmp.time=tmp.electric_cur=tmp.therm_temp,tmp.am_of_sub,tmp.lum_intens=0;

				tmp.conv_factor=1;
				if(j==0)//если единица Си, расставим множители (по префиксу)
				{
					CString si_prefix;
					if(m_data->GetAttr(ext[i],a_apl_si_unit_prefix,si_prefix))
					{
						for(int k=0;k<prefixCount;k++)
						{	
							if(CString(prefix[k].name)==si_prefix)
								tmp.conv_factor=prefix[k].exponent;
						}
					}

				}
				all_unit_bases.Add(tmp);
			}
		}
	}
	return true;
}

bool CaplUnitExchangeManager::LoadUnits()
{	
	CaplLoadData ld(m_data,DEF_SOURCE);
	ld.ClearQuery();
		  ld.AddQuery(_T('e'), 0, m_api->m_charact_mgr.e_apl_step_unit, 0, true,true);
	int i=ld.AddQuery(_T('e'), 0, m_api->m_charact_mgr.e_apl_si_unit, 0, true,true);
		  ld.AddQuery(_T('d'), i, 0, a_apl_named_unit_dimensions, true,true);
		i=ld.AddQuery(_T('e'), 0, m_api->m_charact_mgr.e_apl_cont_dep_unit, 0, true,true);
		  ld.AddQuery(_T('d'), i, 0, a_apl_named_unit_dimensions, true,true);
		i=ld.AddQuery(_T('e'), 0, m_api->m_charact_mgr.e_apl_conv_base_unit, 0, true,true);
		  ld.AddQuery(_T('d'), i,  0, a_apl_conv_base_unit_conv_factor, true,true);
		i=ld.AddQuery(_T('e'), 0, m_api->m_charact_mgr.e_apl_derived_unit, 0, true,true);
		  ld.AddQuery(_T('d'), i, 0, a_apl_derived_unit_elements, true,true);
		i=ld.AddQuery(_T('e'), 0, m_api->m_charact_mgr.e_mission_apl_alp_base_unit, 0, true,true);
		  ld.AddQuery(_T('d'), i, 0, a_apl_derived_unit_elements, true,true);
		i=ld.AddQuery(_T('e'), 0, m_api->m_charact_mgr.e_year_apl_alp_base_unit, 0, true,true);
		  ld.AddQuery(_T('d'), i, 0, a_apl_derived_unit_elements, true,true);
		i=ld.AddQuery(_T('e'), 0, m_api->m_charact_mgr.e_flight_days_apl_alp_base_unit, 0, true,true);
		  ld.AddQuery(_T('d'), i, 0, a_apl_derived_unit_elements, true,true);
	
	if(!ld.LoadEx()){
		if(apidata.GetLastAplError()!=APL_SOCK_OPERATION_IN_PROGRESS){
			return false;
		}
	}
	return true;

}

bool CaplUnitExchangeManager::GetAllOtherUnitsBases()
{
	int i;
	aplExtent ext;
	ext.Clear();
	m_data->GetEntityExtent(m_api->m_charact_mgr.e_apl_conv_base_unit,ext);
	for(i=0;i<ext.GetSize();i++)
		GetConvBasedUnitBase(ext[i],ext[i],1,1);
	ext.Clear();
	m_data->GetEntityExtent(m_api->m_charact_mgr.e_apl_derived_unit,ext);
	for(i=0;i<ext.GetSize();i++)
		GetDerUnitBases(ext[i],ext[i],1,1);
	
	return true;
	
}

bool CaplUnitExchangeManager::GetConvBasedUnitBase(CaplInstance *convUnitAttr,CaplInstance *Unit,double exponent,double conv_factor)
{
	CaplInstance *measureInst,*unitInst;
	if(!(m_data->GetAttr(convUnitAttr,a_apl_conv_base_unit_conv_factor,measureInst)))
		return false;
	if(!measureInst) return false;
	if(!(m_data->GetAttr(measureInst,a_apl_measure_with_unit_unit_comp,unitInst)))
		return false;
	if(!unitInst) return false;
	double conv_f=0;
	m_data->GetAttr(measureInst,a_apl_measure_with_unit_value_comp,conv_f);
	if(conv_f)
		conv_factor*=conv_f;
	if((m_data->IsKindOf(unitInst,m_api->m_charact_mgr.e_apl_si_unit))||(m_data->IsKindOf(unitInst,m_api->m_charact_mgr.e_apl_cont_dep_unit)))
		AddUnitBase(unitInst,Unit,exponent,conv_factor);
	if(m_data->IsKindOf(unitInst,m_api->m_charact_mgr.e_apl_conv_base_unit))
		GetConvBasedUnitBase(unitInst,Unit,exponent,conv_factor);
	if(m_data->IsKindOf(unitInst,m_api->m_charact_mgr.e_apl_derived_unit))
		GetDerUnitBases(unitInst,Unit,exponent,conv_factor);
	return true;
}

bool CaplUnitExchangeManager::GetDerUnitBases(CaplInstance *derUnitAttr,CaplInstance *Unit,double exponent,double conv_factor)
{
	CaplAggr cElements;
	CaplInstance *elemInst,*unitInst;

	double exp,exp_new;

	if(!(m_data->GetAttr(derUnitAttr,a_apl_derived_unit_elements,cElements)))
		return false;
	
	for(int i=0;i<cElements.GetSize();i++)
	{
		cElements.GetByIndex(i,elemInst);
		if(elemInst)
		{
			exp_new=exponent;
			exp=0;
			m_data->GetAttr(elemInst,a_apl_derived_unit_element_exponent,exp);
			if(exp)
				exp_new*=exp;
			
			unitInst=0;
			m_data->GetAttr(elemInst,a_apl_derived_unit_element_unit,unitInst);
			if(!unitInst)
				continue;
			if((m_data->IsKindOf(unitInst,m_api->m_charact_mgr.e_apl_si_unit))||(m_data->IsKindOf(unitInst,m_api->m_charact_mgr.e_apl_cont_dep_unit)))
				AddUnitBase(unitInst,Unit,exp_new,conv_factor);
			if(m_data->IsKindOf(unitInst,m_api->m_charact_mgr.e_apl_conv_base_unit))
				GetConvBasedUnitBase(unitInst,Unit,exp_new,conv_factor);
			if(m_data->IsKindOf(unitInst,m_api->m_charact_mgr.e_apl_derived_unit))
				GetDerUnitBases(unitInst,Unit,exp_new,conv_factor);
		}
	}

	return true;
}

bool CaplUnitExchangeManager::AddUnitBase(CaplInstance *inst, CaplInstance *Unit,double exponent,double conv_factor)
{
	double prefix=0;
	int baseNum=-1;
	int i;
	for(i=0;i<all_unit_bases.GetSize();i++)
	{
		if(inst==all_unit_bases[i].cUnit)
		{
			baseNum=i;
			break;
		}
	}
	if(baseNum<0)
		return false;
	for(i=0;i<all_unit_bases.GetSize();i++)
	{
		if(Unit==all_unit_bases[i].cUnit)//такая единица уже занесена,надо складывать базис
		{
			prefix=all_unit_bases[baseNum].conv_factor;
			if((-1*exponent)<0)
				all_unit_bases[i].conv_factor*=conv_factor*prefix;
			else
				all_unit_bases[i].conv_factor/=conv_factor*prefix;
			all_unit_bases[i].am_of_sub+=exponent*all_unit_bases[baseNum].am_of_sub;
			all_unit_bases[i].electric_cur+=exponent*all_unit_bases[baseNum].electric_cur;
			all_unit_bases[i].length+=exponent*all_unit_bases[baseNum].length;
			all_unit_bases[i].lum_intens+=exponent*all_unit_bases[baseNum].lum_intens;
			all_unit_bases[i].mass+=exponent*all_unit_bases[baseNum].mass;
			all_unit_bases[i].therm_temp+=exponent*all_unit_bases[baseNum].therm_temp;
			all_unit_bases[i].time+=exponent*all_unit_bases[baseNum].time;
			return true;
		}
	}

	//"новая" единица измерения
	BASE tmp;
	tmp.cUnit=Unit;
	if(Unit)
	{
		m_data->GetAttr(Unit,a_apl_unit_name,tmp.name);
		m_data->GetAttr(Unit,a_apl_unit_id,tmp.id);
	}
	prefix=all_unit_bases[baseNum].conv_factor;
	if((-1*exponent)<0)
		tmp.conv_factor=conv_factor*prefix;
	else
		tmp.conv_factor=1/conv_factor*prefix;
	tmp.am_of_sub=exponent*all_unit_bases[baseNum].am_of_sub;
	tmp.electric_cur=exponent*all_unit_bases[baseNum].electric_cur;
	tmp.length=exponent*all_unit_bases[baseNum].length;
	tmp.lum_intens=exponent*all_unit_bases[baseNum].lum_intens;
	tmp.mass=exponent*all_unit_bases[baseNum].mass;
	tmp.therm_temp=exponent*all_unit_bases[baseNum].therm_temp;
	tmp.time=exponent*all_unit_bases[baseNum].time;
	all_unit_bases.Add(tmp);
	return true;
}

CaplUnitExchangeManager::BASE *CaplUnitExchangeManager::GetBase(CaplInstance *unit)
{

	if(!attached)
		return 0;
	
	for(int i=0;i<all_unit_bases.GetSize();i++)
	{
		CaplInstance *uid=all_unit_bases[i].cUnit;
		if(all_unit_bases[i].cUnit==unit)
		{
			return &all_unit_bases[i];
		}
	}
	return 0;
}

bool CaplUnitExchangeManager::IsSameBase(BASE *first_base, BASE *second_base)
{
	if(0==first_base || 0==second_base ) return false;
	if(!attached)
		return false;
	if((first_base->am_of_sub   ==second_base->am_of_sub)&&
	   (first_base->electric_cur==second_base->electric_cur)&&
	   (first_base->length      ==second_base->length)&&
	   (first_base->lum_intens  ==second_base->lum_intens)&&
	   (first_base->mass        ==second_base->mass)&&
	   (first_base->therm_temp  ==second_base->therm_temp)&&
	   (first_base->time        ==second_base->time))
	   return true;
	return false;
}

bool CaplUnitExchangeManager::IsSameBase(CaplInstance *first_unit, CaplInstance *second_unit)
{
	if(0==first_unit || 0==second_unit ) return false;
	if(!attached)
		return false;
	BASE *fb=0,*sb=0;
	fb=GetBase(first_unit);
	sb=GetBase(second_unit);
	if((!fb)||(!sb))
		return false;
	if(IsSameBase(fb,sb))
		return true;
	return false;

}

bool CaplUnitExchangeManager::Convert(CaplInstance *whatUnit, CaplInstance *inWhatUnit,double amount_to_convert,double &result)
{
	if(!attached)
		return false;
	if((!whatUnit)||(!inWhatUnit))
		return false;
	BASE *whatBase=GetBase(whatUnit);
	BASE *inWhatBase=GetBase(inWhatUnit);
	if(!IsSameBase(whatBase,inWhatBase))
		return false;
	result=0;
	if(inWhatBase->conv_factor)
		result=amount_to_convert*(whatBase->conv_factor/inWhatBase->conv_factor);
	else 
		return false;
	return true;

}

bool CaplUnitExchangeManager::GetPrdUnits(aplExtent &units)
{
	units.Clear();
	if(!attached)
		return false;

	if(m_ext_prd_unit.GetSize()) { units.Append(m_ext_prd_unit); return true; }

	// Сначала читаем класс из опций
	CString sClassIdOption;
	m_api->m_options_mgr.GetOptionValueBN( APL_NO_T("Изделия\\Класс единиц измерения для задания количества изделий"),sClassIdOption,_T(""));

	if(sClassIdOption!=_T(""))
	{
		sClassIdOption.MakeLower();
		aplExtent ext_unit_groups;
		CString buf;
		apidata.GetEntityExtent( m_api->m_charact_mgr.e_apl_unit_group , ext_unit_groups);
		int i;
		bool bFound=false;
		for(i=0; i<ext_unit_groups.Size; ++i)
		{
			CaplInstance *group=ext_unit_groups[i];
			m_api->m_data.GetAttr(group,  m_api->m_charact_mgr.a_apl_unit_group_id, buf);
			buf.MakeLower();
			if(buf==sClassIdOption)
			{
				m_api->m_data.GetAttr(group, m_api->m_charact_mgr.a_apl_unit_group_content, m_ext_prd_unit);
				bFound=true;
				break;
			}
		}
		if(!bFound)
		{
			CString buf;
			buf.Format(_T("Не найден заданный в настройках класс ед. изм. \"%s\"!"),LPCTSTR(sClassIdOption));
			AfxMessageBox(buf,MB_OK|MB_ICONWARNING);
		}
		if(0!=m_ext_prd_unit.GetSize()) { units.Append(m_ext_prd_unit); return true; }
	}


	// Если такого не нашли, то берем по базису.

	for(int i=0;i<all_unit_bases.GetSize();i++)
	{
		if( (all_unit_bases[i].length==0)&&
			(all_unit_bases[i].mass==0)&&
			(all_unit_bases[i].time==0)&&
			(all_unit_bases[i].electric_cur==0)&&
			(all_unit_bases[i].therm_temp==0)&&
			(all_unit_bases[i].am_of_sub==0)&&
			(all_unit_bases[i].lum_intens==0))
		{
			units.Add(all_unit_bases[i].cUnit);
			continue;
		}
		
		bool firstCheck=false;
		if((!all_unit_bases[i].electric_cur)&&
		   (!all_unit_bases[i].lum_intens)&&
		   (!all_unit_bases[i].therm_temp)&&
		   (!all_unit_bases[i].time))     
			firstCheck=true;
		if((firstCheck)&&
		   (all_unit_bases[i].am_of_sub==1)&&
		   (!all_unit_bases[i].length)&&
		   (!all_unit_bases[i].mass))
		   	units.Add(all_unit_bases[i].cUnit);
		if((firstCheck)&&
		   (!all_unit_bases[i].am_of_sub)&&
		   (!all_unit_bases[i].length)&&
		   (all_unit_bases[i].mass==1))
		   	units.Add(all_unit_bases[i].cUnit);
		if((firstCheck)&&
		   (!all_unit_bases[i].am_of_sub)&&
		   (all_unit_bases[i].length)&&
		   (!all_unit_bases[i].mass))
		   	units.Add(all_unit_bases[i].cUnit);

	}
	m_ext_prd_unit.Append(units);

	return true;
}
