/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK 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
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#include <iostream>
#include <string>

using namespace std;

#include <pml/PhysicalModel.h>
#include <pml/MultiComponent.h>
#include <pml/StructuralComponent.h>
#include <pml/Atom.h>
#include <math.h>
#include <limits.h>

// -------------------- main ------------------------
int main(int argc, char** argv) {

    if (argc != 3 && argc != 4) {
        cout << "Usage:" << endl;
        cout << "\t" << argv[0] << " file.pml d [vonNeumann]" << endl;
        cout << "create Moore neighborhood of distance d in a new exclusive structural component called \"Moore Neighborhood\", output to: file-Moore.pml" << endl;
        cout << "Ai is a Moore neighbor of distance d of Aj if Aix-Ajx<d and Aiy-Ajy<d and Aiz-Ajz<d" << endl;
        cout << "NOTE: to get von Neumann neighborhood, add vonNeumann as the thirs parameter (it will then use dist(ai,aj)<d as a criteria)" << endl;
        cout << "PML " << PhysicalModel::VERSION << endl;
        exit(-1);
    }

    try {
        // read the pml
        string filename(argv[1]);
        double mooreDist = atof(argv[2]);
        bool isMoore = true;

        if (argc == 4) {
            isMoore = (string(argv[3]) != "vonNeumann");
        }

        cout << "-> please wait while processing " << filename << " with d=" << mooreDist << endl;

        PhysicalModel* pm = new PhysicalModel(filename.c_str());

        // create the neigborhood
        StructuralComponent* moore;

        moore = new StructuralComponent(pm, "New Neighborhoods");

        // get all atoms
        StructuralComponent* sc = pm->getAtoms();

        Atom* a;

        double pos[3];

        Atom* n;

        double posn[3];

        Cell* nCell;

        unsigned int minN = UINT_MAX;

        unsigned int maxN = 0;

        unsigned int added = 0;

        // brute force neighborhood
        for (unsigned int i = 0; i < sc->getNumberOfStructures(); i++) {
            a = (Atom*) sc->getStructure(i);
            a->getPosition(pos);

            // create neighborhood cell
            nCell = new Cell(pm, StructureProperties::POLY_VERTEX);
            stringstream nCellName;
            nCellName << "Atom #" << a->getIndex();
            ((Structure*)nCell)->setName(nCellName.str());

            // check all other atoms!

            for (unsigned int j = 0; j < sc->getNumberOfStructures(); j++) {
                if (i != j) {
                    n = (Atom*) sc->getStructure(j);
                    n->getPosition(posn);

                    if (isMoore) {
                        if (fabs(posn[0] - pos[0]) <= mooreDist && fabs(posn[1] - pos[1]) <= mooreDist && fabs(posn[2] - pos[2]) <= mooreDist) {
                            nCell->addStructure(n);
                            added++;
                        }
                    }
                    else {
                        // von Neumann
                        if (sqrt((posn[0] - pos[0]) * (posn[0] - pos[0]) + (posn[1] - pos[1]) * (posn[1] - pos[1]) + (posn[2] - pos[2]) * (posn[2] - pos[2])) <= mooreDist) {
                            nCell->addStructure(n);
                            added++;
                        }
                    }
                }
            }

            unsigned int nNeigh = nCell->getNumberOfStructures();

            if (nNeigh > 0) {
                moore->addStructure(nCell);
            }

            if (minN > nNeigh) {
                minN = nNeigh;
            }

            if (maxN < nNeigh) {
                maxN = nNeigh;
            }
        }

        cout << "-> minN=" << minN << ", maxN=" << maxN << ", total: " << added << endl;

        // add the new SC to the exclusive component
        pm->getExclusiveComponents()->addSubComponent(moore);

        // save the result
        unsigned int pLast = filename.rfind(".");

        if (pLast != string::npos) {
            filename.erase(pLast);
            filename += "-";
        }

        if (isMoore) {
            filename += "Moore.pml";
        }
        else {
            filename += "vonNeumann.pml";
        }

        cout << "-> please wait while saving " << filename << endl;

        ofstream outputFile(filename.c_str());
        stringstream pmname(stringstream::out);
        pmname << pm->getName();
        if (isMoore) {
            pmname << " Moore";
        }
        else {
            filename += " vonNeumann";
        }
        pmname << " Neighborhoods d=" << mooreDist;
        pm->setName(pmname.str());

        // do not optimize output (do not touch cell and atom id)
        pm->xmlPrint(outputFile, false);

        cout << "WARNING: there is now two Exclusive component representing neighborhood : \"Neighborhoods\" (the old neighborhood) and \"New Neighborhoods\" (the generated moore/vonNeumann neighborhood), do not forget to remove the old one" << endl;

        delete pm;
    }
    catch (const PMLAbortException& ae) {
        cout << "AbortException: Physical model aborted:" << endl ;
        cout << ae.what() << endl;
    }
}
