//
// $Id: Test.cpp,v 1.19 2006/08/15 14:04:01 bakerj Exp $
//
//****************************************************************************************//
// Copyright (c) 2006, The MITRE Corporation
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice, this list
//       of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice, this 
//       list of conditions and the following disclaimer in the documentation and/or other
//       materials provided with the distribution.
//     * Neither the name of The MITRE Corporation nor the names of its contributors may be
//       used to endorse or promote products derived from this software without specific 
//       prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT 
// SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//****************************************************************************************//

#include "Test.h"

//TestVector Test::processedTests;
TestMap Test::processedTestsMap;
//****************************************************************************************//
//								Test Class												  //	
//****************************************************************************************//
Test::Test() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Create a compelete Test object
	//
	// -----------------------------------------------------------------------

	this->SetId("");
	this->SetResult(OvalEnum::ERROR_RESULT);
	this->SetVariableInstance(1);
	this->SetVersion(1);
	this->SetWritten(false);
	this->SetAnalyzed(false);
	this->SetCheck(OvalEnum::ALL_CHECK);
	this->SetObjectId("");
	this->SetStateId("");
}

Test::~Test() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	// -----------------------------------------------------------------------
	
	TestedItem* item = NULL;
	while(this->testedItems.size() != 0) {
		item = this->testedItems[this->testedItems.size()-1];
	  	this->testedItems.pop_back();
	  	delete item;
	  	item = NULL;
	}
}

// ***************************************************************************************	//
//								 Public members												//
// ***************************************************************************************	//

OvalEnum::Check Test::GetCheck() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the check field's value
	//
	// -----------------------------------------------------------------------

	return this->check;
}

void Test::SetCheck(OvalEnum::Check check) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the check field's value
	//
	// -----------------------------------------------------------------------

	this->check = check;
}

TestedItemVector* Test::GetTestedItems() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the testedItems field's value
	//
	// -----------------------------------------------------------------------

	return &this->testedItems;
}

void Test::SetTestedItems(TestedItemVector* testedItems) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the testedItems field's value
	//
	// -----------------------------------------------------------------------

	this->testedItems = (*testedItems);
}

void Test::AppendTestedItem(TestedItem* testedItem) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Add the specifed TestedItem tot he set of tested Items
	//
	// -----------------------------------------------------------------------
	
	this->GetTestedItems()->push_back(testedItem);
}

VariableValueVector* Test::GetTestedVariables() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the testedVariables field's value
	//
	// -----------------------------------------------------------------------

	return &this->testedVariables;
}

void Test::SetTestedVariables(VariableValueVector* testedVariables) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the testedVariables field's value
	//
	// -----------------------------------------------------------------------

	this->testedVariables = (*testedVariables);
}

void Test::AppendTestedVariable(VariableValue* testedVariable) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Add the specifed Testedvariable tot he set of tested variables
	//
	// -----------------------------------------------------------------------
	
	this->GetTestedVariables()->push_back(testedVariable);
}

string Test::GetId() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the id field's value
	//
	// -----------------------------------------------------------------------

	return this->id;
}

void Test::SetId(string id) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the id field's value
	//
	// -----------------------------------------------------------------------

	this->id = id;
}

string Test::GetObjectId() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the objectId field's value
	//
	// -----------------------------------------------------------------------

	return this->objectId;
}

void Test::SetObjectId(string objectId) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the objectId field's value
	//
	// -----------------------------------------------------------------------

	this->objectId = objectId;
}


string Test::GetStateId() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the stateId field's value
	//
	// -----------------------------------------------------------------------

	return this->stateId;
}

void Test::SetStateId(string stateId) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the stateId field's value
	//
	// -----------------------------------------------------------------------

	this->stateId = stateId;
}

OvalEnum::ResultEnumeration Test::GetResult() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the result field's value
	//
	// -----------------------------------------------------------------------

	return this->result;
}

void Test::SetResult(OvalEnum::ResultEnumeration result) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the result field's value
	//
	// -----------------------------------------------------------------------

	this->result = result;
}

int Test::GetVariableInstance() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the variableInstance field's value
	//
	// -----------------------------------------------------------------------

	return this->variableInstance;
}

void Test::SetVariableInstance(int variableInstance) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the variableInstance field's value
	//
	// -----------------------------------------------------------------------

	this->variableInstance = variableInstance;
}

int Test::GetVersion() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the version field's value
	//
	// -----------------------------------------------------------------------

	return this->version;
}

void Test::SetVersion(int version) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the version field's value
	//
	// -----------------------------------------------------------------------

	this->version = version;
}

bool Test::GetWritten() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the written field's value
	//
	// -----------------------------------------------------------------------

	return this->written;
}

void Test::SetWritten(bool written) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the written field's value
	//
	// -----------------------------------------------------------------------

	this->written = written;
}

bool Test::GetAnalyzed() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the analyzed field's value
	//
	// -----------------------------------------------------------------------

	return this->analyzed;
}

void Test::SetAnalyzed(bool analyzed) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	Return the analyzed field's value
	//
	// -----------------------------------------------------------------------

	this->analyzed = analyzed;
}

Test* Test::SearchCache(string id) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	search the cache of Tests for the specifed Test. 
	//	return NULL if not found
	//
	// -----------------------------------------------------------------------

	Test* cachedTest = NULL;

	//TestVector::iterator iterator;
	//for(iterator = Test::processedTests.begin(); iterator != Test::processedTests.end(); iterator++) {
	//	if((*iterator)->GetId().compare(id) == 0) {
	//		cachedTest = (*iterator);
	//		break;
	//	}
	//}

	TestMap::iterator iterator;
	iterator = Test::processedTestsMap.find(id);
	if(iterator != Test::processedTestsMap.end()) {
		cachedTest = iterator->second;
	} 

	return cachedTest;
}

void Test::ClearCache() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	delete all items in the cache
	//
	// -----------------------------------------------------------------------

	TestMap::iterator iterator;
	for(iterator = Test::processedTestsMap.begin(); iterator != Test::processedTestsMap.end(); iterator++) {
		
		Test* test = iterator->second;
		delete test;
	}
	
	Test::processedTestsMap.clear();
}

void Test::Write(DOMElement* parentElm) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	writes a Test element
	//	calls testedObject->Write() 
	//	calls testedVariable->Write(0 for each tested var
	//
	// -----------------------------------------------------------------------

	if(!this->GetWritten()) {
		this->SetWritten(true);

		// get the parent document
		XERCES_CPP_NAMESPACE_QUALIFIER DOMDocument* resultDoc = parentElm->getOwnerDocument();

		// create a new Test element
		DOMElement* testElm = XmlCommon::AddChildElement(resultDoc, parentElm, "test");

		// add the attributes
		XmlCommon::AddAttribute(testElm, "test_id", this->GetId());
		XmlCommon::AddAttribute(testElm, "version", Common::ToString(this->GetVersion()));
		XmlCommon::AddAttribute(testElm, "check", OvalEnum::CheckToString(this->GetCheck()));
		XmlCommon::AddAttribute(testElm, "result", OvalEnum::ResultToString(this->GetResult()));

		if(this->GetVariableInstance() != 1) {
			XmlCommon::AddAttribute(testElm, "variable_instance", Common::ToString(this->GetVariableInstance()));
		}	

		TestedItem* currentElement = NULL;
		while(this->GetTestedItems()->size() != 0) {
			currentElement = this->GetTestedItems()->at(this->GetTestedItems()->size()-1);
			this->GetTestedItems()->pop_back();
			currentElement->Write(testElm);	  		
	  		delete currentElement;
	  		currentElement = NULL;
		}

		// loop through all variable values and call write method
		VariableValueVector::iterator iterator1;
		for(iterator1 = this->GetTestedVariables()->begin(); iterator1 != this->GetTestedVariables()->end(); iterator1++) {
			(*iterator1)->WriteTestedVariable(testElm);
		}

		// loop through all vars in the state
		if(this->GetStateId().compare("") != 0) {
			State* tmpState = State::SearchCache(this->GetStateId());
			if(tmpState != NULL) { 
				VariableValueVector::iterator iterator2;
				VariableValueVector* stateVars = tmpState->GetVariableValues();
				for(iterator2 = stateVars->begin(); iterator2 != stateVars->end(); iterator2++) {
					(*iterator2)->WriteTestedVariable(testElm);
				}
			}
		}
	}
}

void Test::Parse(DOMElement* testElm) {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	parses Test elm to a Test obj
	//	calls Criteria->Parse() on the Criteria elm
	//
	// -----------------------------------------------------------------------

	// get id
	string id = XmlCommon::GetAttributeByName(testElm, "id");

	// get the attributes
	this->SetId(XmlCommon::GetAttributeByName(testElm, "id"));
	this->SetVersion(atoi(XmlCommon::GetAttributeByName(testElm, "version").c_str()));
	this->SetCheck(OvalEnum::ToCheck(XmlCommon::GetAttributeByName(testElm, "check")));

	// get the object element and the object id if it exists
	DOMElement* objectElm = XmlCommon::FindElementNS(testElm, "object");
	if(objectElm != NULL) {
		this->SetObjectId(XmlCommon::GetAttributeByName(objectElm, "object_ref"));
	}
    
	// get the state element and the state id if it exists
	DOMElement* stateElm = XmlCommon::FindElementNS(testElm, "state");
	if(stateElm != NULL) {
		string stateId = XmlCommon::GetAttributeByName(stateElm, "state_ref");
		this->SetStateId(stateId);
	}

	Test::processedTestsMap.insert(TestPair(this->GetId(), this));
}

OvalEnum::ResultEnumeration Test::Analyze() {
	// -----------------------------------------------------------------------
	//	Abstract
	//
	//	make sure not previously analyzed.
	//	get the collected object from the sc file by its id
	//		- check flag on collected object against check attribute on test
	//		- copy variable values from collected object to test as tested_variables
	//			this is just a matter of passing the variable_value element in the colelcted obj
	//			to the testedVaraible->Parse() method.
	//	if collected obj not ok stop
	//	parse all referencesin the colelcted object into Items
	//	get the state by its id and parse
	//	analyze each item against the state
	//	combine the result of each item with teh check attribute
	//	saves and returns the result
	//
	//
	// -----------------------------------------------------------------------

	if(!this->GetAnalyzed()) {

		// Does the test have a object ref?
		if(this->GetObjectId().compare("") == 0) {
			// Assumes it is only unknowns tests that do not have an object specifier and sets result to unknown
			this->SetResult(OvalEnum::UNKNOWN_RESULT);
		} else {
			// get the collected object from the sc file
			DOMElement* collectedObjElm = XmlCommon::FindElement(DocumentManager::GetSystemCharacterisitcsDocument(), "object", "id", this->GetObjectId());
			
			if(collectedObjElm == NULL) {
				// this is an error the interpreter requires that all objects in a definition
				// file have a corresponding collected object in the sc file
				Log::Info("Test::Analyze() - Test id: " + this->GetId() + " Unable to locate corresponding collected object in system characteristics file for object id: " + this->GetObjectId());
				this->SetResult(OvalEnum::ERROR_RESULT);
			} else {

				// Copy all variables in the collected object into Tested variables for the results file
				// Copy all item references into tested items for the results file
				// loop over all child elements and call tested object
				DOMNodeList *collectedObjChildren = collectedObjElm->getChildNodes();
				unsigned int index = 0;
				while(index < collectedObjChildren->getLength()) {
					DOMNode *tmpNode = collectedObjChildren->item(index);

					//	only concerned with ELEMENT_NODEs
					if (tmpNode->getNodeType() == DOMNode::ELEMENT_NODE) {
						DOMElement *collectedObjChildElm = (DOMElement*)tmpNode;
						
						//	get the name of the child and construct the appropriate criteria type
						string childName = XmlCommon::GetElementName(collectedObjChildElm);
						if(childName.compare("reference") == 0) {
							// get the reference's id
							string itemId = XmlCommon::GetAttributeByName(collectedObjChildElm, "item_ref");
							
							// create a new tested item
						    TestedItem* testedItem = new TestedItem();
							Item* item = Item::SearchCache(atoi(itemId.c_str()));
							if(item != NULL) {                          
								testedItem->SetItem(item);
							} else {
								// get the item elm in the sc file
								DOMElement* itemElm = XmlCommon::FindElementByAttribute(DocumentManager::GetSystemCharacterisitcsDocument()->getDocumentElement(), "id", itemId);
                                testedItem->Parse(itemElm);
							}
							this->AppendTestedItem(testedItem);
						
						} else if(childName.compare("variable_value") == 0) {
							// create a new tested variable
							VariableValue* testedVar = new VariableValue();
							testedVar->Parse(collectedObjChildElm);
							this->AppendTestedVariable(testedVar);
						} 
					}
					index ++;
				}

				// check the flag on the collected object
				string flagStr = XmlCommon::GetAttributeByName(collectedObjElm, "flag");
				OvalEnum::Flag collectedObjFlag = OvalEnum::ToFlag(flagStr);

				// determine how to proceed based on flag value
				if(collectedObjFlag == OvalEnum::ERROR_FLAG) {
					this->SetResult(OvalEnum::ERROR_RESULT);
					// set result value for all tested items
					TestedItemVector::iterator iterator;
					for(iterator = this->GetTestedItems()->begin(); iterator != this->GetTestedItems()->end(); iterator++) {
						(*iterator)->SetResult(this->GetResult());
					}
				} else if(collectedObjFlag == OvalEnum::NOT_APPLICABLE_FLAG) {
					this->SetResult(OvalEnum::NOT_APPLICABLE_RESULT);
					// set result value for all tested items
					TestedItemVector::iterator iterator;
					for(iterator = this->GetTestedItems()->begin(); iterator != this->GetTestedItems()->end(); iterator++) {
						(*iterator)->SetResult(this->GetResult());
					}
				} else if(collectedObjFlag == OvalEnum::NOT_COLLECTED_FLAG) {
					this->SetResult(OvalEnum::UNKNOWN_RESULT);
					// set result value for all tested items
					TestedItemVector::iterator iterator;
					for(iterator = this->GetTestedItems()->begin(); iterator != this->GetTestedItems()->end(); iterator++) {
						(*iterator)->SetResult(this->GetResult());
					}
				} else if(collectedObjFlag == OvalEnum::INCOMPLETE_FLAG) {
					// this interpreter can not determine a test result if a collected object in incomplete
					Log::Debug("Test::Analyze() - Test id: " + this->GetId() + " Unable to compute non error test result due to incomplete collected object. object id: " + this->GetObjectId()); 
					this->SetResult(OvalEnum::ERROR_RESULT);
					// set result value for all tested items
					TestedItemVector::iterator iterator;
					for(iterator = this->GetTestedItems()->begin(); iterator != this->GetTestedItems()->end(); iterator++) {
						(*iterator)->SetResult(this->GetResult());
					}
				} else if(collectedObjFlag == OvalEnum::DOES_NOT_EXIST_FLAG) {
					// determine the result based on the check attribute for this test
					if(this->GetCheck() == OvalEnum::NONE_EXIST_CHECK) {
						this->SetResult(OvalEnum::TRUE_RESULT);
					} else {
						this->SetResult(OvalEnum::FALSE_RESULT);
					}
					// set result value for all tested items
					TestedItemVector::iterator iterator;
					for(iterator = this->GetTestedItems()->begin(); iterator != this->GetTestedItems()->end(); iterator++) {
						(*iterator)->SetResult(this->GetResult());
					}
				} else if(collectedObjFlag == OvalEnum::COMPLETE_FLAG) {
					// to determine remaining results need to process collected object item refereneces

					// is there a state associated with this test?
					if(this->GetStateId().compare("") == 0) {
						// no state specified
						// so if check is only one either true or false based on number of items found in colected object
						// otherwise the result is true
						if(this->GetCheck() == OvalEnum::ONLY_ONE_CHECK) {
							if(this->GetTestedItems()->size() == 1) {
								this->SetResult(OvalEnum::TRUE_RESULT);
							} else {
								this->SetResult(OvalEnum::FALSE_RESULT);
							}
						} else if(this->GetCheck() == OvalEnum::NONE_EXIST_CHECK) {
							// no state specified and an item was found here we are
							// essentially checking that no item was found so result is false
							this->SetResult(OvalEnum::FALSE_RESULT);
						} else {
							this->SetResult(OvalEnum::TRUE_RESULT);
						}

						// set result value for all tested items
						TestedItemVector::iterator iterator;
						for(iterator = this->GetTestedItems()->begin(); iterator != this->GetTestedItems()->end(); iterator++) {
							(*iterator)->SetResult(this->GetResult());
						}

					} else {
						State* tmpState =  State::SearchCache(this->GetStateId());
						if(tmpState == NULL) {
							// state not already cached so parse it
							tmpState = new State(this->GetStateId());
						}

						// analyze each tested item
						IntVector results;
						TestedItemVector::iterator iterator;
						for(iterator = this->GetTestedItems()->begin(); iterator != this->GetTestedItems()->end(); iterator++) {
							OvalEnum::ResultEnumeration tmpResult;
							tmpResult = tmpState->Analyze((*iterator)->GetItem());
							(*iterator)->SetResult(tmpResult);
							results.push_back(tmpResult);
						}

						// combine results based on the check attribute
						this->SetResult(OvalEnum::CombineResultsByCheck(&results, this->GetCheck()));
					}
				}
			}
		}
		this->SetAnalyzed(true);
	}

	return this->GetResult();
}


