/*
 * @file
 * @author University of Warwick
 * @version 1.0
 *
 * @section LICENSE
 *
 * @section DESCRIPTION
 *
 * Unit Tests for the methods of the Hexahedral class
 */

#define BOOST_TEST_MODULE Hexahedron
#include <boost/test/unit_test.hpp>
#include <boost/test/output_test_stream.hpp>
#include <stdexcept>

#include "Hexahedron.h"
#include "EuclideanPoint.h"

using namespace cupcfd::geometry::shapes;
namespace euc = cupcfd::geometry::euclidean;
namespace utf = boost::unit_test;

// === Constructor ===
// Test 1: Construct a regular hexahedron
BOOST_AUTO_TEST_CASE(constructor_test1, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	Quadrilateral3D<double> faces[6] = {top, bottom, front, rear, left, right};
	for (int f=0; f<6; f++) {
		for (int v=0; v<4; v++) {
			for (int c=0; c<3; c++) {
				BOOST_TEST(shape.faces[f].vertices[v].cmp[c] == faces[f].vertices[v].cmp[c]);
			}
		}
	}
}

// === isPointInside ===
// Test 1: Point is inside Hexahedron
BOOST_AUTO_TEST_CASE(isPointInside_test1, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(2.3, 4.6, 6.7);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 2: Point is outside Hexahedron
BOOST_AUTO_TEST_CASE(isPointInside_test2, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(2.3, 4.6, 13.7);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, false);
}

// Test 3: Point is on Vertex tlf
BOOST_AUTO_TEST_CASE(isPointInside_test3, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	bool isPointInside = shape.isPointInside(tlf);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 4: Point is on Vertex trf
BOOST_AUTO_TEST_CASE(isPointInside_test4, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	bool isPointInside = shape.isPointInside(trf);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 5: Point is on Vertex blf
BOOST_AUTO_TEST_CASE(isPointInside_test5, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	bool isPointInside = shape.isPointInside(blf);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 6: Point is on Vertex brf
BOOST_AUTO_TEST_CASE(isPointInside_test6, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	bool isPointInside = shape.isPointInside(brf);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 7: Point is on Vertex tlb
BOOST_AUTO_TEST_CASE(isPointInside_test7, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	bool isPointInside = shape.isPointInside(tlb);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 8: Point is on Vertex trb
BOOST_AUTO_TEST_CASE(isPointInside_test8, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	bool isPointInside = shape.isPointInside(trb);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 9: Point is on Vertex blb
BOOST_AUTO_TEST_CASE(isPointInside_test9, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	bool isPointInside = shape.isPointInside(blb);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 10: Point is on Vertex brb
BOOST_AUTO_TEST_CASE(isPointInside_test10, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	bool isPointInside = shape.isPointInside(brb);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 11: Point is on Top Face
BOOST_AUTO_TEST_CASE(isPointInside_test11, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(2.3, 4.6, 12.0);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 12: Point is on Bottom Face
BOOST_AUTO_TEST_CASE(isPointInside_test12, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(2.3, 3.853, 0.0);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 13: Point is on Left Face
BOOST_AUTO_TEST_CASE(isPointInside_test13, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(0.0, 2.98, 5.2);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 14: Point is on Right Face
BOOST_AUTO_TEST_CASE(isPointInside_test14, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(5.0, 2.98, 5.2);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 15: Point is on Front Face
BOOST_AUTO_TEST_CASE(isPointInside_test15, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(1.63, 5.0, 5.2);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 16: Point is on Back Face
BOOST_AUTO_TEST_CASE(isPointInside_test16, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(1.63, 0.0, 5.2);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, true);
}

// Test 17: Point is Outside
BOOST_AUTO_TEST_CASE(isPointInside_test17, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0909091,0.5,0.454545);
	euc::EuclideanPoint<double,3> trf(0.0909091,0.5,0.545455);
	euc::EuclideanPoint<double,3> blf(0.0,0.5,0.454545);
	euc::EuclideanPoint<double,3> brf(0.0,0.5,0.545455);
	euc::EuclideanPoint<double,3> tlb(0.0909091,0.583333,0.454545);
	euc::EuclideanPoint<double,3> trb(0.0909091,0.583333,0.545455);
	euc::EuclideanPoint<double,3> blb(0.0,0.583333,0.454545);
	euc::EuclideanPoint<double,3> brb(0.0,0.583333,0.545455);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(0.23, 0.572, 0.5563);

	bool isPointInside = shape.isPointInside(point);
	BOOST_CHECK_EQUAL(isPointInside, false);
}

// Test 18: Point is on same plane as a face, but not in a face

// ToDo: Point on Edges Tests

// === getVolume ===
// Test 1: Compute volume of cube
BOOST_AUTO_TEST_CASE(getVolume_test1, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	double volume = shape.getVolume();

	BOOST_TEST(volume == 300.0);
}

// Test 2: Compute volume of more irregular hexahedron
// ToDo
BOOST_AUTO_TEST_CASE(getVolume_test2, * utf::tolerance(0.00001))
{

}

// === getCentroid ===
// Test 1: Compute correct centroid of a cube
BOOST_AUTO_TEST_CASE(getCentroid_test1, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> centroid = shape.getCentroid();
	BOOST_TEST(centroid.cmp[0], 2.5);
	BOOST_TEST(centroid.cmp[1], 2.5);
	BOOST_TEST(centroid.cmp[2], 6.0);
}

// === isPointOnEdge ===
// Test 1: Test point that is not on an edge
BOOST_AUTO_TEST_CASE(isPointOnEdge_test1, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(3.4, 2.3, 4.7);

	bool onEdge = shape.isPointOnEdge(point);

	BOOST_CHECK_EQUAL(onEdge, false);
}

// Test 2: Test point that is on an edge
BOOST_AUTO_TEST_CASE(isPointOnEdge_test2, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(2.13, 5.0, 12.0);

	bool onEdge = shape.isPointOnEdge(point);

	BOOST_CHECK_EQUAL(onEdge, true);
}

// === isPointOnVertex ===
// Test 1: Test point that is not on a vertex
BOOST_AUTO_TEST_CASE(isPointOnVertex_test1, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(2.3, 5.0, 12.0);

	bool onEdge = shape.isPointOnVertex(point);

	BOOST_CHECK_EQUAL(onEdge, false);
}

// Test 2: Test point that is on a vertex
BOOST_AUTO_TEST_CASE(isPointOnVertex_test2, * utf::tolerance(0.00001))
{
	euc::EuclideanPoint<double,3> tlf(0.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> trf(5.0, 5.0, 12.0);
	euc::EuclideanPoint<double,3> blf(0.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> brf(5.0, 5.0, 0.0);
	euc::EuclideanPoint<double,3> tlb(0.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> trb(5.0, 0.0, 12.0);
	euc::EuclideanPoint<double,3> blb(0.0, 0.0, 0.0);
	euc::EuclideanPoint<double,3> brb(5.0, 0.0, 0.0);
	Quadrilateral3D<double> top(trf, tlf, tlb, trb);
	Quadrilateral3D<double> bottom(brf, brb, blb, blf);
	Quadrilateral3D<double> front(trf, brf, blf, tlf);
	Quadrilateral3D<double> rear(tlb, blb, brb, trb);
	Quadrilateral3D<double> left(tlb, tlf, blf, blb);
	Quadrilateral3D<double> right(trf, trb, brb, brf);
	Hexahedron<double> shape(top, bottom, front, rear, left, right);

	euc::EuclideanPoint<double,3> point(0.0, 0.0, 12.0);

	bool onEdge = shape.isPointOnVertex(point);

	BOOST_CHECK_EQUAL(onEdge, true);
}


#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <tuple>
#include <stdbool.h>
#include <bitset>
#include <string>
#include <boost/multiprecision/cpp_int.hpp>

namespace mp = boost::multiprecision;
using namespace std;

int main(void) {
    int n;
    cin >> n;

    int sum = 0;
    int max = 0;
    for(int i = 0 ; i < n ; ++i) {
        int p;
        cin >> p;
        sum += p;
        if(max < p) {
            max = p;
        }
    }
    sum -= max / 2;
    cout << sum << endl;
    return 0;
}


/**
  *
  * @file SpatialPoint.cpp
  * @author Naoki Takahashi
  *
  **/

#include "SpatialPoint.hpp"

#include <sstream>

#include <Eigen/Geometry>
#include <unsupported/Eigen/EulerAngles>

namespace Kinematics {
	namespace Quantity {
		template <typename Scalar>
		void SpatialPoint<Scalar>::reset() {
			reset_point();
			reset_angle();
		}

		template <typename Scalar>
		void SpatialPoint<Scalar>::reset_point() {
			point_vector = Point::Zero();
		}

		template <typename Scalar>
		void SpatialPoint<Scalar>::reset_angle() {
			q_rotation.vec() = Vector3::Zero();
			q_rotation.w() = 0;
		}

		template <typename Scalar>
		const typename SpatialPoint<Scalar>::Point &SpatialPoint<Scalar>::point() const {
			return point_vector;
		}

		template <typename Scalar>
		SpatialPoint<Scalar> &SpatialPoint<Scalar>::point(const Point &new_point) {
			point_vector = new_point;

			return *this;
		}

		template <typename Scalar>
		SpatialPoint<Scalar> &SpatialPoint<Scalar>::point(const Scalar &x, const Scalar &y, const Scalar &z) {
			point_vector << x, y, z;

			return *this;
		}

		template <typename Scalar>
		const typename SpatialPoint<Scalar>::EulerAngles &SpatialPoint<Scalar>::angle() const {
			static Eigen::EulerAngles<Scalar, Eigen::EulerSystem<Eigen::EULER_X, Eigen::EULER_Y, Eigen::EULER_Z>> euler_angle_system;

			euler_angle_system = q_rotation;

			return euler_angle_system.angles();
		}

		template <typename Scalar>
		SpatialPoint<Scalar> &SpatialPoint<Scalar>::angle(const EulerAngles &new_angle) {
			q_rotation = Eigen::AngleAxis<Scalar>(new_angle.x(), EulerAngles::UnitX());
			q_rotation = q_rotation * Eigen::AngleAxis<Scalar>(new_angle.y(), EulerAngles::UnitY());
			q_rotation = q_rotation * Eigen::AngleAxis<Scalar>(new_angle.z(), EulerAngles::UnitZ());

			return *this;
		}

		template <typename Scalar>
		SpatialPoint<Scalar> &SpatialPoint<Scalar>::angle(const Scalar &roll, const Scalar &pitch, const Scalar &yaw) {
			q_rotation = Eigen::AngleAxis<Scalar>(roll, EulerAngles::UnitX());
			q_rotation = q_rotation * Eigen::AngleAxis<Scalar>(pitch, EulerAngles::UnitY());
			q_rotation = q_rotation * Eigen::AngleAxis<Scalar>(yaw, EulerAngles::UnitZ());

			return *this;
		}

		template <typename Scalar>
		const typename SpatialPoint<Scalar>::Quaternion &SpatialPoint<Scalar>::quaternion() {
			return q_rotation;
		}

		template <typename Scalar>
		SpatialPoint<Scalar> &SpatialPoint<Scalar>::quaternion(const Quaternion &quaternion) {
			q_rotation = quaternion;

			return *this;
		}

		template <typename Scalar>
		const typename SpatialPoint<Scalar>::Matrix3x3 &SpatialPoint<Scalar>::rotation() {
			static Matrix3x3 rotation_matrix;

			rotation_matrix = q_rotation.matrix();

			return rotation_matrix;

		}

		template <typename Scalar>
		SpatialPoint<Scalar> &SpatialPoint<Scalar>::rotation(const Matrix3x3 &rotation_matrix) {
			const auto rrt = rotation_matrix * rotation_matrix.transpose();

			if(1e-5 < (Matrix3x3::Identity() - rrt).norm()) {
				std::stringstream ss;

				ss << "Input Matrix is\n" << rotation_matrix << "\n";
				ss << "\nM*MT is\n" << rrt << "\n";
				ss << (Matrix3x3::Identity() - rrt).norm() << "\n";
				ss << "\n";

				throw std::runtime_error("Unknown type matrix from Kinematics::SpatialPoint\n" + ss.str());
			}

			q_rotation = rotation_matrix;

			return *this;
		}

		template <typename Scalar>
		void SpatialPoint<Scalar>::operator ()(const Vector6 &new_point_with_angle_vector) {
			point(new_point_with_angle_vector.block(0, 0, Rank3, 1));
			angle(new_point_with_angle_vector.block(Rank3, 0, Rank3, 1));
		}

		template <typename Scalar>
		const typename SpatialPoint<Scalar>::Vector6 SpatialPoint<Scalar>::operator ()() {
			static Vector6 point_with_angle_vector;

			point_with_angle_vector << point(), angle();

			return point_with_angle_vector;
		}

		template <typename Scalar>
		SpatialPoint<Scalar> &SpatialPoint<Scalar>::operator += (const SpatialPoint<Scalar> &spatial_point) {
			if(this != &spatial_point) {
				static Eigen::EulerAngles<Scalar, Eigen::EulerSystem<Eigen::EULER_X, Eigen::EULER_Y, Eigen::EULER_Z>> euler_angle_system;

				euler_angle_system = spatial_point.q_rotation;

				this->angle(this->angle() + euler_angle_system.angles());

				this->point_vector += spatial_point.point_vector;
			}

			return *this;
		}

		template <typename Scalar>
		SpatialPoint<Scalar> &SpatialPoint<Scalar>::operator -= (const SpatialPoint<Scalar> &spatial_point) {
			if(this != &spatial_point) {
				static Eigen::EulerAngles<Scalar, Eigen::EulerSystem<Eigen::EULER_X, Eigen::EULER_Y, Eigen::EULER_Z>> euler_angle_system;

				euler_angle_system = spatial_point.q_rotation;

				this->angle(this->angle() - euler_angle_system.angles());

				this->point_vector -= spatial_point.point_vector;
			}

			return *this;
		}

		template <typename Scalar>
		bool SpatialPoint<Scalar>::operator == (const SpatialPoint &spatial_point) {
			return
				this->point_vector == spatial_point.point_vector
				&& this->q_rotation.vec() == spatial_point.q_rotation.vec()
				&& this->q_rotation.w() == spatial_point.q_rotation.w();
		}

		template <typename Scalar>
		bool SpatialPoint<Scalar>::operator != (const SpatialPoint &spatial_point) {
			return !this->operator == (spatial_point);
		}

		template class SpatialPoint<float>;
		template class SpatialPoint<double>;
		template class SpatialPoint<long double>;
	}
}


//
// Copyright Fabien Dekeyser, Quoc-Cuong Pham 2008
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#ifndef BOOST_NUMERIC_BINDINGS_LAPACK_HEGV_HPP
#define BOOST_NUMERIC_BINDINGS_LAPACK_HEGV_HPP

#include <boost/numeric/bindings/traits/traits.hpp>
#include <boost/numeric/bindings/lapack/lapack.h>
#include <boost/numeric/bindings/lapack/workspace.hpp>
#include <boost/numeric/bindings/traits/detail/array.hpp>
// #include <boost/numeric/bindings/traits/std_vector.hpp>

#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
#  include <boost/static_assert.hpp>
#  include <boost/type_traits.hpp>
#endif

#include <cassert>

namespace boost { namespace numeric { namespace bindings {

  namespace lapack {

    ///////////////////////////////////////////////////////////////////
    //
    // hegv
    //
    ///////////////////////////////////////////////////////////////////

    /*
     *  hegv() computes all the eigenvalues, and optionally, the eigenvectors
     *  of a real generalized symmetric-definite eigenproblem, of the form
     *  A*x=(lambda)*B*x,  A*Bx=(lambda)*x,  or B*A*x=(lambda)*x.
     *  Here A and B are assumed to be symmetric and B is also
     *  positive definite.
     * TYPE   (input) INTEGER
     *          Specifies the problem type to be solved:
     *          = 1:  A*x = (lambda)*B*x
     *          = 2:  A*B*x = (lambda)*x
     *          = 3:  B*A*x = (lambda)*x
     *
     *  JOBZ    (input) CHARACTER*1
     *          = 'N':  Compute eigenvalues only;
     *          = 'V':  Compute eigenvalues and eigenvectors.
     *
     *  UPLO    (input) CHARACTER*1
     *          = 'U':  Upper triangles of A and B are stored;
     *          = 'L':  Lower triangles of A and B are stored.
     *
     *  N       (input) INTEGER
     *          The order of the matrices A and B.  N >= 0.
     *
     *  A       (input/output) DOUBLE PRECISION array, dimension (LDA, N)
     *          On entry, the symmetric matrix A.  If UPLO = 'U', the
     *          leading N-by-N upper triangular part of A contains the
     *          upper triangular part of the matrix A.  If UPLO = 'L',
     *          the leading N-by-N lower triangular part of A contains
     *          the lower triangular part of the matrix A.
     *
     *          On exit, if JOBZ = 'V', then if INFO = 0, A contains the
     *          matrix Z of eigenvectors.  The eigenvectors are normalized
     *          as follows:
     *          if ITYPE = 1 or 2, Z**T*B*Z = I;
     *          if ITYPE = 3, Z**T*inv(B)*Z = I.
     *          If JOBZ = 'N', then on exit the upper triangle (if UPLO='U')
     *          or the lower triangle (if UPLO='L') of A, including the
     *          diagonal, is destroyed.
     *
     *  LDA     (input) INTEGER
     *          The leading dimension of the array A.  LDA >= max(1,N).
     *
     *  B       (input/output) DOUBLE PRECISION array, dimension (LDB, N)
     *          On entry, the symmetric positive definite matrix B.
     *          If UPLO = 'U', the leading N-by-N upper triangular part of B
     *          contains the upper triangular part of the matrix B.
     *          If UPLO = 'L', the leading N-by-N lower triangular part of B
     *          contains the lower triangular part of the matrix B.
     *
     *          On exit, if INFO <= N, the part of B containing the matrix is
     *          overwritten by the triangular factor U or L from the Cholesky
     *          factorization B = U**T*U or B = L*L**T.
     *
     *  LDB     (input) INTEGER
     *          The leading dimension of the array B.  LDB >= max(1,N).
     *
     *  W       (output) DOUBLE PRECISION array, dimension (N)
     *          If INFO = 0, the eigenvalues in ascending order.
     *
     *  WORK    (workspace/output) DOUBLE PRECISION array, dimension (MAX(1,LWORK))
     *          On exit, if INFO = 0, WORK(1) returns the optimal LWORK.
     *
     *  LWORK   (input) INTEGER
     *          The length of the array WORK.  LWORK >= max(1,3*N-1).
     *          For optimal efficiency, LWORK >= (NB+2)*N,
     *          where NB is the blocksize for DSYTRD returned by ILAENV.
     *
     *          If LWORK = -1, then a workspace query is assumed; the routine
     *          only calculates the optimal size of the WORK array, returns
     *          this value as the first entry of the WORK array, and no error
     *          message related to LWORK is issued by XERBLA.
     *
     *  INFO    (output) INTEGER
     *          = 0:  successful exit
     *          < 0:  if INFO = -i, the i-th argument had an illegal value
     *          > 0:  DPOTRF or DSYEV returned an error code:
     *             <= N:  if INFO = i, DSYEV failed to converge;
     *                    i off-diagonal elements of an intermediate
     *                    tridiagonal form did not converge to zero;
     *             > N:   if INFO = N + i, for 1 <= i <= N, then the leading
     *                    minor of order i of B is not positive definite.
     *                    The factorization of B could not be completed and
     *                    no eigenvalues or eigenvectors were computed.
     *
     */

    namespace detail {

      inline
      void hegv (int const itype, char const jobz, char const uplo, int const n,
                 float *a, int const lda, float *b, int const ldb,
                 float *w, float *work, int const lwork, int& info)
      {
        LAPACK_SSYGV (&itype, &jobz, &uplo, &n, a, &lda, b, &ldb, w, work, &lwork, &info);
      }

      inline
      void hegv (int const itype, char const jobz, char const uplo, int const n,
                 double *a, int const lda, double *b, int const ldb,
                 double *w, double *work, int const lwork, int& info)
      {
        LAPACK_DSYGV (&itype, &jobz, &uplo, &n, a, &lda, b, &ldb, w, work, &lwork, &info);
      }

      inline
      void hegv (int const itype, char const jobz, char const uplo, int const n,
                 traits::complex_f *a, int const lda, traits::complex_f *b, int const ldb,
                 float *w, traits::complex_f *work, int const lwork, float *rwork, int& info)
      {
        LAPACK_CHEGV (&itype, &jobz, &uplo, &n, traits::complex_ptr(a), &lda,
          traits::complex_ptr(b), &ldb, w, traits::complex_ptr(work), &lwork, rwork, &info);
      }

      inline
      void hegv (int const itype, char const jobz, char const uplo, int const n,
                 traits::complex_d *a, int const lda, traits::complex_d *b, int const ldb,
                 double *w, traits::complex_d *work, int const lwork, double *rwork, int& info)
      {
        LAPACK_ZHEGV (&itype, &jobz, &uplo, &n, traits::complex_ptr(a), &lda,
          traits::complex_ptr(b), &ldb, w, traits::complex_ptr(work), &lwork, rwork, &info);
      }
    }  // namespace detail

    namespace detail {

      template <int N>
      struct Hegv{};

      /// Handling of workspace in the case of one workarray.
      template <>
      struct Hegv< 1 > {
        // Function that allocates work arrays
        template <typename T, typename R>
        void operator() (
                 int const itype, char const jobz, char const uplo, int const n,
                 T *a, int const lda, T *b, int const ldb,
                 R *w, optimal_workspace, int& info ) {

           traits::detail::array<T> work( std::max<int>(1,34*n) );

           detail::hegv(itype, jobz, uplo, n, a, lda, b, ldb, w,
             traits::vector_storage (work), traits::vector_size (work), info);
        }
        // Function that allocates work arrays
        template <typename T, typename R>
        void operator() (
                 int const itype, char const jobz, char const uplo, int const n,
                 T *a, int const lda, T *b, int const ldb,
                 R *w, minimal_workspace, int& info ) {

           traits::detail::array<T> work( std::max<int>(1,3*n-1) );

           detail::hegv(itype, jobz, uplo, n, a, lda, b, ldb, w,
             traits::vector_storage (work), traits::vector_size (work), info);
        }
        // Function that uses given workarrays
        template <typename T, typename R, typename W>
        void operator() (
                 int const itype, char const jobz, char const uplo, int const n,
                 T *a, int const lda, T *b, int const ldb,
                 R *w, detail::workspace1<W> work, int& info ) {

           assert (traits::vector_size (work.w_) >= 3*n-1);

           detail::hegv(itype, jobz, uplo, n, a, lda, b, ldb, w,
             traits::vector_storage (work.w_), traits::vector_size (work.w_), info);
        }
      };

      /// Handling of workspace in the case of two workarrays.
      template <>
      struct Hegv< 2 > {
        // Function that allocates work arrays
        template <typename T, typename R>
        void operator() (
                 int const itype, char const jobz, char const uplo, int const n,
                 T *a, int const lda, T *b, int const ldb,
                 R *w, optimal_workspace, int& info ) {

           traits::detail::array<T> work( std::max<int>(1,34*n) );
           traits::detail::array<R> rwork( std::max<int>(1,3*n-2) );

           detail::hegv(itype, jobz, uplo, n, a, lda, b, ldb, w,
             traits::vector_storage (work), traits::vector_size (work),
             traits::vector_storage (rwork), info);
        }
        // Function that allocates work arrays
        template <typename T, typename R>
        void operator() (
                 int const itype, char const jobz, char const uplo, int const n,
                 T *a, int const lda, T *b, int const ldb,
                 R *w, minimal_workspace, int& info ) {

           traits::detail::array<T> work( std::max<int>(1,2*n-1) );
           traits::detail::array<R> rwork( std::max<int>(1,3*n-2) );

           detail::hegv(itype, jobz, uplo, n, a, lda, b, ldb, w,
             traits::vector_storage (work), traits::vector_size (work),
             traits::vector_storage (rwork), info);
        }
        // Function that uses given workarrays
        template <typename T, typename R, typename WC, typename WR>
        void operator() (
                 int const itype, char const jobz, char const uplo, int const n,
                 T *a, int const lda, T *b, int const ldb,
                 R *w, detail::workspace2<WC,WR> work, int& info ) {

           assert (traits::vector_size (work.w_) >= 2*n-1);
           assert (traits::vector_size (work.wr_) >= 3*n-2);

           detail::hegv(itype, jobz, uplo, n, a, lda, b, ldb, w,
             traits::vector_storage (work.w_), traits::vector_size (work.w_),
             traits::vector_storage (work.wr_), info);
        }
      };    } // namespace detail

    template <typename A, typename B, typename W, typename Work>
    int hegv (int itype, char jobz, char uplo, A& a, B& b, W& w, Work work = optimal_workspace()) {

#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<A>::matrix_structure,
        traits::general_t
      >::value));
#endif

      int const n = traits::matrix_size1 (a);
      assert ( n>0 );
      assert (traits::matrix_size2 (a)==n);
      assert (traits::leading_dimension (a)>=n);
      assert (traits::vector_size (w)==n);

      int const nb = traits::matrix_size1 (b);
      assert ( nb>0 );
      assert (traits::matrix_size2 (b)==nb);
      assert (traits::leading_dimension (b)>=nb);
      assert ( n== nb);

      assert ( uplo=='U' || uplo=='L' );
      assert ( jobz=='N' || jobz=='V' );

      assert( itype==1 || itype==2 || itype==3);

      int info;
      detail::Hegv< n_workspace_args<typename A::value_type>::value >() (
                   itype, jobz, uplo, n,
                   traits::matrix_storage (a),
                   traits::leading_dimension (a),
                   traits::matrix_storage (b),
                   traits::leading_dimension (b),
                   traits::vector_storage (w),
                   work,
                   info);
      return info;
    }
  }

}}}

#endif


/*
Part of the Fluid Corpus Manipulation Project (http://www.flucoma.org/)
Copyright 2017-2019 University of Huddersfield.
Licensed under the BSD-3 License.
See license.md file in the project root for full license information.
This project has received funding from the European Research Council (ERC)
under the European Union’s Horizon 2020 research and innovation programme
(grant agreement No 725899).
*/

#pragma once

#include "../util/ButterworthHPFilter.hpp"
#include "../util/FluidEigenMappings.hpp"
#include "../util/SlideUDFilter.hpp"
#include "../../data/FluidIndex.hpp"
#include "../../data/TensorTypes.hpp"
#include <Eigen/Core>
#include <cmath>

namespace fluid {
namespace algorithm {

class EnvelopeGate
{

  using ArrayXd = Eigen::ArrayXd;

public:
  EnvelopeGate(index maxSize)
  {
    mInputStorage = ArrayXd(maxSize);
    mOutputStorage = ArrayXd(maxSize);
  }

  void init(double onThreshold, double offThreshold, double hiPassFreq,
            index minTimeAboveThreshold, index upwardLookupTime,
            index minTimeBelowThreshold, index downwardLookupTime)
  {
    using namespace std;

    mMinTimeAboveThreshold = minTimeAboveThreshold;
    mUpwardLookupTime = upwardLookupTime;
    mMinTimeBelowThreshold = minTimeBelowThreshold,
    mDownwardLookupTime = downwardLookupTime;
    mDownwardLatency = max<index>(minTimeBelowThreshold, mDownwardLookupTime);
    mLatency = max<index>(mMinTimeAboveThreshold + mUpwardLookupTime,
                          mDownwardLatency);
    if (mLatency < 0) mLatency = 1;
    mHiPassFreq = hiPassFreq;
    initFilters(mHiPassFreq);
    double initVal = min(onThreshold, offThreshold) - 1;
    initBuffers(initVal);
    mSlide.init(initVal);
    mInputState = false;
    mOutputState = false;
    mOnStateCount = 0;
    mOffStateCount = 0;
    mEventCount = 0;
    mSilenceCount = 0;
    mInitialized = true;
  }

  double processSample(const double in, double onThreshold, double offThreshold,
                       index rampUpTime, index rampDownTime, double hiPassFreq,
                       index minEventDuration, index minSilenceDuration)
  {
    using namespace std;
    assert(mInitialized);

    mSlide.updateCoeffs(rampUpTime, rampDownTime);

    double filtered = in;
    if (hiPassFreq != mHiPassFreq)
    {
      initFilters(hiPassFreq);
      mHiPassFreq = hiPassFreq;
    }
    if (mHiPassFreq > 0)
      filtered = mHiPass2.processSample(mHiPass1.processSample(in));

    double rectified = abs(filtered);
    double dB = 20 * log10(rectified);
    double floor = min(offThreshold, onThreshold) - 1;
    double clipped = max(dB, floor);
    double smoothed = mSlide.processSample(clipped);
    bool   forcedState = false;

    // case 1: we are waiting for event to finish
    if (mOutputState && mEventCount > 0)
    {
      if (mEventCount >= minEventDuration) { mEventCount = 0; }
      else
      {
        forcedState = true;
        mOutputBuffer(mWriteHead) = 1;
        mEventCount++;
      }
      // case 2: we are waiting for silence to finish
    }
    else if (!mOutputState && mSilenceCount > 0)
    {
      if (mSilenceCount >= minSilenceDuration) { mSilenceCount = 0; }
      else
      {
        forcedState = true;
        mOutputBuffer(mWriteHead) = 0;
        mSilenceCount++;
      }
    }
    // case 3: need to compute state
    if (!forcedState)
    {
      bool nextState = mInputState;
      if (!mInputState && smoothed >= onThreshold) { nextState = true; }
      if (mInputState && smoothed <= offThreshold) { nextState = false; }
      updateCounters(nextState);
      // establish and refine
      if (!mOutputState && mOnStateCount >= mMinTimeAboveThreshold &&
          mFillCount >= mLatency)
      {
        index onsetIndex =
            refineStart(mWriteHead - mMinTimeAboveThreshold - mUpwardLookupTime,
                        mUpwardLookupTime);

        index blockSize = mWriteHead > onsetIndex
                              ? mWriteHead - onsetIndex
                              : (mLatency - onsetIndex) + mWriteHead;

        index size = onsetIndex + blockSize > mLatency ? mLatency - onsetIndex
                                                       : blockSize;

        mOutputBuffer.segment(onsetIndex, size) = 1;
        mOutputBuffer.segment(0, blockSize - size) = 1;

        mEventCount = mOnStateCount;
        mOutputState = true; // we are officially on
      }
      else if (mOutputState && mOffStateCount >= mDownwardLatency &&
               mFillCount >= mLatency)
      {

        index offsetIndex =
            refineStart(mWriteHead - mDownwardLatency, mDownwardLookupTime);

        index blockSize = mWriteHead > offsetIndex
                              ? mWriteHead - offsetIndex
                              : (mLatency - offsetIndex) + mWriteHead;

        index size = offsetIndex + blockSize > mLatency ? mLatency - offsetIndex
                                                        : blockSize;

        mOutputBuffer.segment(offsetIndex, size) = 0;
        mOutputBuffer.segment(0, blockSize - size) = 0;

        mSilenceCount = mOffStateCount;
        mOutputState = false; // we are officially off
      }

      mOutputBuffer(mWriteHead) = mOutputState ? 1 : 0;

      mInputState = nextState;
    }

    mInputBuffer(mWriteHead) = smoothed;

    if (mFillCount < mLatency) mFillCount++;
    double result = mOutputBuffer(mReadHead);
    mWriteHead++;
    mWriteHead = mWriteHead % mOutputBuffer.size();
    mReadHead++;
    mReadHead = mReadHead % mOutputBuffer.size();
    return result;
}
  index getLatency() { return mLatency; }
  bool  initialized() { return mInitialized; }

private:
  void initBuffers(double initialValue)
  {
    using namespace std;
    mInputBuffer = mInputStorage.segment(0, max<index>(mLatency, 1))
                       .setConstant(initialValue);
    mOutputBuffer =
        mOutputStorage.segment(0, max<index>(mLatency, 1)).setZero();
    mInputState = false;
    mOutputState = false;
    mFillCount = max<index>(mLatency, 1);
    mWriteHead = mOutputBuffer.size() - 1;
    mReadHead = 0;
  }

  void initFilters(double cutoff)
  {
    mHiPass1.init(cutoff);
    mHiPass2.init(cutoff);
  }

  index refineStart(index start, index nSamples)
  {

    using Eigen::Array2d;
    using Eigen::Array2i;

    index circularStart = start < 0 ? mLatency + start : start;

    if (nSamples < 2)
      return circularStart + nSamples < mLatency
                 ? circularStart + nSamples
                 : circularStart + nSamples - mLatency;

    index circularNSamples = circularStart + nSamples > mLatency
                                 ? mLatency - circularStart
                                 : nSamples;

    if (circularNSamples == nSamples)
    {
      index argMin;
      mInputBuffer.segment(circularStart, nSamples).minCoeff(&argMin);
      return circularStart + argMin;
    }
    else
    {
      Array2i argMins;
      mInputBuffer.segment(circularStart, circularNSamples)
          .minCoeff(argMins.data());
      mInputBuffer.segment(0, nSamples - circularNSamples)
          .minCoeff(argMins.data() + 1);
      Array2d mins = {
          mInputBuffer.segment(circularStart, circularNSamples)(argMins(0)),
          mInputBuffer.segment(0, nSamples - circularNSamples)(argMins(1))};
      index whichArgMin;
      mins.minCoeff(&whichArgMin);

      return whichArgMin == 0 ? circularStart + argMins(0) : argMins(1);
    }
  }

  void updateCounters(bool nextState)
  {
    if (!mInputState && nextState)
    {
      mOffStateCount = 0;
      mOnStateCount = 1;
    }
    else if (mInputState && !nextState)
    {
      mOnStateCount = 0;
      mOffStateCount = 1;
    }
    else if (mInputState && nextState)
    {
      mOnStateCount++;
    }
    else if (!mInputState && !nextState)
    {
      mOffStateCount++;
    }
  }

  index  mLatency;
  index  mFillCount;
  double mHiPassFreq{0};

  index mMinTimeAboveThreshold{440};
  index mDownwardLookupTime{10};
  index mDownwardLatency;
  index mMinTimeBelowThreshold{10};
  index mUpwardLookupTime{24};

  ArrayXd mInputBuffer;
  ArrayXd mOutputBuffer;
  ArrayXd mInputStorage;
  ArrayXd mOutputStorage;
  index   mWriteHead;
  index   mReadHead;

  bool mInputState{false};
  bool mOutputState{false};

  index mOnStateCount{0};
  index mOffStateCount{0};
  index mEventCount{0};
  index mSilenceCount{0};
  bool  mInitialized{false};

  ButterworthHPFilter mHiPass1;
  ButterworthHPFilter mHiPass2;
  SlideUDFilter       mSlide;
};
} // namespace algorithm
} // namespace fluid


#include <iostream>
#include <algorithm>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <tuple>
#include <stdbool.h>
#include <bitset>
#include <string>
#include <boost/multiprecision/cpp_int.hpp>

namespace mp = boost::multiprecision;
using namespace std;

int main(void) {
    mp::cpp_int n;
    cin >> n;

    vector<mp::cpp_int> arr(6); // 移動時間
    vector<mp::cpp_int> term(6); // ターン数
    arr[0] = INT64_MAX;
    term[0] = 0; // dummy
    for(int i = 0 ; i < 5 ; ++i) {
        cin >> arr[i + 1];
    }

    for(int i = 0 ; i < 5 ; ++i) {
        if (arr[i + 1] > arr[i]) {
            // 前回の移動手段のほうが遅いので、そっちに引っ張られる
            term[i + 1] = term[i] + 1; // +1は1ターンのディレイ
        } else {
            mp::cpp_int t = (n / arr[i + 1]) + (n % arr[i + 1] == 0 ? 0 : 1) + i;
            mp::cpp_int recent = term[i] + 1;
            term[i + 1] = t < recent ? recent : t;
        }
    }
    auto result = term[term.size() - 1];
    cout << result << endl;
    return 0;
}


#include "vtkNonRigidICP.h"
#include <Eigen/Sparse>
#include <vtkDoubleArray.h>
#include <vtkInformation.h>
#include <vtkInformationVector.h>
#include <vtkKdTreePointLocator.h>
#include <vtkPointData.h>
#include <vtkPolyDataNormals.h>
//#include <chrono>

vtkStandardNewMacro(vtkNonRigidICP);

using Triplet = Eigen::Triplet<double>;

void vtkNonRigidICP::Cleanup()
{
	outputCellArray = NULL;
	fixedKdTree = NULL;
}

int vtkNonRigidICP::RequestData(vtkInformation* vtkNotUsed(request), vtkInformationVector** inputVec, vtkInformationVector* outputVec)
{
	// Get input polys
	vtkInformation* inInfo1 = inputVec[0]->GetInformationObject(0);
	vtkPolyData* fixedPolyData = vtkPolyData::SafeDownCast(inInfo1->Get(vtkDataObject::DATA_OBJECT()));
	vtkInformation* inInfo2 = inputVec[1]->GetInformationObject(0);
	vtkPolyData* movingPolyData = vtkPolyData::SafeDownCast(inInfo2->Get(vtkDataObject::DATA_OBJECT()));
	// Get output poly
	vtkInformation* outInfo = outputVec->GetInformationObject(0);
	vtkPolyData* outputMovingPolyData = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

	outputMovingPolyData->DeepCopy(movingPolyData);

	// Only compute if we haven't stored a previous one
	if (outputCellArray == NULL)
	{
		// Compute the edge only topology of the output polygon
		outputCellArray = vtkSmartPointer<vtkCellArray>::New();
		for (vtkIdType i = 0; i < outputMovingPolyData->GetNumberOfCells(); i++)
		{
			vtkCell* cell = outputMovingPolyData->GetCell(i);
			if (cell->GetNumberOfPoints() == 3)
			{
				vtkIdType ptIds1[2] = { cell->GetPointId(0), cell->GetPointId(1) };
				outputCellArray->InsertNextCell(2, ptIds1);
				vtkIdType ptIds2[2] = { cell->GetPointId(1), cell->GetPointId(2) };
				outputCellArray->InsertNextCell(2, ptIds2);
				vtkIdType ptIds3[2] = { cell->GetPointId(2), cell->GetPointId(0) };
				outputCellArray->InsertNextCell(2, ptIds3);
			}
			else if (cell->GetNumberOfPoints() == 2)
			{
				vtkIdType ptIds[2] = { cell->GetPointId(0), cell->GetPointId(1) };
				outputCellArray->InsertNextCell(2, ptIds);
			}
		}
	}
	// Setup a kdtree for nearest neighbor searches on the fixed poly
	if (fixedKdTree == NULL)
	{
		fixedKdTree = vtkSmartPointer<vtkKdTreePointLocator>::New();
		fixedKdTree->SetDataSet(fixedPolyData);
		fixedKdTree->BuildLocator();
	}

	double* targetBounds = fixedPolyData->GetBounds();
	double maxDist = std::pow(std::pow(targetBounds[1] - targetBounds[0], 2) + std::pow(targetBounds[3] - targetBounds[2], 2) + std::pow(targetBounds[5] - targetBounds[4], 2), 1.0 / 3.0);

	const vtkIdType fixedPolyNumberOfPoints = fixedPolyData->GetNumberOfPoints();
	const vtkIdType movingPolyNumberOfPoints = outputMovingPolyData->GetNumberOfPoints();
	const vtkIdType movingPolyNumberOfCells = outputCellArray->GetNumberOfCells();

	double stiffness = Stiffness;
	vtkNew<vtkIdTypeArray> correspondences;
	correspondences->SetName("Correspondences");
	correspondences->SetNumberOfValues(movingPolyNumberOfPoints);

	for (unsigned int iter = 0; iter < NumberOfIterations; iter++)
	{
		vtkNew<vtkKdTreePointLocator> movingKdTree;
		movingKdTree->SetDataSet(movingPolyData);
		movingKdTree->Update();

		vtkNew<vtkPolyDataNormals> normalsFilter;
		normalsFilter->SetInputData(outputMovingPolyData);
		normalsFilter->Update();
		vtkDataArray* normalsArray = normalsFilter->GetOutput()->GetPointData()->GetNormals();

		correspondences->Fill(-1);
		// Find the forward correspondences of all the moving points
		for (vtkIdType i = 0; i < movingPolyNumberOfPoints; i++)
		{
			double pt1[3];
			outputMovingPolyData->GetPoint(i, pt1);

			const vtkIdType closestPtIndex = fixedKdTree->FindClosestPoint(pt1);
			correspondences->SetValue(i, closestPtIndex);
		}

		// Construct matrix A, composed of MG and WD
		Eigen::SparseMatrix<double> A(4 * movingPolyNumberOfCells + movingPolyNumberOfPoints, 4 * movingPolyNumberOfPoints);

		std::vector<Triplet> ATripletList;
		ATripletList.reserve(movingPolyNumberOfCells * 8 + 4 * movingPolyNumberOfPoints);

		// Construct M kronecker G, regularization term resulting from the topology of the mesh
		vtkNew<vtkIdList> idList;
		outputCellArray->InitTraversal();
		for (vtkIdType i = 0; i < movingPolyNumberOfCells; i++)
		{
			outputCellArray->GetNextCell(idList);
			const vtkIdType id1 = idList->GetId(0);
			const vtkIdType id2 = idList->GetId(1);

			for (unsigned int j = 0; j < 3; j++)
			{
				ATripletList.push_back(Triplet(4 * i + j, 4 * id1 + j, 1.0  * stiffness));
			}
			ATripletList.push_back(Triplet(4 * i + 3, 4 * id1 + 3, SkewWeight * stiffness));

			for (unsigned int j = 0; j < 3; j++)
			{
				ATripletList.push_back(Triplet(4 * i + j, 4 * id2 + j, -1.0 * stiffness));
			}
			ATripletList.push_back(Triplet(4 * i + 3, 4 * id2 + 3, -SkewWeight * stiffness));
		}
		// Construct WD, for the distance term
		for (vtkIdType i = 0; i < movingPolyNumberOfPoints; i++)
		{
			const double w = (correspondences->GetValue(i) != -1) ? 1.0 : 0.0;
			double pt[3];
			outputMovingPolyData->GetPoint(i, pt);
			for (vtkIdType j = 0; j < 3; j++)
			{
				ATripletList.push_back(Triplet(4 * movingPolyNumberOfCells + i, i * 4 + j, pt[j] * w));
			}
			ATripletList.push_back(Triplet(4 * movingPolyNumberOfCells + i, i * 4 + 3, 1.0 * w));
		}
		A.setFromTriplets(ATripletList.begin(), ATripletList.end());

		Eigen::SparseMatrix<double> AT(A.cols(), A.rows());
		AT = A.transpose();
		Eigen::SparseMatrix<double> ATA(4 * movingPolyNumberOfPoints, 4 * movingPolyNumberOfPoints);
		ATA = AT * A;

		// Construct matrix B, resulting from correspodences of the points (data term)
		Eigen::MatrixXd B(4 * movingPolyNumberOfCells + movingPolyNumberOfPoints, 3);
		B.setZero();

		for (unsigned int i = 0; i < movingPolyNumberOfPoints; i++)
		{
			const vtkIdType correspondence = correspondences->GetValue(i);
			const double w = (correspondence != -1) ? 1.0 : 0.0;
			double pt[3];
			fixedPolyData->GetPoint(correspondence, pt);
			for (int j = 0; j < 3; j++)
			{
				B(4 * movingPolyNumberOfCells + i, j) = pt[j] * w;
			}
		}

		Eigen::MatrixXd ATB(4 * movingPolyNumberOfPoints, 3);
		ATB.setZero();
		ATB = AT * B;

		// Write the matrices
		/*Eigen::saveMarket(AmatrixFinal, "C:/Users/Andx_/Desktop/a.txt");
		Eigen::saveMarket(BmatrixFinal, "C:/Users/Andx_/Desktop/b.txt");*/

		Eigen::SparseLU<Eigen::SparseMatrix<double>, Eigen::COLAMDOrdering<int>> solver;
		solver.analyzePattern(ATA);
		solver.factorize(ATA);
		if (solver.info() != Eigen::Success)
			printf("Decomposition Failed.\n");
		Eigen::MatrixXd X(4 * movingPolyNumberOfPoints, 3);
		X.setZero();
		X = solver.solve(ATB);

		// Do the transformation
		vtkPoints* pts = outputMovingPolyData->GetPoints();
		for (vtkIdType i = 0; i < movingPolyNumberOfPoints; i++)
		{
			double pt[3];
			pts->GetPoint(i, pt);

			double results[3] = { 0.0, 0.0, 0.0 };
			for (int j = 0; j < 3; j++)
			{
				results[j] =
					pt[0] * X(i * 4 + 0, j) +
					pt[1] * X(i * 4 + 1, j) +
					pt[2] * X(i * 4 + 2, j) +
					1.0 * X(i * 4 + 3, j);
			}
			//printf("%f,%f,%f,%f\n", X(i * 4 + 0, 0), X(i * 4 + 1, 1), X(i * 4 + 2, 2), X(i * 4 + 3, 3));

			pts->SetPoint(i, results[0], results[1], results[2]);
		}

		// Invokes the progress update event
		if (iter % 4 == 0)
			UpdateProgress(static_cast<double>(iter) / NumberOfIterations);
	}

	if (!InteractiveExecute)
		Cleanup();

	if (OutputDisplacements)
	{
		// Stash a copy of the registered/moved surface
		vtkNew<vtkPolyData> registeredPolyData;
		registeredPolyData->DeepCopy(outputMovingPolyData);

		// Bring back the original polygon
		outputMovingPolyData->DeepCopy(movingPolyData);

		// Setup an array of displacements
		vtkNew<vtkDoubleArray> displacements;
		displacements->SetName("Displacements");
		displacements->SetNumberOfComponents(3);
		displacements->SetNumberOfTuples(movingPolyNumberOfPoints);

		// The indices should be the same so just compute vector differences
		for (vtkIdType i = 0; i < movingPolyNumberOfPoints; i++)
		{
			double orgPt[3];
			double movedPt[3];
			registeredPolyData->GetPoint(i, movedPt);
			outputMovingPolyData->GetPoint(i, orgPt);
			displacements->SetTuple3(i, movedPt[0] - orgPt[0], movedPt[1] - orgPt[1], movedPt[2] - orgPt[2]);
		}

		outputMovingPolyData->GetPointData()->SetScalars(displacements);
	}

	return 1;
}


/*=============================================================================
 *
 *  Copyright (c) 2021 Sunnybrook Research Institute
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in all
 *  copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 *
 *=============================================================================*/

#include "StainVectorMath.h"
#include "ODConversion.h"

//Boost includes
#include <boost/qvm/vec.hpp>
#include <boost/qvm/mat.hpp>
#include <boost/qvm/vec_access.hpp>
#include <boost/qvm/mat_access.hpp>
#include <boost/qvm/vec_operations.hpp>
#include <boost/qvm/mat_operations.hpp>
#include <boost/qvm/vec_mat_operations.hpp>
#include <boost/qvm/map_mat_mat.hpp>

///Compute the inverse of a 3x3 matrix using Boost qvm: ensure matrix is unitary before using
void StainVectorMath::Compute3x3MatrixInverse(const double (&inputMat)[9], double (&inversionMat)[9]) {
    //Clear the inversionMat (output) array
    for (int i = 0; i < 9; i++) { inversionMat[i] = 0.0; }
    //Define an output matrix
    boost::qvm::mat<double, 3, 3> outputMatrix;

    //Reshape to a 3x3 matrix
    double reshapedInput[3][3] = { 0.0 };
    int i, j = 0;
    for (int x = 0; x < 9; x++) {
        i = x / 3; //integer division
        j = x % 3;
        reshapedInput[i][j] = inputMat[x];
    }

    //Check the determinant of the matrix: is it 0? Thus is the matrix singular?
    double theDeterminant = boost::qvm::determinant(reshapedInput);
    //Get the inverse of the reshapedInput matrix if the determinant is not zero
    //Return matrix of all zeros if the determinant is zero
    if (abs(theDeterminant) < ODConversion::GetODMinValue()) {
        outputMatrix = boost::qvm::zero_mat<double, 3, 3>();
    }
    else {
        outputMatrix = boost::qvm::transposed(boost::qvm::inverse(reshapedInput));
    }

    //Assign output values (first index is row), no looping
    inversionMat[0] = boost::qvm::A<0, 0>(outputMatrix);
    inversionMat[1] = boost::qvm::A<0, 1>(outputMatrix);
    inversionMat[2] = boost::qvm::A<0, 2>(outputMatrix);
    inversionMat[3] = boost::qvm::A<1, 0>(outputMatrix);
    inversionMat[4] = boost::qvm::A<1, 1>(outputMatrix);
    inversionMat[5] = boost::qvm::A<1, 2>(outputMatrix);
    inversionMat[6] = boost::qvm::A<2, 0>(outputMatrix);
    inversionMat[7] = boost::qvm::A<2, 1>(outputMatrix);
    inversionMat[8] = boost::qvm::A<2, 2>(outputMatrix);
    //void return
}//end Compute3x3MatrixInverse

void StainVectorMath::Make3x3MatrixUnitary(const double (&inputMat)[9], double (&unitaryMat)[9]) {
    //Bundle the input values in rows of three
    std::vector<std::array<double, 3>> inputRows;
    inputRows.push_back(std::array<double, 3>({ inputMat[0], inputMat[1], inputMat[2] }));
    inputRows.push_back(std::array<double, 3>({ inputMat[3], inputMat[4], inputMat[5] }));
    inputRows.push_back(std::array<double, 3>({ inputMat[6], inputMat[7], inputMat[8] }));
    //Get the norm values
    std::vector<double> normVals;
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[0].begin(), inputRows[0].end()));
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[1].begin(), inputRows[1].end()));
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[2].begin(), inputRows[2].end()));
    //Create output rows
    std::vector<std::array<double, 3>> outputRows;
    for (auto it = normVals.begin(); it != normVals.end(); ++it) {
        bool smallValue = (*it < 10.0*ODConversion::GetODMinValue());
        std::array<double, 3> addRow = inputRows[it - normVals.begin()];
        if (smallValue) {
            //do not modify
        }
        else {
            for (auto p = addRow.begin(); p != addRow.end(); ++p) {
                *p = *p / *it;
            }
        }
        outputRows.push_back(addRow);
    }
    //Assign to the unitary (output) matrix
    for (int x = 0; x < 9; x++) {
        int i = x / 3;
        int j = x % 3;
        unitaryMat[x] = outputRows[i][j];
    }
}//end Make3x3MatrixUnitary

void StainVectorMath::ConvertZeroRowsToUnitary(const double (&inputMat)[9], double (&unitaryMat)[9]) {
    double replacementVals[3] = { 1.0,1.0,1.0 };
    StainVectorMath::ConvertZeroRowsToUnitary(inputMat, unitaryMat, replacementVals);
}//end ConvertZeroRowsToUnitary

void StainVectorMath::ConvertZeroRowsToUnitary(const double (&inputMat)[9], double (&unitaryMat)[9], const double (&replacementVals)[3]) {
    //Bundle the input values in rows of three
    std::vector<std::array<double, 3>> inputRows;
    inputRows.push_back(std::array<double, 3>({ inputMat[0], inputMat[1], inputMat[2] }));
    inputRows.push_back(std::array<double, 3>({ inputMat[3], inputMat[4], inputMat[5] }));
    inputRows.push_back(std::array<double, 3>({ inputMat[6], inputMat[7], inputMat[8] }));
    //Get the norm values
    std::vector<double> normVals;
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[0].begin(), inputRows[0].end()));
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[1].begin(), inputRows[1].end()));
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[2].begin(), inputRows[2].end()));
    //Compare against 10xGetODMinValue, set to unitary row if smaller
    std::array<double, 3> replacementArray = { replacementVals[0], replacementVals[1], replacementVals[2] };
    auto unitaryRow = StainVectorMath::NormalizeArray(replacementArray);
    std::vector<std::array<double, 3>> outputRows;
    for (auto it = normVals.begin(); it != normVals.end(); ++it) {
        bool smallValue = (*it < 10.0*ODConversion::GetODMinValue());
        auto addRow = smallValue ? unitaryRow : inputRows[it-normVals.begin()];
        outputRows.push_back(addRow);
    }
    //Assign to the unitary (output) matrix
    for (int x = 0; x < 9; x++) {
        int i = x / 3;
        int j = x % 3;
        unitaryMat[x] = outputRows[i][j];
    }
}//end ConvertZeroRowsToUnitary

std::array<bool, 3> StainVectorMath::RowSumZeroCheck(const double (&inputMat)[9]) {
    std::array<bool, 3> returnVals;
    //Bundle the input values in rows of three
    std::vector<std::array<double, 3>> inputRows;
    inputRows.push_back(std::array<double, 3>({ inputMat[0], inputMat[1], inputMat[2] }));
    inputRows.push_back(std::array<double, 3>({ inputMat[3], inputMat[4], inputMat[5] }));
    inputRows.push_back(std::array<double, 3>({ inputMat[6], inputMat[7], inputMat[8] }));
    //Get the sums
    std::vector<double> rowSums;
    rowSums.push_back(std::accumulate(inputRows[0].begin(), inputRows[0].end(), 0.0));
    rowSums.push_back(std::accumulate(inputRows[1].begin(), inputRows[1].end(), 0.0));
    rowSums.push_back(std::accumulate(inputRows[2].begin(), inputRows[2].end(), 0.0));
    //Get the norm values
    std::vector<double> normVals;
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[0].begin(), inputRows[0].end()));
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[1].begin(), inputRows[1].end()));
    normVals.push_back(StainVectorMath::Norm<std::array<double, 3>::iterator, double>(inputRows[2].begin(), inputRows[2].end()));

    //For each, if rowSums is zero and normVals is non-zero, returnVals is true
    for (int i = 0; i < 3; i++) {
        bool checkVal = ((abs(rowSums[i]) < ODConversion::GetODMinValue()) && (normVals[i] > 0.0));
        returnVals[i] = checkVal ? true : false;
    }
    return returnVals;
}//end RowSumZeroCheck

void StainVectorMath::Multiply3x3MatrixAndVector(const double (&inputMat)[9], const double (&inputVec)[3], double (&outputVec)[3]) {
    //Clear the output vector
    for (int i = 0; i < 3; i++) { outputVec[i] = 0.0; }
    //Reshape to a 3x3 matrix
    double reshapedMatrix[3][3] = { 0.0 };
    int i, j = 0;
    for (int x = 0; x < 9; x++) {
        i = x / 3;
        j = x % 3;
        reshapedMatrix[i][j] = inputMat[x];
    }

    //Assign matrix and input vector to boost::qvm types
    boost::qvm::mat<double, 3, 3> inputQVMMatrix = boost::qvm::mref(reshapedMatrix);
    boost::qvm::vec<double, 3> inputQVMVector = boost::qvm::zero_vec<double, 3>();
    boost::qvm::A<0>(inputQVMVector) = inputVec[0];
    boost::qvm::A<1>(inputQVMVector) = inputVec[1];
    boost::qvm::A<2>(inputQVMVector) = inputVec[2];

    //Multiplication
    boost::qvm::vec<double, 3> outputQVMVector;
    outputQVMVector = inputQVMMatrix * inputQVMVector;

    //Assign to the output vector
    outputVec[0] = boost::qvm::A<0>(outputQVMVector);
    outputVec[1] = boost::qvm::A<1>(outputQVMVector);
    outputVec[2] = boost::qvm::A<2>(outputQVMVector);
    //void return
}//end Multiply3x3MatrixAndVector

void StainVectorMath::SortStainVectors(const double(&inputMat)[9], double(&outputMat)[9],
    const int &sortOrder /*= SortOrder::ASCENDING */) {
    //Define lambdas to set how to compare two stain vectors (as 3-element arrays)
    auto ascLambda = [](const std::array<double, 3> a, const std::array<double, 3> b) {
        double prec = 1e-3;
        //Always put (0,0,0) stain vectors at the end
        double aSum = std::abs(std::accumulate(a.begin(), a.end(), 0.0));
        double bSum = std::abs(std::accumulate(b.begin(), b.end(), 0.0));
        if (aSum < prec) { return false; }
        if (bSum < prec) { return true; }
        //If first element is the same within error, sort by second element
        if (std::abs(a[0] - b[0]) > prec) { return a[0] < b[0]; }
        //If second element is the same within error, sort by third element
        else if (std::abs(a[1] - b[1]) > prec) { return a[1] < b[1]; }
        else { return a[2] < b[2]; }
    };

    auto descLambda = [](const std::array<double, 3> a, const std::array<double, 3> b) {
        double prec = 1e-3;
        //Always put (0,0,0) stain vectors at the end
        double aSum = std::abs(std::accumulate(a.begin(), a.end(), 0.0));
        double bSum = std::abs(std::accumulate(b.begin(), b.end(), 0.0));
        if (aSum < prec) { return false; }
        if (bSum < prec) { return true; }
        //If first element is the same within error, sort by second element
        if (std::abs(a[0] - b[0]) > prec) { return a[0] > b[0]; }
        //If second element is the same within error, sort by third element
        else if (std::abs(a[1] - b[1]) > prec) { return a[1] > b[1]; }
        else { return a[2] >= b[2]; }
    };

    //Bundle the input values in rows of three
    std::vector<std::array<double, 3>> inputRows;
    inputRows.push_back(std::array<double, 3>({ inputMat[0], inputMat[1], inputMat[2] }));
    inputRows.push_back(std::array<double, 3>({ inputMat[3], inputMat[4], inputMat[5] }));
    inputRows.push_back(std::array<double, 3>({ inputMat[6], inputMat[7], inputMat[8] }));
    //Create output rows
    std::vector<std::array<double, 3>> outputRows = inputRows;

    //Sort using the appropriate lambda
    if (sortOrder == SortOrder::ASCENDING) {
        std::sort(outputRows.begin(), outputRows.end(), ascLambda);
    }
    else if (sortOrder == SortOrder::DESCENDING) {
        std::sort(outputRows.begin(), outputRows.end(), descLambda);
    }
    else {
        //do not fill the output matrix
        return;
    }

    //Assign to the output matrix
    for (int x = 0; x < 9; x++) {
        int i = x / 3;
        int j = x % 3;
        outputMat[x] = outputRows[i][j];
    }
}//end SortStainVectors