import star.common.*;
import star.meshing.*;

import java.io.*;
import java.util.*;

import star.base.neo.*;
import star.base.query.*;
import star.base.report.*;
import star.vis.*;

/**
 * Create constrained planes along streamlines
 * the automated setup requires that the sim-file is open in serial as well as
 * that there is a flow field that follows the pipe, continuity initilization
 * with the coupled solver is enough of a flow field
 * 
 * 0. give the correct start coordinate for the streamline under "streamPoint"
 * 
 * 1. run script to create a tag called "streamLineTag". Use that to tag
 * parts/regions that you want to create the streamline from as well as
 * constrained planes from. you can also tag field functions if you want to 
 * create surface averaged reports of these FFs for every plane
 * 
 * 2a. after tagging parts/regions, run script again. that will create a field
 * function, a streamline and a table. make sure streamline looks ok and that
 * table got extracted
 * 2b. if you already have a table, comment out the line that says
 * "doTheSetup()". make sure your table matches the name given in
 * the macro and that the xyz columns are column 2-4.
 * 
 * 3. run the script again and this will create the planes and reports
 * 
 * 4. the macro will also create an extra table used to create a field function
 * with the information of the length of the streamline
 */
public class CreateNormalSections_04 extends StarMacro {

    public void execute() {
        myExecute();
    }

    // start of streamline
    double[] streamPoint = { 0, 0, 0 };

    // planes
    double maxRadius = 0.03; // in meter
    double distBetweenPlanes = 0.1; // in meter
    int pointsAroundAxis = 6;
    int initPlaneNumber = 1;
    double initPlaneDist = 0.0;
    int maxPlanes = 100;

    Simulation sim;
    String tableName = "streamlineTable";
    String myPlaneName = "streamlinePlane";
    String FFname = "streamlineFF";
    String streamlineLengthFF = "streamlineLength";
    String tagName = "streamLineTag";
    String streamlineName = "streamlineForPlanes";

    // for length of streamine
    String tableLengthName = "streamlineTable_forLength";
    String simDir;
    String sep = System.getProperty("file.separator");

    // if you want spheres for post processing. Not used by default
    double sphereRadius = 1e-2;

    // grouping
    String reportGroupStr = "reportGroup";
    String monitorGroupStr = "monitorGroup";

    // in 18.04 multivalued monitors and plots was introduced. The macro looks at
    // what version you are running and uses
    // multivalued monitors in 18.04 and later releases if this boolean is set to
    // true
    boolean multivalueMonitor = true;

    private void myExecute() {

        sim = getActiveSimulation();
        simDir = sim.getSessionDir();

        if (createTag()) {
            return;
        }

        if (doTheSetup() > 0) { // create FF, streamline, table
            return;
        }
        writeTableHeader();

        double[] Xp = extractLocation(1);
        double[] Yp = extractLocation(2);
        double[] Zp = extractLocation(3);

        double[] orgPoints = new double[3];
        double[] normVect = new double[3];

        // main loop of creating sectional plane
        double totalDist = initPlaneDist;
        int planeNumber = initPlaneNumber;
        Collection<FieldFunction> taggedFF = getTaggedFF();
        Collection<ConstrainedPlaneSection> createdConstPlaneSections = new ArrayList<>();
        if (Xp.length > 0) {
            if (getTaggedRegionOrPart() > 0) {
                for (int i = 0; i < Xp.length; i++) {
                    if (i > 0) { // can't do this for the first point

                        double dist_i = Math.sqrt(Math.pow(Xp[i] - Xp[i - 1], 2) + Math.pow(Yp[i] - Yp[i - 1], 2)
                                + Math.pow(Zp[i] - Zp[i - 1], 2));
                        totalDist = totalDist + dist_i;
                    }
                    createLengthTable(totalDist, orgPoints);
                    orgPoints[0] = Xp[i];
                    orgPoints[1] = Yp[i];
                    orgPoints[2] = Zp[i];

                    if (i > 0) {
                        normVect[0] = Xp[i] - Xp[i - 1];
                        normVect[1] = Yp[i] - Yp[i - 1];
                        normVect[2] = Zp[i] - Zp[i - 1];
                    } else {
                        normVect[0] = 1;
                        normVect[1] = 0;
                        normVect[2] = 0;
                    }

                    double normVectSize = Math
                            .sqrt(normVect[0] * normVect[0] + normVect[1] * normVect[1] + normVect[2] * normVect[2]);
                    // normalize the normVect
                    normVect[0] = normVect[0] / normVectSize;
                    normVect[1] = normVect[1] / normVectSize;
                    normVect[2] = normVect[2] / normVectSize;

                    if (totalDist - initPlaneDist
                            - (planeNumber - initPlaneNumber) * distBetweenPlanes > distBetweenPlanes) {
                        String numberString = String.format("%03d", planeNumber);
                        String totalDistStr = String.format("%.3f", totalDist);
                        String strPlaneName = myPlaneName + "_" + numberString + "_" + totalDistStr;
                        double[] loopPoints = findLoopPoints(orgPoints, normVect);
                        ConstrainedPlaneSection Cplane = createConstrainedPlane(orgPoints, normVect, loopPoints,
                                strPlaneName);
                        // createReport(taggedFF, Cplane);
                        createdConstPlaneSections.add(Cplane);
                        planeNumber = planeNumber + 1;
                    }
                    if (planeNumber - initPlaneNumber >= maxPlanes) {
                        sim.println("reached max number of planes: " + maxPlanes);
                        break;
                    }
                }
                createReport(taggedFF, createdConstPlaneSections);
                organizePlanes();
                importLengthTable();
            } else {
                sim.println("no tagged regions or parts found");
                createTag();
            }
        } else {
            sim.println("no data in the table " + tableName + ". make sure to extract it");
        }

    }

    private void organizePlanes() {

        String planeGroupStr = "planeGroup";
        // create the group
        try {
            sim.getPartManager().getGroupsManager().getObject(planeGroupStr);
        } catch (Exception e) {
            sim.getPartManager().getGroupsManager().createGroup("New Group");

            ((ClientServerObjectGroup) sim.getPartManager().getGroupsManager().getObject("New Group"))
                    .setPresentationName(planeGroupStr);
        }

        Collection<ConstrainedPlaneSection> toMove = new ArrayList<>();
        for (ConstrainedPlaneSection constPl : sim.getPartManager().getObjectsOf(ConstrainedPlaneSection.class)) {
            if (constPl.getPresentationName().contains(myPlaneName)) {
                toMove.add(constPl);
            }
        }
        ((ClientServerObjectGroup) sim.getPartManager().getGroupsManager().getObject(planeGroupStr)).getGroupsManager()
                .groupObjects(planeGroupStr, toMove);
    }

    private void createReportGroups() {
        try {
            sim.getReportManager().getGroupsManager().getObject(reportGroupStr);
        } catch (Exception e) {
            sim.getReportManager().getGroupsManager().createGroup("New Group");

            ((ClientServerObjectGroup) sim.getReportManager().getGroupsManager().getObject("New Group"))
                    .setPresentationName(reportGroupStr);
        }
        try {
            sim.getMonitorManager().getGroupsManager().getObject(monitorGroupStr);
        } catch (Exception e) {
            sim.getMonitorManager().getGroupsManager().createGroup("New Group");

            ((ClientServerObjectGroup) sim.getMonitorManager().getGroupsManager().getObject("New Group"))
                    .setPresentationName(monitorGroupStr);
        }

    }

    private ConstrainedPlaneSection createConstrainedPlane(double[] origin, double[] normal, double[] loopPoints,
            String name) {

        Units units_0 = sim.getUnitsManager().getPreferredUnits(Dimensions.Builder().length(1).build());

        ConstrainedPlaneSection constrainedPlaneSection_0 = (ConstrainedPlaneSection) sim.getPartManager()
                .createConstrainedPlaneImplicitPart(new NeoObjectVector(new Object[] {}),
                        new DoubleVector(new double[] {}), units_0);

        LabCoordinateSystem labCoordinateSystem_0 = sim.getCoordinateSystemManager().getLabCoordinateSystem();

        constrainedPlaneSection_0.setCoordinateSystem(labCoordinateSystem_0);

        UserTag userTag_0 = ((UserTag) sim.get(TagManager.class).getObject(tagName));

        constrainedPlaneSection_0.getInputParts()
                .setQuery(new Query(new TagPredicate(TagOperator.Contains, userTag_0), Query.STANDARD_MODIFIERS));

        // constrainedPlaneSection_0.getInputParts().setObjects(regions);

        constrainedPlaneSection_0.getOriginCoordinate().setCoordinateSystem(labCoordinateSystem_0);

        constrainedPlaneSection_0.getOriginCoordinate().setUnits0(units_0);

        constrainedPlaneSection_0.getOriginCoordinate().setUnits1(units_0);

        constrainedPlaneSection_0.getOriginCoordinate().setUnits2(units_0);

        constrainedPlaneSection_0.getOriginCoordinate().setDefinition("");

        constrainedPlaneSection_0.getOriginCoordinate().setValue(new DoubleVector(origin));

        constrainedPlaneSection_0.getOriginCoordinate().setCoordinate(units_0, units_0, units_0,
                new DoubleVector(origin));

        constrainedPlaneSection_0.getNormalCoordinate().setCoordinateSystem(labCoordinateSystem_0);

        constrainedPlaneSection_0.getNormalCoordinate().setUnits0(units_0);

        constrainedPlaneSection_0.getNormalCoordinate().setUnits1(units_0);

        constrainedPlaneSection_0.getNormalCoordinate().setUnits2(units_0);

        constrainedPlaneSection_0.getNormalCoordinate().setDefinition("");

        constrainedPlaneSection_0.getNormalCoordinate().setValue(new DoubleVector(normal));

        constrainedPlaneSection_0.getNormalCoordinate().setCoordinate(units_0, units_0, units_0,
                new DoubleVector(normal));

        constrainedPlaneSection_0.setLoop(new DoubleVector(loopPoints));

        SingleValue singleValue_0 = constrainedPlaneSection_0.getSingleValue();

        singleValue_0.getValueQuantity().setValue(0.0);

        singleValue_0.getValueQuantity().setUnits(units_0);

        RangeMultiValue rangeMultiValue_0 = constrainedPlaneSection_0.getRangeMultiValue();

        rangeMultiValue_0.setNValues(2);

        rangeMultiValue_0.getStartQuantity().setValue(0.0);

        rangeMultiValue_0.getStartQuantity().setUnits(units_0);

        rangeMultiValue_0.getEndQuantity().setValue(1.0);

        rangeMultiValue_0.getEndQuantity().setUnits(units_0);

        DeltaMultiValue deltaMultiValue_0 = constrainedPlaneSection_0.getDeltaMultiValue();

        deltaMultiValue_0.setNValues(2);

        deltaMultiValue_0.getStartQuantity().setValue(0.0);

        deltaMultiValue_0.getStartQuantity().setUnits(units_0);

        deltaMultiValue_0.getDeltaQuantity().setValue(1.0);

        deltaMultiValue_0.getDeltaQuantity().setUnits(units_0);

        MultiValue multiValue_0 = constrainedPlaneSection_0.getArbitraryMultiValue();

        multiValue_0.getValueQuantities().setUnits(units_0);

        multiValue_0.getValueQuantities().setArray(new DoubleVector(new double[] { 0.0 }));

        constrainedPlaneSection_0.setValueMode(ValueMode.SINGLE);

        constrainedPlaneSection_0.setPresentationName(name);

        return constrainedPlaneSection_0;
    }

    private double[] findLoopPoints(double[] orgPoint, double[] normVect) {
        double a = normVect[0];
        double b = normVect[1];
        double c = normVect[2];
        double x0 = orgPoint[0];
        double y0 = orgPoint[1];
        double z0 = orgPoint[2];

        /*
         * plane equation: a(x−x0)+b(y−y0)+c(z−z0)=0
         * ax + by + cz = ax0 + by0 + cz0 = D
         * 
         */
        Double D = a * x0 + b * y0 + c * z0;

        double s0 = Math.abs(normVect[0]);
        double s1 = Math.abs(normVect[1]);
        double s2 = Math.abs(normVect[2]);
        double x, y, z;

        if (s0 > s1 && s0 > s2) { // s0 biggest
            x = D / a;
            y = 0;
            z = 0;
        } else if (s1 > s0 && s1 > s2) { // s1 biggest
            x = 0;
            y = D / b;
            z = 0;
        } else { // s2 biggest
            x = 0;
            y = 0;
            z = D / c;
        }

        // double[] pointOnPlane = new double[] { x, y, z };
        // makeSphere(orgPoint, "centerpoint");
        // makeSphere(pointOnPlane, "point on plane");
        double distBetweenPoints = Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2) + Math.pow(z - z0, 2));

        double x2 = x0 + (x - x0) * maxRadius / distBetweenPoints;
        double y2 = y0 + (y - y0) * maxRadius / distBetweenPoints;
        double z2 = z0 + (z - z0) * maxRadius / distBetweenPoints;
        double[] pointOnPlane2 = new double[] { x2, y2, z2 };
        // makeSphere(pointOnPlane2, "point " + maxRadius + "m away");
        double[] p = pointOnPlane2;
        double[] loop = new double[3 * pointsAroundAxis];
        loop[0] = p[0];
        loop[1] = p[1];
        loop[2] = p[2];
        for (int i = 1; i < pointsAroundAxis; i++) {
            p = rotatePointAroundAxis(p, normVect, orgPoint);
            loop[i * 3 + 0] = p[0];
            loop[i * 3 + 1] = p[1];
            loop[i * 3 + 2] = p[2];
            // makeSphere(p, "point " + i);
        }
        return loop;
    }

    private double[] rotatePointAroundAxis(double[] point2rotate, double[] axis, double[] point2rotateAround) {

        double angle = 2 * Math.PI / pointsAroundAxis;
        double sin = Math.sin(angle);
        double cos = Math.cos(angle);
        double x = point2rotate[0];
        double y = point2rotate[1];
        double z = point2rotate[2];
        double x0 = point2rotateAround[0];
        double y0 = point2rotateAround[1];
        double z0 = point2rotateAround[2];
        double a = axis[0];
        double b = axis[1];
        double c = axis[2];

        double[][] R = new double[3][3];

        R[0][0] = cos + a * a * (1 - cos);
        R[0][1] = a * b * (1 - cos) - c * sin;
        R[0][2] = a * c * (1 - cos) + b * sin;

        R[1][0] = b * a * (1 - cos) + c * sin;
        R[1][1] = cos + b * b * (1 - cos);
        R[1][2] = b * c * (1 - cos) - a * sin;

        R[2][0] = c * a * (1 - cos) - b * sin;
        R[2][1] = c * b * (1 - cos) + a * sin;
        R[2][2] = cos + c * c * (1 - cos);

        // shift then rotate
        double[] s = { x - x0, y - y0, z - z0 };

        double[] sRot = multiply(R, s);

        // shift back
        double[] result = { sRot[0] + x0, sRot[1] + y0, sRot[2] + z0 };
        return result;
    }

    private double[] multiply(double[][] matrix, double[] vector) {
        int rows = matrix.length;
        int columns = matrix[0].length;

        double[] result = new double[rows];

        for (int row = 0; row < rows; row++) {
            double sum = 0;
            for (int column = 0; column < columns; column++) {
                sum += matrix[row][column]
                        * vector[column];
            }
            result[row] = sum;
        }
        return result;
    }

    private double[] extractLocation(int intColumn) {

        XyzInternalTable myXyzInternalTable = ((XyzInternalTable) sim.getTableManager().getTable(tableName));
        /*
         * assumes the table is extracted
         * myXyzInternalTable.extract();
         */
        TableData data = myXyzInternalTable.getSeriesData();
        double[] XYZp = data.getSeries(intColumn);
        return XYZp;
    }

    private void createSectionalPlane(double[] origPoints, double[] normVect, String strPlaneName) {

        PlaneSection myPlaneSection = (PlaneSection) sim.getPartManager().createImplicitPart(
                new NeoObjectVector(new Object[] {}), new DoubleVector(new double[] { 0.0, 0.0, 1.0 }),
                new DoubleVector(new double[] { 0.0, 0.0, 0.0 }), 0, 1, new DoubleVector(new double[] { 0.0 }));

        Coordinate myCoordinate1 = myPlaneSection.getOriginCoordinate();
        Units myUnits = ((Units) sim.getUnitsManager().getObject("m"));
        myCoordinate1.setCoordinate(myUnits, myUnits, myUnits, new DoubleVector(origPoints));
        Coordinate myCoordinate2 = myPlaneSection.getOrientationCoordinate();

        myCoordinate2.setCoordinate(myUnits, myUnits, myUnits, new DoubleVector(normVect));

        myPlaneSection.setPresentationName(strPlaneName);

    }

    private void makeSphere(double[] org, String name) {

        Units units_0 = sim.getUnitsManager().getPreferredUnits(Dimensions.Builder().length(1).build());

        MeshPartFactory meshPartFactory_0 = sim.get(MeshPartFactory.class);

        SimpleSpherePart simpleSpherePart_0 = meshPartFactory_0
                .createNewSpherePart(sim.get(SimulationPartManager.class));

        simpleSpherePart_0.setDoNotRetessellate(true);

        LabCoordinateSystem labCoordinateSystem_0 = sim.getCoordinateSystemManager().getLabCoordinateSystem();

        simpleSpherePart_0.setCoordinateSystem(labCoordinateSystem_0);

        simpleSpherePart_0.getOrigin().setCoordinateSystem(labCoordinateSystem_0);

        simpleSpherePart_0.getOrigin().setCoordinate(units_0, units_0, units_0, new DoubleVector(org));

        simpleSpherePart_0.getRadius().setUnits(units_0);

        simpleSpherePart_0.getRadius().setValue(sphereRadius);

        simpleSpherePart_0.getTessellationDensityOption().setSelected(TessellationDensityOption.Type.MEDIUM);

        simpleSpherePart_0.rebuildSimpleShapePart();

        simpleSpherePart_0.setDoNotRetessellate(false);

        simpleSpherePart_0.setPresentationName(name);
    }

    private int doTheSetup() {
        return createFF() + createStreamline() + createTable();
    }

    private int getTaggedRegionOrPart() {
        Collection<Region> allRegions = sim.getRegionManager().getObjects();
        Collection<Region> taggedRegions = new ArrayList<>();
        for (Region r : allRegions) {
            if (r.getTagsInput().toString().contains(tagName)) {
                taggedRegions.add(r);
            }
        }
        Collection<GeometryPart> allParts = sim.get(SimulationPartManager.class).getObjects();
        Collection<GeometryPart> taggedParts = new ArrayList<>();
        for (GeometryPart gp : allParts) {
            if (gp.getTagsInput().toString().contains(tagName)) {
                taggedParts.add(gp);
            }
        }
        return taggedRegions.size() + taggedParts.size();
    }

    private int createStreamline() {

        try {
            sim.getPartManager().getObject(streamlineName);
            return 0;
        } catch (Exception e) {

            Units units_0 = sim.getUnitsManager().getPreferredUnits(Dimensions.Builder().length(1).build());

            UserFieldFunction userFieldFunction_4 = ((UserFieldFunction) sim.getFieldFunctionManager()
                    .getFunction(FFname));

            StreamPart streamPart_2 = sim.getPartManager().createStreamPart(new ArrayList<>(Collections.<NamedObject>emptyList()),
                    new DoubleVector(streamPoint), userFieldFunction_4, 0.0, 1, 0);

            RK2 rK2_0 = streamPart_2.getSecondOrderIntegrator();

            rK2_0.setMaxPropagation(2000.0);

            rK2_0.setMaximumNumberOfSteps(20000);

            PointSeed pointSeed_0 = streamPart_2.getPointSeed();

            LabCoordinateSystem labCoordinateSystem_0 = sim.getCoordinateSystemManager().getLabCoordinateSystem();

            pointSeed_0.setCoordinateSystem(labCoordinateSystem_0);

            pointSeed_0.getCenterCoordinate().setCoordinate(units_0, units_0, units_0,
                    new DoubleVector(streamPoint));

            pointSeed_0.getCenterCoordinate().setUnits0(units_0);

            pointSeed_0.getCenterCoordinate().setUnits1(units_0);

            pointSeed_0.getCenterCoordinate().setUnits2(units_0);

            pointSeed_0.getCenterCoordinate().setDefinition("");

            pointSeed_0.getCenterCoordinate().setValue(new DoubleVector(streamPoint));

            pointSeed_0.getRadius().setUnits(units_0);

            pointSeed_0.getRadius().setValue(0.0);

            pointSeed_0.setResolution(1);

            UserTag userTag_0 = ((UserTag) sim.get(TagManager.class).getObject(tagName));

            streamPart_2.getInputParts()
                    .setQuery(new Query(new TagPredicate(TagOperator.Contains, userTag_0), Query.STANDARD_MODIFIERS));

            streamPart_2.setPresentationName(streamlineName);

            sim.println("created the streamline " + streamlineName + ". make sure the starting point is set correctly");
        }
        return 1;
    }

    private boolean createTag() {

        sim.println("tag your region/parts and field functions with the '" + tagName + "' tag");
        try {
            sim.get(TagManager.class).getObject(tagName);
            return false;
        } catch (Exception e) {
            UserTag userTag_0 = sim.get(TagManager.class).createNewUserTag("Tag");

            userTag_0.setPresentationName(tagName);
            sim.println("created the tag " + tagName);
        }
        return true;
    }

    private int createFF() {

        try {
            sim.getFieldFunctionManager().getObject("_" + FFname);
            return 0;
        } catch (Exception e) {
            UserFieldFunction userFieldFunction_4 = sim.getFieldFunctionManager().createFieldFunction();

            userFieldFunction_4.getTypeOption().setSelected(FieldFunctionTypeOption.Type.VECTOR);

            userFieldFunction_4.setPresentationName("_" + FFname);

            userFieldFunction_4.setFunctionName(FFname);

            userFieldFunction_4.setDefinition("$${Velocity}/max($${Velocity}.mag(),1e-5) + grad(${WallDistance})/1");

            sim.println("created the field function " + FFname + ". it will be used för the streamline");
        }
        return 1;
    }

    private int createTable() {

        try {
            sim.getTableManager().getObject(tableName);
            return 0;
        } catch (Exception e) {

            XyzInternalTable xyzInternalTable_1 = sim.getTableManager().createTable(XyzInternalTable.class);

            xyzInternalTable_1.setPresentationName(tableName);

            PrimitiveFieldFunction primitiveFieldFunction_0 = ((PrimitiveFieldFunction) sim.getFieldFunctionManager()
                    .getFunction("PartitionId"));

            xyzInternalTable_1.setFieldFunctions(new NeoObjectVector(new Object[] { primitiveFieldFunction_0 }));

            xyzInternalTable_1.getParts().setQuery(null);

            StreamPart streamPart_0 = ((StreamPart) sim.getPartManager().getObject(streamlineName));

            xyzInternalTable_1.getParts().setObjects(streamPart_0);

            xyzInternalTable_1.extract();
            sim.println("created table " + tableName);
        }
        return 1;
    }

    private void createReport(Collection<FieldFunction> allFF, Collection<ConstrainedPlaneSection> plane) {

        createReportGroups();

        multivalueMonitor = useMultivaluedMonitors();

        if (!multivalueMonitor) {
            for (ConstrainedPlaneSection cs : plane) {
                for (FieldFunction ff : allFF) {
                    AreaAverageReport areaAverageReport_1 = sim.getReportManager()
                            .createReport(AreaAverageReport.class);

                    areaAverageReport_1.setFieldFunction(ff);

                    areaAverageReport_1.getParts().setQuery(null);

                    areaAverageReport_1.getParts().setObjects(cs);

                    areaAverageReport_1.setPresentationName(ff.getPresentationName() + "_" + cs.getPresentationName());

                    ReportMonitor reportMonitor_0 = areaAverageReport_1.createMonitor();

                    // move part, might want to do this together as a list instead

                    ((ClientServerObjectGroup) sim.getReportManager().getGroupsManager().getObject(reportGroupStr))
                            .getGroupsManager()
                            .groupObjects(reportGroupStr, new NeoObjectVector(new Object[] { areaAverageReport_1 }));

                    ((ClientServerObjectGroup) sim.getMonitorManager().getGroupsManager().getObject(monitorGroupStr))
                            .getGroupsManager()
                            .groupObjects(monitorGroupStr, new NeoObjectVector(new Object[] { reportMonitor_0 }));
                }
            }
        } else {
            for (FieldFunction ff : allFF) {
                AreaAverageReport areaAverageReport_1 = sim.getReportManager()
                        .createReport(AreaAverageReport.class);

                areaAverageReport_1.setFieldFunction(ff);

                areaAverageReport_1.getParts().setQuery(null);

                areaAverageReport_1.getParts().setObjects(plane);

                areaAverageReport_1.setPresentationName(ff.getPresentationName() + "_" + myPlaneName);

                ReportMonitor reportMonitor_0 = areaAverageReport_1.createMonitor();

                reportMonitor_0.getValueType().setSelected(ReportMonitorValueType.Type.PartValue);

                // move part, might want to do this together as a list instead

                ((ClientServerObjectGroup) sim.getReportManager().getGroupsManager().getObject(reportGroupStr))
                        .getGroupsManager()
                        .groupObjects(reportGroupStr, new NeoObjectVector(new Object[] { areaAverageReport_1 }));

                ((ClientServerObjectGroup) sim.getMonitorManager().getGroupsManager().getObject(monitorGroupStr))
                        .getGroupsManager()
                        .groupObjects(monitorGroupStr, new NeoObjectVector(new Object[] { reportMonitor_0 }));
            }
        }
    }

    private Collection<FieldFunction> getTaggedFF() {
        Collection<FieldFunction> allFF = sim.getFieldFunctionManager().getObjects();
        Collection<FieldFunction> taggedff = new ArrayList<>();
        for (FieldFunction ff : allFF) {
            if (ff.getTagsInput().toString().contains(tagName)) {
                if (ff.getType().toString().equals("Vector")) {
                    VectorMagnitudeFieldFunction vectorMagnitudeFieldFunction_0 = ((VectorMagnitudeFieldFunction) ff
                            .getMagnitudeFunction());
                    taggedff.add(vectorMagnitudeFieldFunction_0);
                } else {
                    taggedff.add(ff);
                }
            }
        }
        return taggedff;
    }

    private void createLengthTable(double length, double[] pos) { // writes pictures and stores data in table

        // write down data to file. simfile is not cleared so the data is availible
        // there as well
        File newFile = new File(simDir + sep + tableLengthName + ".csv");
        try {
            // sim.println("1");
            if (newFile.createNewFile()) {
                Writer writer = new BufferedWriter(
                        new OutputStreamWriter(new FileOutputStream(newFile, false), "UTF-8"));
                writer.write("\"Length (m)\"" + "," + "\"X (m)\"" + "," + "\"Y (m)\"" + "," + "\"Z (m)\""
                        + System.lineSeparator());
                writer.close();
            }
            Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile, true), "UTF-8"));
            writer.write(length + ", " + pos[0] + ", " + pos[1] + ", " + pos[2] + System.lineSeparator());
            writer.close();
        } catch (Exception e) {
            // sim.println("1");
        }

    }

    private void writeTableHeader() {
        // to clear potential old table and write the header
        File newFile = new File(simDir + sep + tableLengthName + ".csv");
        try {
            // sim.println("1");
            newFile.createNewFile();
            Writer writer = new BufferedWriter(
                    new OutputStreamWriter(new FileOutputStream(newFile, false), "UTF-8"));
            writer.write("\"Length (m)\"" + "," + "\"X (m)\"" + "," + "\"Y (m)\"" + "," + "\"Z (m)\""
                    + System.lineSeparator());
            writer.close();
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    private void importLengthTable() {

        try {
            sim.getTableManager().getObject(tableLengthName + ".csv");
        } catch (Exception e) {
            FileTable fileTable_1 = (FileTable) sim.getTableManager()
                    .createFromFile(resolvePath(tableLengthName + ".csv"));
        }

        UserFieldFunction userFieldFunction_1;
        try {
            userFieldFunction_1 = (UserFieldFunction) sim.getFieldFunctionManager().getObject("_" + streamlineLengthFF);
        } catch (Exception e) {
            userFieldFunction_1 = sim.getFieldFunctionManager().createFieldFunction();

            userFieldFunction_1.getTypeOption().setSelected(FieldFunctionTypeOption.Type.SCALAR);

            userFieldFunction_1.setPresentationName("_" + streamlineLengthFF);

            userFieldFunction_1.setFunctionName(streamlineLengthFF);

            userFieldFunction_1
                    .setDefinition("interpolatePositionTable(@Table(\"" + tableLengthName + "\"), \"Length\")");

            userFieldFunction_1.setDimensions(Dimensions.Builder().length(1).build());
        }

        String plotname = "streamlinePlot_velMag";

        XYPlot xYPlot_1;
        try {
            xYPlot_1 = (XYPlot) sim.getPlotManager().getObject(plotname);
        } catch (Exception e) {

            xYPlot_1 = sim.getPlotManager().createPlot(XYPlot.class);

            xYPlot_1.setPresentationName(plotname);

            PlotUpdate plotUpdate_1 = xYPlot_1.getPlotUpdate();

            HardcopyProperties hardcopyProperties_2 = plotUpdate_1.getHardcopyProperties();

            hardcopyProperties_2.setCurrentResolutionWidth(25);

            hardcopyProperties_2.setCurrentResolutionHeight(25);

            hardcopyProperties_2.setCurrentResolutionWidth(1431);

            hardcopyProperties_2.setCurrentResolutionHeight(678);

            xYPlot_1.getParts().setQuery(null);

            StreamPart streamPart_0 = ((StreamPart) sim.getPartManager().getObject(streamlineName));

            xYPlot_1.getParts().setObjects(streamPart_0);

            AxisType axisType_1 = xYPlot_1.getXAxisType();

            axisType_1.setMode(AxisTypeMode.SCALAR);

            FieldFunctionUnits fieldFunctionUnits_2 = axisType_1.getScalarFunction();

            fieldFunctionUnits_2.setFieldFunction(userFieldFunction_1);

            YAxisType yAxisType_1 = ((YAxisType) xYPlot_1.getYAxes().getAxisType("Y Type 1"));

            FieldFunctionUnits fieldFunctionUnits_3 = yAxisType_1.getScalarFunction();

            PrimitiveFieldFunction primitiveFieldFunction_0 = ((PrimitiveFieldFunction) sim.getFieldFunctionManager()
                    .getFunction("Velocity"));

            VectorMagnitudeFieldFunction vectorMagnitudeFieldFunction_0 = ((VectorMagnitudeFieldFunction) primitiveFieldFunction_0
                    .getMagnitudeFunction());

            fieldFunctionUnits_3.setFieldFunction(vectorMagnitudeFieldFunction_0);
        }

    }

    private boolean useMultivaluedMonitors() {

        String releaseString = sim.getReleaseNumber();
        String[] releaseParts = releaseString.split("\\.");
        int majorVersion = Integer.parseInt(releaseParts[0]);
        int minorVersion = Integer.parseInt(releaseParts[1]);
        // multivalued monitors was released in 18.04
        if (majorVersion < 18 || (majorVersion == 18 && minorVersion < 4)) {
            return false;
        }
        return multivalueMonitor;
    }

}