// License: GPL. For details, see LICENSE file.
package s57;

import java.util.ArrayList;

import s57.S57map.AttMap;
import s57.S57map.Edge;
import s57.S57map.Feature;
import s57.S57map.ObjTab;
import s57.S57map.Pflag;
import s57.S57map.Prim;
import s57.S57map.Rflag;
import s57.S57map.Snode;
import s57.S57obj.Obj;

/**
 * @author Malcolm Herring
 */
public final class S57box { //S57 bounding box truncation
    private S57box() {
        // Hide default constructor for utilities classes
    }
    // CHECKSTYLE.OFF: LineLength

    enum Ext { I, N, W, S, E }

    static Ext getExt(S57map map, double lat, double lon) {
        if ((lat >= map.bounds.maxlat) && (lon < map.bounds.maxlon)) {
            return Ext.N;
        } else if (lon <= map.bounds.minlon) {
            return Ext.W;
        } else if (lat <= map.bounds.minlat) {
            return Ext.S;
        } else if (lon >= map.bounds.maxlon) {
            return Ext.E;
        }
        return Ext.I;
    }

    public static void bBox(S57map map) {
        /* Truncations
         * Points: delete point features outside BB
         * Lines: Truncate edges at BB boundaries
         * Areas: Truncate edges of outers & inners and add new border edges. Merge inners to outer where necessary
         * Remove nodes outside BB
         * Remove edges that are completely outside BB
         */
        class Land {
            long first;
            Snode start;
            Ext sbound;
            long last;
            Snode end;
            Ext ebound;
            Feature land;

            Land(Feature l) {
                land = l;
                first = last = 0;
                start = end = null;
                sbound = ebound = Ext.I;
            }
        }

        if (map.features.get(Obj.COALNE) != null) {
            ArrayList<Feature> coasts = new ArrayList<>();
            ArrayList<Land> lands = new ArrayList<>();
            if (map.features.get(Obj.LNDARE) == null) {
                map.features.put(Obj.LNDARE, new ArrayList<Feature>());
            }
            for (Feature feature : map.features.get(Obj.COALNE)) {
                Feature land = new Feature();
                land.id = ++map.xref;
                land.type = Obj.LNDARE;
                land.reln = Rflag.MASTER;
                land.objs.put(Obj.LNDARE, new ObjTab());
                land.objs.get(Obj.LNDARE).put(0, new AttMap());
                if (feature.geom.prim == Pflag.AREA) {
                    land.geom = feature.geom;
                    map.features.get(Obj.LNDARE).add(land);
                } else if (feature.geom.prim == Pflag.LINE) {
                    land.geom.prim = Pflag.LINE;
                    land.geom.elems.addAll(feature.geom.elems);
                    coasts.add(land);
                }
            }
            while (coasts.size() > 0) {
                Feature land = coasts.remove(0);
                Edge fedge = map.edges.get(land.geom.elems.get(0).id);
                long first = fedge.first;
                long last = map.edges.get(land.geom.elems.get(land.geom.elems.size() - 1).id).last;
                if (coasts.size() > 0) {
                    boolean added = true;
                    while (added) {
                        added = false;
                        for (int i = 0; i < coasts.size(); i++) {
                            Feature coast = coasts.get(i);
                            Edge edge = map.edges.get(coast.geom.elems.get(0).id);
                            if (edge.first == last) {
                                land.geom.elems.add(coast.geom.elems.get(0));
                                last = edge.last;
                                coasts.remove(i--);
                                added = true;
                            } else if (edge.last == first) {
                                land.geom.elems.add(0, coast.geom.elems.get(0));
                                first = edge.first;
                                coasts.remove(i--);
                                added = true;
                            }
                        }
                    }
                }
                lands.add(new Land(land));
            }
            ArrayList<Land> islands = new ArrayList<>();
            for (Land land : lands) {
                map.sortGeom(land.land);
                if (land.land.geom.prim == Pflag.AREA) {
                    islands.add(land);
                    map.features.get(Obj.LNDARE).add(land.land);
                }
            }
            for (Land island : islands) {
                lands.remove(island);
            }
            for (Land land : lands) {
                land.first = map.edges.get(land.land.geom.elems.get(0).id).first;
                land.start = map.nodes.get(land.first);
                land.sbound = getExt(map, land.start.lat, land.start.lon);
                land.last = map.edges.get(land.land.geom.elems.get(land.land.geom.comps.get(0).size - 1).id).last;
                land.end = map.nodes.get(land.last);
                land.ebound = getExt(map, land.end.lat, land.end.lon);
            }
            islands = new ArrayList<>();
            for (Land land : lands) {
                if ((land.sbound == Ext.I) || (land.ebound == Ext.I)) {
                    islands.add(land);
                }
            }
            for (Land island : islands) {
                lands.remove(island);
            }
            for (Land land : lands) {
                Edge nedge = new Edge();
                nedge.first = land.last;
                nedge.last = land.first;
                Ext bound = land.ebound;
                while (bound != land.sbound) {
                    switch (bound) {
                    case N:
                        nedge.nodes.add(1L);
                        bound = Ext.W;
                        break;
                    case W:
                        nedge.nodes.add(2L);
                        bound = Ext.S;
                        break;
                    case S:
                        nedge.nodes.add(3L);
                        bound = Ext.E;
                        break;
                    case E:
                        nedge.nodes.add(4L);
                        bound = Ext.N;
                        break;
                    default:
                        continue;
                    }
                }
                map.edges.put(++map.xref, nedge);
                land.land.geom.elems.add(new Prim(map.xref));
                land.land.geom.comps.get(0).size++;
                land.land.geom.prim = Pflag.AREA;
                map.features.get(Obj.LNDARE).add(land.land);
            }
        }
        return;
    }
}
