/********************************************************************************
 *                                                                              *
 * This file is part of IfcOpenShell.                                           *
 *                                                                              *
 * IfcOpenShell is free software: you can redistribute it and/or modify         *
 * it under the terms of the Lesser GNU General Public License as published by  *
 * the Free Software Foundation, either version 3.0 of the License, or          *
 * (at your option) any later version.                                          *
 *                                                                              *
 * IfcOpenShell is distributed in the hope that it will be useful,              *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of               *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the                 *
 * Lesser GNU General Public License for more details.                          *
 *                                                                              *
 * You should have received a copy of the Lesser GNU General Public License     *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.         *
 *                                                                              *
 ********************************************************************************/

#include <string>
#include <iostream>
#include <fstream>

#include <TColgp_Array2OfPnt.hxx>
#include <TColgp_Array1OfPnt.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColStd_Array1OfInteger.hxx>

#include <Geom_BSplineSurface.hxx>

#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_NurbsConvert.hxx>

#include <BRepPrimAPI_MakeBox.hxx>
#include <BRepPrimAPI_MakeSphere.hxx>

#include <BRepAlgoAPI_Cut.hxx>

#include <Standard_Version.hxx>

#include "ifcparse/macros.h"

#ifndef IfcSchema
#define IfcSchema Ifc2x3
#endif

#include INCLUDE_SCHEMA(ifcparse/schemas, IfcSchema)
#include INCLUDE_SCHEMA_DEFINITIONS(ifcparse/schemas, IfcSchema)

#include "ifcparse/hierarchy_helper.h"
#include "plugin/plugin.h"

#include "../ifcgeom/serialization/serialization.h"

#if USE_VLD
#include <vld.h>
#endif

using namespace std::string_literals;

// The creation of Nurbs-surface for the IfcSite mesh, to be implemented lateron
void createGroundShape(TopoDS_Shape& shape);

int main() {

#ifdef IFCOPENSHELL_EXAMPLE_PLUGIN_PATH
	ifcopenshell::plugin::set_search_paths({IFCOPENSHELL_EXAMPLE_PLUGIN_PATH});
#endif

	// The hierarchy_helper is a subclass of the regular file that provides several
	// convenience functions for working with geometry in IFC files.
	hierarchy_helper<IfcSchema> file;
	file.header().file_name().setname("IfcAdvancedHouse.ifc");

	auto building = file.addBuilding();
	// By adding a building, a hierarchy has been automatically created that consists of the following
	// structure: IfcProject > IfcSite > IfcBuilding

	// Lateron changing the name of the IfcProject can be done by obtaining a reference to the
	// project, which has been created automatically.
	file.getSingle<IfcSchema::IfcProject>().setName("IfcAdvancedHouse"s);

	// To demonstrate the ability to serialize arbitrary opencascade solids a building envelope is
	// constructed by applying boolean operations. Naturally, in IFC, building elements should be
	// modeled separately, with rich parametric and relational semantics. Creating geometry in this
	// way does not preserve any history and is merely a demonstration of technical capabilities.
	TopoDS_Shape outer =   BRepPrimAPI_MakeBox(gp_Pnt(-5000., -180., -2000.), gp_Pnt(5000., 5180., 3000.)).Shape();
	TopoDS_Shape inner =   BRepPrimAPI_MakeBox(gp_Pnt(-4640.,  180.,     0.), gp_Pnt(4640., 4820., 3000.)).Shape();
	TopoDS_Shape window1 = BRepPrimAPI_MakeBox(gp_Pnt(-5000., -180.,   400.), gp_Pnt( 500., 1180., 2000.)).Shape();
	TopoDS_Shape window2 = BRepPrimAPI_MakeBox(gp_Pnt( 2070., -180.,   400.), gp_Pnt(3930.,  180., 2000.)).Shape();

	TopoDS_Shape building_shell = BRepAlgoAPI_Cut(
		BRepAlgoAPI_Cut(
			BRepAlgoAPI_Cut(outer, inner),
			window1
			),
		window2
	);

	// Since the solid consists only of planar faces and straight edges it can be serialized as an
	// IfcFacetedBRep. If it would not be a polyhedron, serialise() can only be successful when linked
	// to the IFC4 model and with `advanced` set to `true` which introduces IfcAdvancedFace. It would
	// return `0` otherwise.
	auto building_shape_result = ifcopenshell::geom::serialise(file, building_shell, false);
	if (!building_shape_result) {
		std::cerr << "Failed to serialize building shell." << std::endl;
		return 1;
	}
	auto building_shape = building_shape_result.as<IfcSchema::IfcProductDefinitionShape>();

	file.add_entity(building_shape);
	auto building_representations = building_shape.Representations();
	building_representations.front().setContextOfItems(file.getRepresentationContext("model"));

	building.setRepresentation(building_shape);

	// A pale white colour is assigned to the building.
	setSurfaceColour(file, building_shape, 0.75, 0.73, 0.68);

	// For the ground mesh of the IfcSite we will use a Nurbs surface created in Open Cascade. Only
	// in IFC4 the surface can be directly serialized. In IFC2X3 the it will have to be tessellated.
	TopoDS_Shape shape;
	createGroundShape(shape);

	auto ground_representation = ifcopenshell::geom::serialise(file, shape, true);
	if (!ground_representation) {
        ground_representation = ifcopenshell::geom::tesselate(file, shape, 100.);
	}
	file.getSingle<IfcSchema::IfcSite>().setRepresentation(ground_representation.as<IfcSchema::IfcProductDefinitionShape>());

	auto ground_reps = file.getSingle<IfcSchema::IfcSite>().Representation().Representations();
	for (auto& rep : ground_reps) {
		rep.setContextOfItems(file.getRepresentationContext("Model"));
	}
	file.add_entity(ground_representation);
	setSurfaceColour(file, ground_representation.as<IfcSchema::IfcProductDefinitionShape>(), 0.15, 0.25, 0.05);

    /*
	// Note that IFC lacks elementary surfaces that STEP does have, such as spherical_surface.
	// BRepBuilderAPI_NurbsConvert can be used to serialize such surfaces as nurbs surfaces.
	TopoDS_Shape sphere = BRepPrimAPI_MakeSphere(gp_Pnt(), 1000.).Shape();
	auto sphere_representation = ifcopenshell::geom::serialise(file, sphere, true);
	if (S(IfcSchema::Identifier) == "IFC4") {
		sphere = BRepBuilderAPI_NurbsConvert(sphere, true).Shape();
		sphere_representation = ifcopenshell::geom::serialise(file, sphere, true);
	}
    */

	// Finally create a file stream for our output and write the IFC file to it.
	std::ofstream f("IfcAdvancedHouse.ifc");
	f << file;
}

void createGroundShape(TopoDS_Shape& shape) {
	TColgp_Array2OfPnt cv (0, 4, 0, 4);
	cv.SetValue(0, 0, gp_Pnt(-10000, -10000, -4130));
	cv.SetValue(0, 1, gp_Pnt(-10000,  -4330, -4130));
	cv.SetValue(0, 2, gp_Pnt(-10000,      0, -5130));
	cv.SetValue(0, 3, gp_Pnt(-10000,   4330, -7130));
	cv.SetValue(0, 4, gp_Pnt(-10000,  10000, -7130));
	cv.SetValue(1, 0, gp_Pnt( -3330, -10000, -5130));
	cv.SetValue(1, 1, gp_Pnt( -7670,  -3670,  5000));
	cv.SetValue(1, 2, gp_Pnt( -9000,      0,  1000));
	cv.SetValue(1, 3, gp_Pnt( -7670,   7670,  6000));
	cv.SetValue(1, 4, gp_Pnt( -3330,  10000, -4130));
	cv.SetValue(2, 0, gp_Pnt(     0, -10000, -5530));
	cv.SetValue(2, 1, gp_Pnt(     0,  -3670,  3000));
	cv.SetValue(2, 2, gp_Pnt(     0,      0, -12000));
	cv.SetValue(2, 3, gp_Pnt(     0,   7670,  1500));
	cv.SetValue(2, 4, gp_Pnt(     0,  10000, -4130));
	cv.SetValue(3, 0, gp_Pnt(  3330, -10000, -6130));
	cv.SetValue(3, 1, gp_Pnt(  7670,  -3670,  6000));
	cv.SetValue(3, 2, gp_Pnt(  9000,      0,  5000));
	cv.SetValue(3, 3, gp_Pnt(  7670,   9000,  7000));
	cv.SetValue(3, 4, gp_Pnt(  3330,  10000, -4130));
	cv.SetValue(4, 0, gp_Pnt( 10000, -10000, -6130));
	cv.SetValue(4, 1, gp_Pnt( 10000,  -4330, -5130));
	cv.SetValue(4, 2, gp_Pnt( 10000,      0, -4130));
	cv.SetValue(4, 3, gp_Pnt( 10000,   4330, -4130));
	cv.SetValue(4, 4, gp_Pnt( 10000,  10000, -8130));
	TColStd_Array1OfReal knots(0, 1);
	knots(0) = 0;
	knots(1) = 1;
	TColStd_Array1OfInteger mult(0, 1);
	mult(0) = 5;
	mult(1) = 5;
	Handle(Geom_BSplineSurface) surf = new Geom_BSplineSurface(cv, knots, knots, mult, mult, 4, 4);
#if OCC_VERSION_HEX < 0x60502
	shape = BRepBuilderAPI_MakeFace(surf);
#else
	shape = BRepBuilderAPI_MakeFace(surf, Precision::Confusion());
#endif
}