/*
 * Nimrod/A Native Optimisation Library for Nimrod/OK
 * https://github.com/vs49688/nimroda
 *
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) 2019 Zane van Iperen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <cstring>
#include <cmath>
#include <istream>
#include <ostream>
#include <sstream>
#include "nimroda/cxx/Point.hpp"
#include "nimroda/cxx/Nimrod.hpp"

using namespace NimrodA;

Point::Point(Id instanceId, const double *coords, size_t numCoords, const double *objectives, size_t numObjectives) :
	numCoordinates(numCoords),
	numObjectives(numObjectives),
	instanceId(instanceId),
	//m_Nimrod(nimrod),
	m_Coordinates(coords, numCoords),
	m_Objectives(objectives, numObjectives)
{
	/* Initialise the point, except make it backed by the vector's memory. */
	m_Point.num_dims = numCoords;
	m_Point.coord = m_Coordinates.getData();
	m_Point.status = JOB_NOT_SENT;
	m_Point.num_objectives = numObjectives;
	m_Point.objectives = m_Objectives.getData();
}

Point::Point(Id instanceId, const nimroda_point_t * const pt) :
	Point(instanceId, pt->coord, pt->num_dims, pt->objectives, pt->num_objectives)
{}

const DoubleVector& Point::getCoordinates() const
{
	return m_Coordinates;
}

const DoubleVector& Point::getObjectives() const
{
	return m_Objectives;
}

void Point::clampCoordinates(const DoubleVector& lower, const DoubleVector& upper)
{
	ensureCoordinateCompatibility(lower);
	ensureCoordinateCompatibility(upper);

	bool changed = false;
	for(size_t i = 0; i < lower.getLength(); ++i)
	{
		if(m_Coordinates[i] < lower[i])
		{
			m_Coordinates[i] = lower[i];
			changed = true;
		}
		else if(m_Coordinates[i] > upper[i])
		{
			m_Coordinates[i] = upper[i];
			changed = true;
		}
	}

	if(changed)
		this->invalidateObjectives();
}


double Point::euclideanDistance(const Point& pt) const
{
	return euclideanDistance(pt.m_Coordinates);
}

Job_Status Point::getJobStatus() const { return m_Point.status; }

double Point::euclideanDistance(const DoubleVector& pt) const
{
	ensureCoordinateCompatibility(pt);

	double ret = 0.0;

	for(size_t i = 0; i < numCoordinates; ++i)
		ret += pow(m_Coordinates[i] - pt[i], 2);

	return pow(ret, 0.5);
}

size_t Point::hashCode() const
{
	return m_Coordinates.hashCode();
}

double Point::operator[](size_t index) const
{
	return m_Coordinates[index];
}

bool Point::operator==(const Point& rhs) const
{
	return m_Coordinates == rhs.m_Coordinates;
}

bool Point::operator!=(const Point& rhs) const
{
	return !(*this == rhs);
}

bool Point::operator<(const Point& rhs) const
{
	return m_Coordinates < rhs.m_Coordinates;
}

std::string Point::toString(bool objectives) const
{
	return (objectives ? m_Objectives : m_Coordinates).toString();
}

int Point::dominates(const Point& pt) const
{
	ensureCoordinateCompatibility(pt.m_Coordinates);
	ensureObjectiveCompatibility(pt.m_Objectives);

	return m_Objectives.dominates(pt.m_Objectives);
}

void Point::invalidateObjectives()
{
	for(size_t i = 0; i < numObjectives; ++i)
		m_Objectives[i] = HUGEISH;
}

Point::operator const nimroda_point_t*() const noexcept
{
	return &m_Point;
}

void Point::ensureCoordinateCompatibility(const DoubleVector& pt) const
{
	if(numCoordinates != pt.getLength())
		throw std::length_error("Mismatching coordinate count");
}

void Point::ensureObjectiveCompatibility(const DoubleVector& pt) const
{
	if(numObjectives != pt.getLength())
		throw std::length_error("Mismatching objective count");
}
