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

import java.awt.Color;
import java.awt.Font;
import java.awt.geom.AffineTransform;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.EnumMap;
import java.util.HashMap;

import render.ChartContext.RuleSet;
import render.Renderer.LabelStyle;
import s57.S57att.Att;
import s57.S57map.AttMap;
import s57.S57map.Feature;
import s57.S57map.ObjTab;
import s57.S57map.Pflag;
import s57.S57map.Rflag;
import s57.S57obj.Obj;
import s57.S57val;
import s57.S57val.AddMRK;
import s57.S57val.AttVal;
import s57.S57val.BcnSHP;
import s57.S57val.BnkWTW;
import s57.S57val.BoySHP;
import s57.S57val.CatACH;
import s57.S57val.CatCBL;
import s57.S57val.CatCRN;
import s57.S57val.CatDIS;
import s57.S57val.CatHAF;
import s57.S57val.CatLAM;
import s57.S57val.CatLMK;
import s57.S57val.CatMOR;
import s57.S57val.CatNMK;
import s57.S57val.CatOBS;
import s57.S57val.CatOFP;
import s57.S57val.CatPIL;
import s57.S57val.CatPRA;
import s57.S57val.CatREA;
import s57.S57val.CatROD;
import s57.S57val.CatSCF;
import s57.S57val.CatSEA;
import s57.S57val.CatSIL;
import s57.S57val.CatSIT;
import s57.S57val.CatSIW;
import s57.S57val.CatSLC;
import s57.S57val.CatWED;
import s57.S57val.CatWRK;
import s57.S57val.ColCOL;
import s57.S57val.ColPAT;
import s57.S57val.FncFNC;
import s57.S57val.MarSYS;
import s57.S57val.StsSTS;
import s57.S57val.TopSHP;
import s57.S57val.TrfTRF;
import s57.S57val.UniHLU;
import s57.S57val.WatLEV;
import symbols.Areas;
import symbols.Beacons;
import symbols.Buoys;
import symbols.Facilities;
import symbols.Harbours;
import symbols.Landmarks;
import symbols.Notices;
import symbols.Symbols;
import symbols.Symbols.Delta;
import symbols.Symbols.Handle;
import symbols.Symbols.LineStyle;
import symbols.Symbols.Patt;
import symbols.Symbols.Scheme;
import symbols.Symbols.Symbol;
import symbols.Topmarks;

/**
 * @author Malcolm Herring
 */
public class Rules {

    static final DecimalFormat df = new DecimalFormat("#.#");

    static final EnumMap<ColCOL, Color> bodyColours = new EnumMap<>(ColCOL.class);
    static {
        bodyColours.put(ColCOL.COL_UNK, new Color(0, true));
        bodyColours.put(ColCOL.COL_WHT, new Color(0xffffff));
        bodyColours.put(ColCOL.COL_BLK, new Color(0x000000));
        bodyColours.put(ColCOL.COL_RED, new Color(0xd40000));
        bodyColours.put(ColCOL.COL_GRN, new Color(0x00d400));
        bodyColours.put(ColCOL.COL_BLU, Color.blue);
        bodyColours.put(ColCOL.COL_YEL, new Color(0xffd400));
        bodyColours.put(ColCOL.COL_GRY, Color.gray);
        bodyColours.put(ColCOL.COL_BRN, new Color(0x8b4513));
        bodyColours.put(ColCOL.COL_AMB, new Color(0xfbf00f));
        bodyColours.put(ColCOL.COL_VIO, new Color(0xee82ee));
        bodyColours.put(ColCOL.COL_ORG, Color.orange);
        bodyColours.put(ColCOL.COL_MAG, new Color(0xf000f0));
        bodyColours.put(ColCOL.COL_PNK, Color.pink);
    }

    static final EnumMap<ColPAT, Patt> pattMap = new EnumMap<>(ColPAT.class);
    static {
        pattMap.put(ColPAT.PAT_UNKN, Patt.Z);
        pattMap.put(ColPAT.PAT_HORI, Patt.H);
        pattMap.put(ColPAT.PAT_VERT, Patt.V);
        pattMap.put(ColPAT.PAT_DIAG, Patt.D);
        pattMap.put(ColPAT.PAT_BRDR, Patt.B);
        pattMap.put(ColPAT.PAT_SQUR, Patt.S);
        pattMap.put(ColPAT.PAT_CROS, Patt.C);
        pattMap.put(ColPAT.PAT_SALT, Patt.X);
        pattMap.put(ColPAT.PAT_STRP, Patt.H);
    }

    static String getName() {
        AttVal<?> name = feature.atts.get(Att.OBJNAM);
        if (name == null) {
            AttMap atts = feature.objs.get(feature.type).get(0);
            if (atts != null) {
                name = atts.get(Att.OBJNAM);
            }
        }
        return (name != null) ? (String) name.val : null;
    }

    public static void addName(int z, Font font) {
        addName(z, font, Color.black, new Delta(Handle.CC, new AffineTransform()));
    }

    public static void addName(int z, Font font, Color colour) {
        addName(z, font, colour, new Delta(Handle.CC, new AffineTransform()));
    }

    public static void addName(int z, Font font, Delta delta) {
        addName(z, font, Color.black, delta);
    }

    public static void addName(int z, Font font, Color colour, Delta delta) {
        if (Renderer.zoom >= z) {
            String name = getName();
            if (name != null) {
                Renderer.labelText(name, font, colour, delta);
            }
        }
    }

    static AttMap getAtts(Obj obj, int idx) {
        HashMap<Integer, AttMap> objs = feature.objs.get(obj);
        if (objs == null)
            return null;
        else
            return objs.get(idx);
    }

    public static Object getAttVal(Obj obj, Att att) {
        AttMap atts;
        HashMap<Integer, AttMap> objs;
        AttVal<?> item;
        if ((objs = feature.objs.get(obj)) != null)
            atts = objs.get(0);
        else
            return null;
        if ((item = atts.get(att)) == null)
            return null;
        else
            return item.val;
    }

    public static String getAttStr(Obj obj, Att att) {
        String str = (String) getAttVal(obj, att);
        if (str != null) {
            return str;
        }
        return "";
    }

    @SuppressWarnings("unchecked")
    public static Enum<?> getAttEnum(Obj obj, Att att) {
        ArrayList<?> list = (ArrayList<?>) getAttVal(obj, att);
        if (list != null) {
            return ((ArrayList<Enum<?>>) list).get(0);
        }
        return S57val.unknAtt(att);
    }

    @SuppressWarnings("unchecked")
    public static ArrayList<?> getAttList(Obj obj, Att att) {
        ArrayList<Enum<?>> list = (ArrayList<Enum<?>>) getAttVal(obj, att);
        if (list != null) {
            return list;
        }
        list = new ArrayList<>();
        list.add(S57val.unknAtt(att));
        return list;
    }

    @SuppressWarnings("unchecked")
    static Scheme getScheme(Obj obj) {
        ArrayList<Color> colours = new ArrayList<>();
        for (ColCOL col : (ArrayList<ColCOL>) getAttList(obj, Att.COLOUR)) {
            colours.add(bodyColours.get(col));
        }
        ArrayList<Patt> patterns = new ArrayList<>();
        for (ColPAT pat : (ArrayList<ColPAT>) getAttList(obj, Att.COLPAT)) {
            patterns.add(pattMap.get(pat));
        }
        return new Scheme(patterns, colours);
    }

    static boolean hasAttribute(Obj obj, Att att) {
        AttMap atts;
        if ((atts = getAtts(obj, 0)) != null) {
            AttVal<?> item = atts.get(att);
            return item != null;
        }
        return false;
    }

    static boolean testAttribute(Obj obj, Att att, Object val) {
        AttMap atts;
        if ((atts = getAtts(obj, 0)) != null) {
            AttVal<?> item = atts.get(att);
            if (item != null) {
                switch (item.conv) {
                case S:
                case A:
                    return ((String) item.val).equals(val);
                case E:
                case L:
                    return ((ArrayList<?>) item.val).contains(val);
                case F:
                case I:
                    return item.val == val;
                }
            }
        }
        return false;
    }

    static boolean hasObject(Obj obj) {
        return (feature.objs.containsKey(obj));
    }

    public static Feature feature;
    static ArrayList<Feature> objects;

    static boolean testObject(Obj obj) {
        return ((objects = Renderer.map.features.get(obj)) != null);
    }

    static boolean testFeature(Feature f) {
        return ((feature = f).reln == Rflag.MASTER);
    }

    public static boolean rules() {
    	try {
        if ((Renderer.context.ruleset() == RuleSet.ALL) || (Renderer.context.ruleset() == RuleSet.BASE)) {
            if (testObject(Obj.LNDARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.BUAARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.HRBFAC)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.HRBBSN)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.LOKBSN)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.LKBSPT)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.LAKARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.RIVERS)) for (Feature f : objects) if (testFeature(f)) waterways();
            if (testObject(Obj.CANALS)) for (Feature f : objects) if (testFeature(f)) waterways();
            if (testObject(Obj.DEPARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.COALNE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.ROADWY)) for (Feature f : objects) if (testFeature(f)) highways();
            if (testObject(Obj.RAILWY)) for (Feature f : objects) if (testFeature(f)) highways();
        }
        if (Renderer.context.ruleset() == RuleSet.ALL) {
            if (testObject(Obj.SOUNDG)) for (Feature f : objects) if (testFeature(f)) depths();
            if (testObject(Obj.DEPCNT)) for (Feature f : objects) if (testFeature(f)) depths();
        }
        if (testObject(Obj.SLCONS)) for (Feature f : objects) if (testFeature(f)) shoreline();
        if ((Renderer.context.ruleset() == RuleSet.ALL) || (Renderer.context.ruleset() == RuleSet.SEAMARK)) {
            if (testObject(Obj.PIPSOL)) for (Feature f : objects) if (testFeature(f)) pipelines();
            if (testObject(Obj.CBLSUB)) for (Feature f : objects) if (testFeature(f)) cables();
            if (testObject(Obj.PIPOHD)) for (Feature f : objects) if (testFeature(f)) pipelines();
            if (testObject(Obj.CBLOHD)) for (Feature f : objects) if (testFeature(f)) cables();
            if (testObject(Obj.TSEZNE)) for (Feature f : objects) if (testFeature(f)) separation();
            if (testObject(Obj.TSSCRS)) for (Feature f : objects) if (testFeature(f)) separation();
            if (testObject(Obj.TSSRON)) for (Feature f : objects) if (testFeature(f)) separation();
            if (testObject(Obj.TSELNE)) for (Feature f : objects) if (testFeature(f)) separation();
            if (testObject(Obj.TSSLPT)) for (Feature f : objects) if (testFeature(f)) separation();
            if (testObject(Obj.TSSBND)) for (Feature f : objects) if (testFeature(f)) separation();
            if (testObject(Obj.ISTZNE)) for (Feature f : objects) if (testFeature(f)) separation();
            if (testObject(Obj.SNDWAV)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.WEDKLP)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.OSPARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.FAIRWY)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.DRGARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.RESARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.PRCARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.SPLARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.SEAARE)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.OBSTRN)) for (Feature f : objects) if (testFeature(f)) obstructions();
            if (testObject(Obj.UWTROC)) for (Feature f : objects) if (testFeature(f)) obstructions();
            if (testObject(Obj.MARCUL)) for (Feature f : objects) if (testFeature(f)) areas();
            if (testObject(Obj.RECTRC)) for (Feature f : objects) if (testFeature(f)) transits();
            if (testObject(Obj.NAVLNE)) for (Feature f : objects) if (testFeature(f)) transits();
            if (testObject(Obj.HRBFAC)) for (Feature f : objects) if (testFeature(f)) harbours();
            if (testObject(Obj.ACHARE)) for (Feature f : objects) if (testFeature(f)) harbours();
            if (testObject(Obj.ACHBRT)) for (Feature f : objects) if (testFeature(f)) harbours();
            if (testObject(Obj.BERTHS)) for (Feature f : objects) if (testFeature(f)) harbours();
            if (testObject(Obj.DISMAR)) for (Feature f : objects) if (testFeature(f)) distances();
            if (testObject(Obj.HULKES)) for (Feature f : objects) if (testFeature(f)) ports();
            if (testObject(Obj.CRANES)) for (Feature f : objects) if (testFeature(f)) ports();
            if (testObject(Obj.LNDMRK)) for (Feature f : objects) if (testFeature(f)) landmarks();
            if (testObject(Obj.SILTNK)) for (Feature f : objects) if (testFeature(f)) landmarks();
            if (testObject(Obj.BUISGL)) for (Feature f : objects) if (testFeature(f)) harbours();
            if (testObject(Obj.MORFAC)) for (Feature f : objects) if (testFeature(f)) moorings();
            if (testObject(Obj.NOTMRK)) for (Feature f : objects) if (testFeature(f)) notices();
            if (testObject(Obj.SMCFAC)) for (Feature f : objects) if (testFeature(f)) marinas();
            if (testObject(Obj.BRIDGE)) for (Feature f : objects) if (testFeature(f)) bridges();
            if (testObject(Obj.PILPNT)) for (Feature f : objects) if (testFeature(f)) points();
            if (testObject(Obj.TOPMAR)) for (Feature f : objects) if (testFeature(f)) points();
            if (testObject(Obj.DAYMAR)) for (Feature f : objects) if (testFeature(f)) points();
            if (testObject(Obj.FOGSIG)) for (Feature f : objects) if (testFeature(f)) points();
            if (testObject(Obj.RDOCAL)) for (Feature f : objects) if (testFeature(f)) callpoint();
            if (testObject(Obj.LITMIN)) for (Feature f : objects) if (testFeature(f)) lights();
            if (testObject(Obj.LITMAJ)) for (Feature f : objects) if (testFeature(f)) lights();
            if (testObject(Obj.LIGHTS)) for (Feature f : objects) if (testFeature(f)) lights();
            if (testObject(Obj.SISTAT)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.SISTAW)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.CGUSTA)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.RDOSTA)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.RADRFL)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.RADSTA)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.RTPBCN)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.RSCSTA)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.PILBOP)) for (Feature f : objects) if (testFeature(f)) stations();
            if (testObject(Obj.WTWGAG)) for (Feature f : objects) if (testFeature(f)) gauges();
            if (testObject(Obj.OFSPLF)) for (Feature f : objects) if (testFeature(f)) platforms();
            if (testObject(Obj.WRECKS)) for (Feature f : objects) if (testFeature(f)) wrecks();
            if (testObject(Obj.LITVES)) for (Feature f : objects) if (testFeature(f)) floats();
            if (testObject(Obj.LITFLT)) for (Feature f : objects) if (testFeature(f)) floats();
            if (testObject(Obj.BOYINB)) for (Feature f : objects) if (testFeature(f)) floats();
            if (testObject(Obj.BOYLAT)) for (Feature f : objects) if (testFeature(f)) buoys();
            if (testObject(Obj.BOYCAR)) for (Feature f : objects) if (testFeature(f)) buoys();
            if (testObject(Obj.BOYISD)) for (Feature f : objects) if (testFeature(f)) buoys();
            if (testObject(Obj.BOYSAW)) for (Feature f : objects) if (testFeature(f)) buoys();
            if (testObject(Obj.BOYSPP)) for (Feature f : objects) if (testFeature(f)) buoys();
            if (testObject(Obj.BCNLAT)) for (Feature f : objects) if (testFeature(f)) beacons();
            if (testObject(Obj.BCNCAR)) for (Feature f : objects) if (testFeature(f)) beacons();
            if (testObject(Obj.BCNISD)) for (Feature f : objects) if (testFeature(f)) beacons();
            if (testObject(Obj.BCNSAW)) for (Feature f : objects) if (testFeature(f)) beacons();
            if (testObject(Obj.BCNSPP)) for (Feature f : objects) if (testFeature(f)) beacons();
        }
    	} catch (ConcurrentModificationException e) {
    		return false;
    	} catch (Exception e) {
    		return true;
    	}
    	return true;
    }

    private static void areas() {
        String name = getName();
        switch (feature.type) {
        case BUAARE:
            Renderer.lineVector(new LineStyle(new Color(0x20000000, true)));
            break;
        case COALNE:
            if (Renderer.zoom >= 12)
                Renderer.lineVector(new LineStyle(Color.black, 10));
            break;
        case DEPARE:
            Double depmax = 0.0;
            if (((depmax = (Double) getAttVal(Obj.DEPARE, Att.DRVAL2)) != null) && (depmax <= 0.0)) {
                Renderer.lineVector(new LineStyle(Symbols.Gdries));
            }
            break;
        case LAKARE:
            if ((Renderer.zoom >= 12) || (feature.geom.area > 10.0))
                Renderer.lineVector(new LineStyle(Symbols.Bwater));
            break;
        case DRGARE:
            if (Renderer.zoom < 16)
                Renderer.lineVector(new LineStyle(Color.black, 8, new float[] {25, 25 }, new Color(0x40ffffff, true)));
            else
                Renderer.lineVector(new LineStyle(Color.black, 8, new float[] {25, 25 }));
            addName(12, new Font("Arial", Font.PLAIN, 100), new Delta(Handle.CC, new AffineTransform()));
            break;
        case FAIRWY:
            if (feature.geom.area > 2.0) {
                if (Renderer.zoom < 16)
                    Renderer.lineVector(new LineStyle(Symbols.Mline, 8, new float[] {50, 50 }, new Color(0x40ffffff, true)));
                else
                    Renderer.lineVector(new LineStyle(Symbols.Mline, 8, new float[] {50, 50 }));
            } else {
                if (Renderer.zoom >= 14)
                    Renderer.lineVector(new LineStyle(new Color(0x40ffffff, true)));
            }
            break;
        case LKBSPT:
        case LOKBSN:
        case HRBBSN:
            if (Renderer.zoom >= 12) {
                Renderer.lineVector(new LineStyle(Color.black, 10, Symbols.Bwater));
            } else {
                Renderer.lineVector(new LineStyle(Symbols.Bwater));
            }
            break;
        case HRBFAC:
            if (feature.objs.get(Obj.HRBBSN) != null) {
                if (Renderer.zoom >= 12) {
                    Renderer.lineVector(new LineStyle(Color.black, 10, Symbols.Bwater));
                } else {
                    Renderer.lineVector(new LineStyle(Symbols.Bwater));
                }
            }
            break;
        case LNDARE:
            Renderer.lineVector(new LineStyle(Symbols.Yland));
            break;
        case MARCUL:
            if (Renderer.zoom >= 12) {
                if (Renderer.zoom >= 14) {
                    Renderer.symbol(Areas.MarineFarm);
                }
                if ((feature.geom.area > 0.2) || ((feature.geom.area > 0.05) && (Renderer.zoom >= 14)) ||
                   ((feature.geom.area > 0.005) && (Renderer.zoom >= 16))) {
                    Renderer.lineVector(new LineStyle(Color.black, 4, new float[] {10, 10}));
                }
            }
            break;
        case OSPARE:
            if (testAttribute(feature.type, Att.CATPRA, CatPRA.PRA_WFRM)) {
                Renderer.symbol(Areas.WindFarm);
                Renderer.lineVector(new LineStyle(Color.black, 20, new float[] {40, 40}));
                addName(15, new Font("Arial", Font.BOLD, 80), new Delta(Handle.TC, AffineTransform.getTranslateInstance(0, 10)));
            }
            break;
        case RESARE:
        case MIPARE:
            if (Renderer.zoom >= 12) {
                Renderer.lineSymbols(Areas.Restricted, 1.0, null, null, 0, Symbols.Mline);
                if (testAttribute(feature.type, Att.CATREA, CatREA.REA_NWAK)) {
                    Renderer.symbol(Areas.NoWake);
                }
            }
            break;
        case PRCARE:
            if (Renderer.zoom >= 12) {
                Renderer.lineVector(new LineStyle(Symbols.Mline, 10, new float[] {40, 40}));
            }
            break;
        case SEAARE:
            switch ((CatSEA) getAttEnum(feature.type, Att.CATSEA)) {
            case SEA_RECH:
                if ((Renderer.zoom >= 10) && (name != null))
                    if (feature.geom.prim == Pflag.LINE) {
                        Renderer.lineText(name, new Font("Arial", Font.PLAIN, 150), Color.black, -40);
                    } else {
                        Renderer.labelText(name, new Font("Arial", Font.PLAIN, 150), Color.black,
                                new Delta(Handle.BC, AffineTransform.getTranslateInstance(0, -40)));
                    }
                break;
            case SEA_BAY:
                if ((Renderer.zoom >= 12) && (name != null))
                    if (feature.geom.prim == Pflag.LINE) {
                        Renderer.lineText(name, new Font("Arial", Font.PLAIN, 150), Color.black, -40);
                    } else {
                        Renderer.labelText(name, new Font("Arial", Font.PLAIN, 150), Color.black,
                                new Delta(Handle.BC, AffineTransform.getTranslateInstance(0, -40)));
                    }
                break;
            case SEA_SHOL:
                if (Renderer.zoom >= 14) {
                    if (feature.geom.prim == Pflag.AREA) {
                        Renderer.lineVector(new LineStyle(new Color(0xc480ff), 4, new float[] {25, 25}));
                        if (name != null) {
                            Renderer.labelText(name, new Font("Arial", Font.ITALIC, 75), Color.black,
                                    new Delta(Handle.BC, AffineTransform.getTranslateInstance(0, -40)));
                            Renderer.labelText("(Shoal)", new Font("Arial", Font.PLAIN, 60), Color.black, new Delta(Handle.BC));
                        }
                    } else if (feature.geom.prim == Pflag.LINE) {
                        if (name != null) {
                            Renderer.lineText(name, new Font("Arial", Font.ITALIC, 75), Color.black, -40);
                            Renderer.lineText("(Shoal)", new Font("Arial", Font.PLAIN, 60), Color.black, 0);
                        }
                    } else {
                        if (name != null) {
                            Renderer.labelText(name, new Font("Arial", Font.ITALIC, 75), Color.black,
                                    new Delta(Handle.BC, AffineTransform.getTranslateInstance(0, -40)));
                            Renderer.labelText("(Shoal)", new Font("Arial", Font.PLAIN, 60), Color.black, new Delta(Handle.BC));
                        }
                    }
                }
                break;
            case SEA_GAT:
            case SEA_NRRW:
                addName(12, new Font("Arial", Font.PLAIN, 100));
                break;
            default:
                break;
            }
            break;
        case SNDWAV:
            if (Renderer.zoom >= 12) Renderer.fillPattern(Areas.Sandwaves);
            break;
        case WEDKLP:
            if (Renderer.zoom >= 12) {
                switch ((CatWED) getAttEnum(feature.type, Att.CATWED)) {
                case WED_KELP:
                    if (feature.geom.prim == Pflag.AREA) {
                        Renderer.fillPattern(Areas.KelpA);
                    } else {
                        Renderer.symbol(Areas.KelpS);
                    }
                    break;
                default:
                    break;
                }
            }
            break;
        case SPLARE:
            if (Renderer.zoom >= 12) {
                Renderer.symbol(Areas.Plane, new Scheme(Symbols.Msymb));
                Renderer.lineSymbols(Areas.Restricted, 0.5, Areas.LinePlane, null, 10, Symbols.Mline);
            }
            addName(15, new Font("Arial", Font.BOLD, 80), new Delta(Handle.BC, AffineTransform.getTranslateInstance(0, -90)));
            break;
        default:
            break;
        }
    }

    @SuppressWarnings("unchecked")
    private static void beacons() {
        if ((Renderer.zoom >= 14) || ((Renderer.zoom >= 12) && ((feature.type == Obj.BCNLAT) || (feature.type == Obj.BCNCAR)))
                || ((Renderer.zoom >= 11) && ((feature.type == Obj.BCNSAW) || hasObject(Obj.RTPBCN)))) {
            BcnSHP shape = (BcnSHP) getAttEnum(feature.type, Att.BCNSHP);
            if (shape == BcnSHP.BCN_UNKN)
                shape = BcnSHP.BCN_PILE;
            if ((shape == BcnSHP.BCN_WTHY) && (feature.type == Obj.BCNLAT)) {
                switch ((CatLAM) getAttEnum(feature.type, Att.CATLAM)) {
                case LAM_PORT:
                    Renderer.symbol(Beacons.WithyPort);
                    break;
                case LAM_STBD:
                    Renderer.symbol(Beacons.WithyStarboard);
                    break;
                default:
                    Renderer.symbol(Beacons.Stake, getScheme(feature.type));
                }
            } else if ((shape == BcnSHP.BCN_PRCH) && (feature.type == Obj.BCNLAT) && !(feature.objs.containsKey(Obj.TOPMAR))) {
                switch ((CatLAM) getAttEnum(feature.type, Att.CATLAM)) {
                case LAM_PORT:
                    Renderer.symbol(Beacons.PerchPort);
                    break;
                case LAM_STBD:
                    Renderer.symbol(Beacons.PerchStarboard);
                    break;
                default:
                    Renderer.symbol(Beacons.Stake, getScheme(feature.type));
                }
            } else {
                Renderer.symbol(Beacons.Shapes.get(shape), getScheme(feature.type));
                if (feature.objs.containsKey(Obj.TOPMAR)) {
                    AttMap topmap = feature.objs.get(Obj.TOPMAR).get(0);
                    if (topmap.containsKey(Att.TOPSHP)) {
                        Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)),
                                getScheme(Obj.TOPMAR), Topmarks.BeaconDelta);
                    }
                } else if (feature.objs.containsKey(Obj.DAYMAR)) {
                    AttMap topmap = feature.objs.get(Obj.DAYMAR).get(0);
                    if (topmap.containsKey(Att.TOPSHP)) {
                        Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)),
                                getScheme(Obj.DAYMAR), Topmarks.BeaconDelta);
                    }
                }
            }
            if (hasObject(Obj.NOTMRK))
                notices();
            addName(15, new Font("Arial", Font.BOLD, 40), new Delta(Handle.BL, AffineTransform.getTranslateInstance(60, -50)));
            Signals.addSignals();
        }
    }

    @SuppressWarnings("unchecked")
    private static void buoys() {
        if ((Renderer.zoom >= 14) || ((Renderer.zoom >= 12) && ((feature.type == Obj.BOYLAT) || (feature.type == Obj.BOYCAR)))
                || ((Renderer.zoom >= 11) && ((feature.type == Obj.BOYSAW) || hasObject(Obj.RTPBCN)))) {
            BoySHP shape = (BoySHP) getAttEnum(feature.type, Att.BOYSHP);
            if (shape == BoySHP.BOY_UNKN) shape = BoySHP.BOY_PILR;
            Renderer.symbol(Buoys.Shapes.get(shape), getScheme(feature.type));
            if (feature.objs.containsKey(Obj.TOPMAR)) {
                AttMap topmap = feature.objs.get(Obj.TOPMAR).get(0);
                if (topmap.containsKey(Att.TOPSHP)) {
                    Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)),
                            getScheme(Obj.TOPMAR), Topmarks.BuoyDeltas.get(shape));
                }
            } else if (feature.objs.containsKey(Obj.DAYMAR)) {
                AttMap topmap = feature.objs.get(Obj.DAYMAR).get(0);
                if (topmap.containsKey(Att.TOPSHP)) {
                    Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)),
                            getScheme(Obj.DAYMAR), Topmarks.BuoyDeltas.get(shape));
                }
            }
            addName(15, new Font("Arial", Font.BOLD, 40), new Delta(Handle.BL, AffineTransform.getTranslateInstance(60, -50)));
            Signals.addSignals();
        }
    }

    private static void bridges() {
        if (Renderer.zoom >= 16) {
            double verclr, verccl, vercop, horclr;
            AttMap atts = feature.objs.get(Obj.BRIDGE).get(0);
            String vstr = "";
            String hstr = "";
            if (atts != null) {
                if (atts.containsKey(Att.HORCLR)) {
                    horclr = (Double) atts.get(Att.HORCLR).val;
                    hstr = String.valueOf(horclr);
                }
                if (atts.containsKey(Att.VERCLR)) {
                    verclr = (Double) atts.get(Att.VERCLR).val;
                } else {
                    verclr = atts.containsKey(Att.VERCSA) ? (Double) atts.get(Att.VERCSA).val : 0;
                }
                verccl = atts.containsKey(Att.VERCCL) ? (Double) atts.get(Att.VERCCL).val : 0;
                vercop = atts.containsKey(Att.VERCOP) ? (Double) atts.get(Att.VERCOP).val : 0;
                if (verclr > 0) {
                    vstr += String.valueOf(verclr);
                } else if (verccl > 0) {
                    if (vercop == 0) {
                        vstr += String.valueOf(verccl) + "/-";
                    } else {
                        vstr += String.valueOf(verccl) + "/" + String.valueOf(vercop);
                    }
                }
                if (hstr.isEmpty() && !vstr.isEmpty()) {
                    Renderer.labelText(vstr, new Font("Arial", Font.PLAIN, 30), Color.black, LabelStyle.VCLR, Color.black, Color.white,
                            new Delta(Handle.CC));
                } else if (!hstr.isEmpty() && !vstr.isEmpty()) {
                    Renderer.labelText(vstr, new Font("Arial", Font.PLAIN, 30), Color.black, LabelStyle.VCLR, Color.black, Color.white,
                            new Delta(Handle.BC));
                    Renderer.labelText(hstr, new Font("Arial", Font.PLAIN, 30), Color.black, LabelStyle.HCLR, Color.black, Color.white,
                            new Delta(Handle.TC));
                } else if (!hstr.isEmpty() && vstr.isEmpty()) {
                    Renderer.labelText(hstr, new Font("Arial", Font.PLAIN, 30), Color.black, LabelStyle.HCLR, Color.black, Color.white,
                            new Delta(Handle.CC));
                }
            }
        }
    }

    private static void cables() {
        if ((Renderer.zoom >= 16) && (feature.geom.length < 2)) {
            if (feature.type == Obj.CBLSUB) {
                Renderer.lineSymbols(Areas.Cable, 0.0, null, null, 0, Symbols.Mline);
            } else if (feature.type == Obj.CBLOHD) {
                AttMap atts = feature.objs.get(Obj.CBLOHD).get(0);
                if ((atts != null) && (atts.containsKey(Att.CATCBL)) && (atts.get(Att.CATCBL).val == CatCBL.CBL_POWR)) {
                    Renderer.lineSymbols(Areas.CableDash, 0, Areas.CableDot, Areas.CableFlash, 2, Color.black);
                } else {
                    Renderer.lineSymbols(Areas.CableDash, 0, Areas.CableDot, null, 2, Color.black);
                }
                if (atts != null) {
                    if (atts.containsKey(Att.VERCLR)) {
                        Renderer.labelText(String.valueOf(atts.get(Att.VERCLR).val), new Font("Arial", Font.PLAIN, 50),
                                Color.black, LabelStyle.VCLR, Color.black, new Delta(Handle.TC, AffineTransform.getTranslateInstance(0, 25)));
                    } else if (atts.containsKey(Att.VERCSA)) {
                        Renderer.labelText(String.valueOf(atts.get(Att.VERCSA).val), new Font("Arial", Font.PLAIN, 50),
                                Color.black, LabelStyle.PCLR, Color.black, new Delta(Handle.TC, AffineTransform.getTranslateInstance(0, 25)));
                    }
                }
            }
        }
    }

    private static void callpoint() {
        if (Renderer.zoom >= 14) {
            Symbol symb = Harbours.CallPoint2;
            TrfTRF trf = (TrfTRF) getAttEnum(feature.type, Att.TRAFIC);
            if (trf != TrfTRF.TRF_TWOW) {
                symb = Harbours.CallPoint1;
            }
            Double orient = 0.0;
            if ((orient = (Double) getAttVal(feature.type, Att.ORIENT)) == null) {
                orient = 0.0;
            }
            Renderer.symbol(symb, new Delta(Handle.CC, AffineTransform.getRotateInstance(Math.toRadians(orient))));
            String chn;
            if (!(chn = getAttStr(feature.type, Att.COMCHA)).isEmpty()) {
                Renderer.labelText(("Ch." + chn), new Font("Arial", Font.PLAIN, 50), Color.black,
                        new Delta(Handle.TC, AffineTransform.getTranslateInstance(0, 50)));
            }
        }
    }

    private static void depths() {
        switch (feature.type) {
        case SOUNDG:
            if ((Renderer.zoom >= 14) && hasAttribute(Obj.SOUNDG, Att.VALSOU)) {
                double depth = (double) getAttVal(Obj.SOUNDG, Att.VALSOU);
                String dstr = df.format(depth);
                String[] tok = dstr.split("[-.]");
                String ul = "";
                String id = tok[0];
                String dd = "";
                if (tok[0].equals("")) {
                    for (int i = 0; i < tok[1].length(); i++) {
                        ul += "_";
                    }
                    id = tok[1];
                    dd = (tok.length == 3) ? tok[2] : "";
                } else {
                    dd = (tok.length == 2) ? tok[1] : "";
                }
                Renderer.labelText(ul, new Font("Arial", Font.PLAIN, 30), Color.black,
                        new Delta(Handle.RC, AffineTransform.getTranslateInstance(10, 15)));
                Renderer.labelText(id, new Font("Arial", Font.PLAIN, 30), Color.black,
                        new Delta(Handle.RC, AffineTransform.getTranslateInstance(10, 0)));
                Renderer.labelText(dd, new Font("Arial", Font.PLAIN, 20), Color.black,
                        new Delta(Handle.LC, AffineTransform.getTranslateInstance(15, 10)));
            }
            break;
        case DEPCNT:
            break;
        default:
            break;
        }
    }

    private static void distances() {
        if (Renderer.zoom >= 14) {
            if (!testAttribute(Obj.DISMAR, Att.CATDIS, CatDIS.DIS_NONI)) {
                Renderer.symbol(Harbours.DistanceI);
            } else {
                Renderer.symbol(Harbours.DistanceU);
            }
            if (Renderer.zoom >= 15) {
                AttMap atts = getAtts(Obj.DISMAR, 0);
                if ((atts != null) && (atts.containsKey(Att.WTWDIS))) {
                    Double dist = (Double) atts.get(Att.WTWDIS).val;
                    String str = "";
                    if (atts.containsKey(Att.HUNITS)) {
                        switch ((UniHLU) getAttEnum(Obj.DISMAR, Att.HUNITS)) {
                        case HLU_METR:
                            str += "m ";
                            break;
                        case HLU_FEET:
                            str += "ft ";
                            break;
                        case HLU_HMTR:
                            str += "hm ";
                            break;
                        case HLU_KMTR:
                            str += "km ";
                            break;
                        case HLU_SMIL:
                            str += "M ";
                            break;
                        case HLU_NMIL:
                            str += "NM ";
                            break;
                        default:
                            break;
                        }
                    }
                    str += String.format("%1.0f", dist);
                    Renderer.labelText(str, new Font("Arial", Font.PLAIN, 40), Color.black,
                            new Delta(Handle.CC, AffineTransform.getTranslateInstance(0, 45)));
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private static void floats() {
        if ((Renderer.zoom >= 12) || ((Renderer.zoom >= 11) && ((feature.type == Obj.LITVES) ||
                (feature.type == Obj.BOYINB) || hasObject(Obj.RTPBCN)))) {
            switch (feature.type) {
            case LITVES:
                Renderer.symbol(Buoys.Super, getScheme(feature.type));
                break;
            case LITFLT:
                Renderer.symbol(Buoys.Float, getScheme(feature.type));
                break;
            case BOYINB:
                Renderer.symbol(Buoys.Super, getScheme(feature.type));
                break;
            default:
                break;
            }
            if (feature.objs.containsKey(Obj.TOPMAR)) {
                AttMap topmap = feature.objs.get(Obj.TOPMAR).get(0);
                if (topmap.containsKey(Att.TOPSHP)) {
                    Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)),
                            getScheme(Obj.TOPMAR), Topmarks.FloatDelta);
                }
            } else if (feature.objs.containsKey(Obj.DAYMAR)) {
                AttMap topmap = feature.objs.get(Obj.DAYMAR).get(0);
                if (topmap.containsKey(Att.TOPSHP)) {
                    Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)),
                            getScheme(Obj.DAYMAR), Topmarks.FloatDelta);
                }
            }
            addName(15, new Font("Arial", Font.BOLD, 40), new Delta(Handle.BL, AffineTransform.getTranslateInstance(20, -50)));
            Signals.addSignals();
        }
    }

    private static void gauges() {
        if (Renderer.zoom >= 14) {
            Renderer.symbol(Harbours.TideGauge);
            addName(15, new Font("Arial", Font.BOLD, 40), new Delta(Handle.BL, AffineTransform.getTranslateInstance(20, -50)));
            Signals.addSignals();
        }
    }

    @SuppressWarnings("unchecked")
    private static void harbours() {
        String name = getName();
        switch (feature.type) {
        case ACHBRT:
            if (Renderer.zoom >= 14) {
                Renderer.symbol(Harbours.Anchor, new Scheme(Symbols.Msymb));
                if (Renderer.zoom >= 15) {
                    Renderer.labelText(name == null ? "" : name,
                            new Font("Arial", Font.PLAIN, 30), Symbols.Msymb, LabelStyle.RRCT, Symbols.Msymb, Color.white, new Delta(Handle.BC));
                }
            }
            if (getAttVal(Obj.ACHBRT, Att.RADIUS) != null) {
                double radius;
                if ((radius = (Double) getAttVal(Obj.ACHBRT, Att.RADIUS)) != 0) {
                    UniHLU units = (UniHLU) getAttEnum(Obj.ACHBRT, Att.HUNITS);
                    if (units == UniHLU.HLU_UNKN) {
                        units = UniHLU.HLU_METR;
                    }
                    Renderer.lineCircle(new LineStyle(Symbols.Mline, 4, new float[] {10, 10}, null), radius, units);
                }
            }
            break;
        case ACHARE:
            if (Renderer.zoom >= 12) {
                if (feature.geom.prim != Pflag.AREA) {
                    Renderer.symbol(Harbours.Anchorage, new Scheme(Color.black));
                } else {
                    Renderer.symbol(Harbours.Anchorage, new Scheme(Symbols.Mline));
                    Renderer.lineSymbols(Areas.Restricted, 1.0, Areas.LineAnchor, null, 10, Symbols.Mline);
                }
                addName(15, new Font("Arial", Font.BOLD, 60), Symbols.Mline, new Delta(Handle.LC, AffineTransform.getTranslateInstance(70, 0)));
                ArrayList<StsSTS> sts = (ArrayList<StsSTS>) getAttList(Obj.ACHARE, Att.STATUS);
                if ((Renderer.zoom >= 15) && (sts.contains(StsSTS.STS_RESV))) {
                    Renderer.labelText("Reserved",
                            new Font("Arial", Font.PLAIN, 50), Symbols.Mline, new Delta(Handle.TC, AffineTransform.getTranslateInstance(0, 60)));
                }
                ArrayList<CatACH> cats = (ArrayList<CatACH>) getAttList(Obj.ACHARE, Att.CATACH);
                int dy = (cats.size() - 1) * -30;
                for (CatACH cat : cats) {
                    switch (cat) {
                    case ACH_DEEP:
                        Renderer.labelText("DW", new Font("Arial", Font.BOLD, 50), Symbols.Msymb,
                                new Delta(Handle.RC, AffineTransform.getTranslateInstance(-60, dy)));
                        dy += 60;
                        break;
                    case ACH_TANK:
                        Renderer.labelText("Tanker", new Font("Arial", Font.BOLD, 50), Symbols.Msymb,
                                new Delta(Handle.RC, AffineTransform.getTranslateInstance(-60, dy)));
                        dy += 60;
                        break;
                    case ACH_H24P:
                        Renderer.labelText("24h", new Font("Arial", Font.BOLD, 50), Symbols.Msymb,
                                new Delta(Handle.RC, AffineTransform.getTranslateInstance(-60, dy)));
                        dy += 60;
                        break;
                    case ACH_EXPL:
                        Renderer.symbol(Harbours.Explosives, new Scheme(Symbols.Msymb),
                                new Delta(Handle.RC, AffineTransform.getTranslateInstance(-60, dy)));
                        dy += 60;
                        break;
                    case ACH_QUAR:
                        Renderer.symbol(Harbours.Hospital, new Scheme(Symbols.Msymb),
                                new Delta(Handle.RC, AffineTransform.getTranslateInstance(-60, dy)));
                        dy += 60;
                        break;
                    case ACH_SEAP:
                        Renderer.symbol(Areas.Seaplane, new Scheme(Symbols.Msymb),
                                new Delta(Handle.RC, AffineTransform.getTranslateInstance(-60, dy)));
                        dy += 60;
                        break;
                    default:
                    }
                }
            }
            break;
        case BERTHS:
            if (Renderer.zoom >= 14) {
                Renderer.lineVector(new LineStyle(Symbols.Mline, 6, new float[] {20, 20}));
                Renderer.labelText(name == null ? " " : name,
                        new Font("Arial", Font.PLAIN, 40), Symbols.Msymb, LabelStyle.RRCT, Symbols.Mline, Color.white);
            }
            break;
        case BUISGL:
            if (Renderer.zoom >= 16) {
                ArrayList<Symbol> symbols = new ArrayList<>();
                ArrayList<FncFNC> fncs = (ArrayList<FncFNC>) getAttList(Obj.BUISGL, Att.FUNCTN);
                for (FncFNC fnc : fncs) {
                    symbols.add(Landmarks.Funcs.get(fnc));
                }
                if (feature.objs.containsKey(Obj.SMCFAC)) {
                    ArrayList<CatSCF> scfs = (ArrayList<CatSCF>) getAttList(Obj.SMCFAC, Att.CATSCF);
                    for (CatSCF scf : scfs) {
                        symbols.add(Facilities.Cats.get(scf));
                    }
                }
                Renderer.cluster(symbols);
            }
            break;
        case HRBFAC:
            if (Renderer.zoom >= 12) {
                ArrayList<CatHAF> cathaf = (ArrayList<CatHAF>) getAttList(Obj.HRBFAC, Att.CATHAF);
                if (cathaf.size() == 1) {
                    switch (cathaf.get(0)) {
                    case HAF_MRNA:
                        Renderer.symbol(Harbours.Marina);
                        break;
                    case HAF_MANF:
                        Renderer.symbol(Harbours.MarinaNF);
                        break;
                    case HAF_FISH:
                        Renderer.symbol(Harbours.Fishing);
                        break;
                    default:
                        Renderer.symbol(Harbours.Harbour);
                        break;
                    }
                } else {
                    Renderer.symbol(Harbours.Harbour);
                }
            }
            break;
        default:
            break;
        }
    }

    @SuppressWarnings("unchecked")
    private static void highways() {
        switch (feature.type) {
        case ROADWY:
            ArrayList<CatROD> cat = (ArrayList<CatROD>) (getAttList(Obj.ROADWY, Att.CATROD));
            if (cat.size() > 0) {
                switch (cat.get(0)) {
                case ROD_MWAY:
                    Renderer.lineVector(new LineStyle(Color.black, 20));
                    break;
                case ROD_MAJR:
                    Renderer.lineVector(new LineStyle(Color.black, 15));
                    break;
                case ROD_MINR:
                    Renderer.lineVector(new LineStyle(Color.black, 10));
                    break;
                default:
                    Renderer.lineVector(new LineStyle(Color.black, 5));
                }
            } else {
                Renderer.lineVector(new LineStyle(Color.black, 5));
            }
            break;
        case RAILWY:
            Renderer.lineVector(new LineStyle(Color.gray, 10));
            Renderer.lineVector(new LineStyle(Color.black, 10, new float[] {30, 30}));
            break;
        default:
        }
    }

    @SuppressWarnings("unchecked")
    private static void landmarks() {
        if (!hasAttribute(Obj.LNDMRK, Att.CATLMK)
                && (!hasAttribute(Obj.LNDMRK, Att.FUNCTN) || testAttribute(Obj.LNDMRK, Att.FUNCTN, FncFNC.FNC_LGHT))
                && hasObject(Obj.LIGHTS))
            lights();
        else if (Renderer.zoom >= 12) {
            switch (feature.type) {
            case LNDMRK:
                ArrayList<CatLMK> cats = (ArrayList<CatLMK>) getAttList(feature.type, Att.CATLMK);
                Symbol catSym = Landmarks.Shapes.get(cats.get(0));
                ArrayList<FncFNC> fncs = (ArrayList<FncFNC>) getAttList(feature.type, Att.FUNCTN);
                Symbol fncSym = Landmarks.Funcs.get(fncs.get(0));
                if ((fncs.get(0) == FncFNC.FNC_CHCH) && (cats.get(0) == CatLMK.LMK_TOWR))
                    catSym = Landmarks.ChurchTower;
                if (cats.get(0) == CatLMK.LMK_RADR)
                    fncSym = Landmarks.RadioTV;
                Renderer.symbol(catSym);
                Renderer.symbol(fncSym);
                break;
            case SILTNK:
                if (testAttribute(feature.type, Att.CATSIL, CatSIL.SIL_WTRT))
                    Renderer.symbol(Landmarks.WaterTower);
                break;
            default:
                break;
            }
            if (Renderer.zoom >= 15)
                addName(15, new Font("Arial", Font.BOLD, 40), new Delta(Handle.BL, AffineTransform.getTranslateInstance(60, -50)));
            Signals.addSignals();
        }
    }

    @SuppressWarnings("unchecked")
    private static void points() {
        boolean ok = false;
        switch (feature.type) {
        case FOGSIG:
            if (Renderer.zoom >= 12) {
                if (feature.objs.containsKey(Obj.LIGHTS))
                    lights();
                else
                    Renderer.symbol(Harbours.Post);
                ok = true;
            }
            break;
        default:
            if (Renderer.zoom >= 14) {
                if (feature.objs.containsKey(Obj.LIGHTS))
                    lights();
                else
                    Renderer.symbol(Harbours.Post);
                ok = true;
            }
            break;
        }
        if (ok) {
            if (feature.objs.containsKey(Obj.TOPMAR)) {
                AttMap topmap = feature.objs.get(Obj.TOPMAR).get(0);
                if (topmap.containsKey(Att.TOPSHP)) {
                    Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)), getScheme(Obj.TOPMAR), null);
                }
            } else if (feature.objs.containsKey(Obj.DAYMAR)) {
                AttMap topmap = feature.objs.get(Obj.DAYMAR).get(0);
                if (topmap.containsKey(Att.TOPSHP)) {
                    Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)), getScheme(Obj.DAYMAR), null);
                }
            }
            Signals.addSignals();
        }
    }

    @SuppressWarnings("unchecked")
    private static void lights() {
        boolean ok = false;
        switch (feature.type) {
        case LITMAJ:
        case LNDMRK:
            if (Renderer.zoom >= 12) {
                Renderer.symbol(Beacons.LightMajor);
                ok = true;
            }
            break;
        case LITMIN:
        case LIGHTS:
        case PILPNT:
            if (Renderer.zoom >= 14) {
                Renderer.symbol(Beacons.LightMinor);
                ok = true;
            }
            break;
        default:
            break;
        }
        if (ok) {
            if (feature.objs.containsKey(Obj.TOPMAR)) {
                AttMap topmap = feature.objs.get(Obj.TOPMAR).get(0);
                if (topmap.containsKey(Att.TOPSHP)) {
                    Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)),
                            getScheme(Obj.TOPMAR), Topmarks.LightDelta);
                }
            } else if (feature.objs.containsKey(Obj.DAYMAR)) {
                AttMap topmap = feature.objs.get(Obj.DAYMAR).get(0);
                if (topmap.containsKey(Att.TOPSHP)) {
                    Renderer.symbol(Topmarks.Shapes.get(((ArrayList<TopSHP>) (topmap.get(Att.TOPSHP).val)).get(0)),
                            getScheme(Obj.DAYMAR), Topmarks.LightDelta);
                }
            }
            Signals.addSignals();
        }
    }

    @SuppressWarnings("unchecked")
    private static void marinas() {
        if (Renderer.zoom >= 16) {
            ArrayList<Symbol> symbols = new ArrayList<>();
            ArrayList<CatSCF> scfs = (ArrayList<CatSCF>) getAttList(Obj.SMCFAC, Att.CATSCF);
            for (CatSCF scf : scfs) {
                symbols.add(Facilities.Cats.get(scf));
            }
            Renderer.cluster(symbols);
        }
    }

    private static void moorings() {
        if (Renderer.zoom >= 14) {
            switch ((CatMOR) getAttEnum(feature.type, Att.CATMOR)) {
            case MOR_DLPN:
                Renderer.symbol(Harbours.Dolphin);
                break;
            case MOR_DDPN:
                Renderer.symbol(Harbours.DeviationDolphin);
                break;
            case MOR_BLRD:
            case MOR_POST:
                Renderer.symbol(Harbours.Bollard);
                break;
            case MOR_BUOY:
                BoySHP shape = (BoySHP) getAttEnum(feature.type, Att.BOYSHP);
                if (shape == BoySHP.BOY_UNKN) {
                    shape = BoySHP.BOY_SPHR;
                }
                Renderer.symbol(Buoys.Shapes.get(shape), getScheme(feature.type));
                Renderer.symbol(Topmarks.TopMooring, Topmarks.BuoyDeltas.get(shape));
                break;
            default:
                break;
            }
            Signals.addSignals();
        }
    }

    @SuppressWarnings("unchecked")
    private static void notices() {
        if (Renderer.zoom >= 14) {
            double dx = 0.0, dy = 0.0;
            switch (feature.type) {
            case BCNCAR:
            case BCNISD:
            case BCNLAT:
            case BCNSAW:
            case BCNSPP:
                if (testAttribute(Obj.TOPMAR, Att.TOPSHP, TopSHP.TOP_BORD) || testAttribute(Obj.DAYMAR, Att.TOPSHP, TopSHP.TOP_BORD)) {
                    dy = -100.0;
                } else {
                    dy = -45.0;
                }
                break;
            case NOTMRK:
                dy = 0.0;
                break;
            default:
                return;
            }
            MarSYS sys = MarSYS.SYS_CEVN;
            BnkWTW bnk = BnkWTW.BWW_UNKN;
            AttVal<?> att = feature.atts.get(Att.MARSYS);
            if (att != null) sys = (MarSYS) att.val;
            att = feature.atts.get(Att.BNKWTW);
            if (att != null) bnk = (BnkWTW) att.val;
            ObjTab objs = feature.objs.get(Obj.NOTMRK);
            int n = objs.size();
            if (n > 5) {
                Renderer.symbol(Notices.Notice, new Delta(Handle.CC, AffineTransform.getTranslateInstance(dx, dy)));
            } else {
                int i = 0;
                for (AttMap atts : objs.values()) {
                    if (atts.get(Att.MARSYS) != null) sys = ((ArrayList<MarSYS>) (atts.get(Att.MARSYS).val)).get(0);
                    if (atts.get(Att.BNKWTW) != null) bnk = ((ArrayList<BnkWTW>) (atts.get(Att.BNKWTW).val)).get(0);
                    CatNMK cat = CatNMK.NMK_UNKN;
                    if (atts.get(Att.CATNMK) != null) cat = ((ArrayList<CatNMK>) (atts.get(Att.CATNMK).val)).get(0);
                    Symbol sym = Notices.getNotice(cat, sys, bnk);
                    Scheme sch = Notices.getScheme(sys, bnk);
                    ArrayList<AddMRK> add = new ArrayList<>();
                    if (atts.get(Att.ADDMRK) != null) add = (ArrayList<AddMRK>) (atts.get(Att.ADDMRK).val);
                    Handle h = Handle.CC;
                    double ax = 0.0;
                    double ay = 0.0;
                    switch (i) {
                    case 0:
                        if (n != 1) h = null;
                        break;
                    case 1:
                        if (n <= 3) {
                            h = Handle.RC;
                            ax = -30;
                            ay = dy;
                        } else {
                            h = Handle.BR;
                        }
                        break;
                    case 2:
                        if (n <= 3)
                            h = Handle.LC;
                        else
                            h = Handle.BL;
                        break;
                    case 3:
                        if (n == 4)
                            h = Handle.TC;
                        else
                            h = Handle.TR;
                        break;
                    case 4:
                        h = Handle.TL;
                        break;
                    }
                    if (h != null) {
                        Renderer.symbol(sym, sch, new Delta(h, AffineTransform.getTranslateInstance(dx, dy)));
                        if (!add.isEmpty())
                            Renderer.symbol(Notices.NoticeBoard, new Delta(Handle.BC, AffineTransform.getTranslateInstance(ax, ay - 30)));
                    }
                    i++;
                }
            }
        }
    }

    private static void obstructions() {
        if ((Renderer.zoom >= 12) && (feature.type == Obj.OBSTRN)) {
            if (getAttEnum(feature.type, Att.CATOBS) == CatOBS.OBS_BOOM) {
                Renderer.lineVector(new LineStyle(Color.black, 5, new float[] {20, 20}, null));
                if (Renderer.zoom >= 15) {
                    Renderer.lineText("Boom", new Font("Arial", Font.PLAIN, 80), Color.black, -20);
                }
            }
        }
        if ((Renderer.zoom >= 14) && (feature.type == Obj.UWTROC)) {
            switch ((WatLEV) getAttEnum(feature.type, Att.WATLEV)) {
            case LEV_CVRS:
                Renderer.symbol(Areas.RockC);
                break;
            case LEV_AWSH:
                Renderer.symbol(Areas.RockA);
                break;
            default:
                Renderer.symbol(Areas.Rock);
            }
        } else {
            Renderer.symbol(Areas.Rock);
        }
    }

    private static void pipelines() {
        if ((Renderer.zoom >= 16) && (feature.geom.length < 2)) {
            if (feature.type == Obj.PIPSOL) {
                Renderer.lineSymbols(Areas.Pipeline, 1.0, null, null, 0, Symbols.Mline);
            } else if (feature.type == Obj.PIPOHD) {
                Renderer.lineVector(new LineStyle(Color.black, 8));
                AttMap atts = feature.atts;
                double verclr = 0;
                if (atts != null) {
                    if (atts.containsKey(Att.VERCLR)) {
                        verclr = (Double) atts.get(Att.VERCLR).val;
                    } else {
                        verclr = atts.containsKey(Att.VERCSA) ? (Double) atts.get(Att.VERCSA).val : 0;
                    }
                    if (verclr > 0) {
                        Renderer.labelText(String.valueOf(verclr), new Font("Arial", Font.PLAIN, 50), Color.black, LabelStyle.VCLR,
                                Color.black, new Delta(Handle.TC, AffineTransform.getTranslateInstance(0, 25)));
                    }
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private static void platforms() {
        ArrayList<CatOFP> cats = (ArrayList<CatOFP>) getAttList(Obj.OFSPLF, Att.CATOFP);
        if (cats.get(0) == CatOFP.OFP_FPSO)
            Renderer.symbol(Buoys.Storage);
        else
            Renderer.symbol(Landmarks.Platform);
        addName(15, new Font("Arial", Font.BOLD, 40), new Delta(Handle.BL, AffineTransform.getTranslateInstance(20, -50)));
        Signals.addSignals();
    }

    private static void ports() {
        if (Renderer.zoom >= 14) {
            if (feature.type == Obj.CRANES) {
                if ((CatCRN) getAttEnum(feature.type, Att.CATCRN) == CatCRN.CRN_CONT)
                    Renderer.symbol(Harbours.ContainerCrane);
                else
                    Renderer.symbol(Harbours.PortCrane);
            } else if (feature.type == Obj.HULKES) {
                Renderer.lineVector(new LineStyle(Color.black, 4, null, new Color(0xffe000)));
                addName(15, new Font("Arial", Font.BOLD, 40));
            }
        }
    }

    private static void separation() {
        switch (feature.type) {
        case TSEZNE:
        case TSSCRS:
        case TSSRON:
            if (Renderer.zoom <= 15)
                Renderer.lineVector(new LineStyle(Symbols.Mtss));
            else
                Renderer.lineVector(new LineStyle(Symbols.Mtss, 20, null, null));
            addName(10, new Font("Arial", Font.BOLD, 150), Symbols.Mline);
            break;
        case TSELNE:
            Renderer.lineVector(new LineStyle(Symbols.Mtss, 20, null, null));
            break;
        case TSSLPT:
            Renderer.lineSymbols(Areas.LaneArrow, 0.5, null, null, 0, Symbols.Mtss);
            break;
        case TSSBND:
            Renderer.lineVector(new LineStyle(Symbols.Mtss, 20, new float[] {40, 40}, null));
            break;
        case ISTZNE:
            Renderer.lineSymbols(Areas.Restricted, 1.0, null, null, 0, Symbols.Mtss);
            break;
        default:
            break;
        }
    }

    @SuppressWarnings("unchecked")
    private static void shoreline() {
        CatSLC cat = (CatSLC) getAttEnum(feature.type, Att.CATSLC);
        if ((Renderer.context.ruleset() == RuleSet.ALL) || (Renderer.context.ruleset() == RuleSet.BASE)) {
            if ((cat != CatSLC.SLC_SWAY) && (cat != CatSLC.SLC_TWAL)) {
                if (Renderer.zoom >= 12) {
                    Renderer.lineVector(new LineStyle(Color.black, 10, Symbols.Yland));
                } else {
                    Renderer.lineVector(new LineStyle(Symbols.Yland));
                }
            }
        }
        if ((Renderer.context.ruleset() == RuleSet.ALL) || (Renderer.context.ruleset() == RuleSet.SEAMARK)) {
            if (Renderer.zoom >= 12) {
                switch (cat) {
                case SLC_TWAL:
                    WatLEV lev = (WatLEV) getAttEnum(feature.type, Att.WATLEV);
                    if (lev == WatLEV.LEV_CVRS) {
                        Renderer.lineVector(new LineStyle(Color.black, 10, new float[] {40, 40}, null));
                        if (Renderer.zoom >= 15)
                            Renderer.lineText("(covers)", new Font("Arial", Font.PLAIN, 60), Color.black, 80);
                    } else {
                        Renderer.lineVector(new LineStyle(Color.black, 10, null, null));
                    }
                    if (Renderer.zoom >= 15)
                        Renderer.lineText("Training Wall", new Font("Arial", Font.PLAIN, 60), Color.black, -30);
                    break;
                case SLC_SWAY:
                    Renderer.lineVector(new LineStyle(Color.black, 2, null, new Color(0xffe000)));
                    if ((Renderer.zoom >= 16) && feature.objs.containsKey(Obj.SMCFAC)) {
                        ArrayList<Symbol> symbols = new ArrayList<>();
                        ArrayList<CatSCF> scfs = (ArrayList<CatSCF>) getAttList(Obj.SMCFAC, Att.CATSCF);
                        for (CatSCF scf : scfs) {
                            symbols.add(Facilities.Cats.get(scf));
                        }
                        Renderer.cluster(symbols);
                    }
                    break;
                default:
                    break;
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private static void stations() {
        if (Renderer.zoom >= 14) {
            String str = "";
            switch (feature.type) {
            case SISTAT:
                Renderer.symbol(Harbours.SignalStation);
                str = "SS";
                ArrayList<CatSIT> tcats = (ArrayList<CatSIT>) getAttList(Obj.SISTAT, Att.CATSIT);
                switch (tcats.get(0)) {
                case SIT_IPT:
                    str += "(INT)";
                    break;
                case SIT_PRTE:
                    str += "(Traffic)";
                    break;
                case SIT_PRTC:
                    str += "(Port Control)";
                    break;
                case SIT_LOCK:
                    str += "(Lock)";
                    break;
                case SIT_BRDG:
                    str += "(Bridge)";
                    break;
                default:
                    break;
                }
                break;
            case SISTAW:
                Renderer.symbol(Harbours.SignalStation);
                str = "SS";
                str = "SS";
                ArrayList<CatSIW> wcats = (ArrayList<CatSIW>) getAttList(Obj.SISTAW, Att.CATSIW);
                switch (wcats.get(0)) {
                case SIW_STRM:
                    str += "(Storm)";
                    break;
                case SIW_WTHR:
                    str += "(Weather)";
                    break;
                case SIW_ICE:
                    str += "(Ice)";
                    break;
                case SIW_TIDG:
                    str = "Tide gauge";
                    break;
                case SIW_TIDS:
                    str = "Tide scale";
                    break;
                case SIW_TIDE:
                    str += "(Tide)";
                    break;
                case SIW_TSTR:
                    str += "(Stream)";
                    break;
                case SIW_DNGR:
                    str += "(Danger)";
                    break;
                case SIW_MILY:
                    str += "(Firing)";
                    break;
                case SIW_TIME:
                    str += "(Time)";
                    break;
                default:
                    break;
                }
                break;
            case RDOSTA:
            case RTPBCN:
                Renderer.symbol(Harbours.SignalStation);
                Renderer.symbol(Beacons.RadarStation);
                break;
            case RADRFL:
                Renderer.symbol(Topmarks.RadarReflector);
                break;
            case RADSTA:
                Renderer.symbol(Harbours.SignalStation);
                Renderer.symbol(Beacons.RadarStation);
                Renderer.labelText("Ra", new Font("Arial", Font.PLAIN, 40), Symbols.Msymb,
                        new Delta(Handle.TR, AffineTransform.getTranslateInstance(-30, -70)));
                break;
            case PILBOP:
                Renderer.symbol(Harbours.Pilot);
                addName(15, new Font("Arial", Font.BOLD, 40), Symbols.Msymb,
                        new Delta(Handle.LC, AffineTransform.getTranslateInstance(70, -40)));
                CatPIL cat = (CatPIL) getAttEnum(feature.type, Att.CATPIL);
                if (cat == CatPIL.PIL_HELI) {
                    Renderer.labelText("H", new Font("Arial", Font.PLAIN, 40), Symbols.Msymb,
                            new Delta(Handle.LC, AffineTransform.getTranslateInstance(70, 0)));
                }
                break;
            case CGUSTA:
                Renderer.symbol(Harbours.SignalStation);
                str = "CG";
                if (feature.objs.containsKey(Obj.RSCSTA)) Renderer.symbol(Harbours.Rescue,
                        new Delta(Handle.CC, AffineTransform.getTranslateInstance(130, 0)));
                break;
            case RSCSTA:
                Renderer.symbol(Harbours.Rescue);
                break;
            default:
                break;
            }
            if ((Renderer.zoom >= 15) && !str.isEmpty()) {
                Renderer.labelText(str, new Font("Arial", Font.PLAIN, 40), Color.black,
                        new Delta(Handle.LC, AffineTransform.getTranslateInstance(40, 0)));
            }
            Signals.addSignals();
        }
    }

    private static void transits() {
        if (Renderer.zoom >= 14) {
            if (feature.type == Obj.RECTRC) Renderer.lineVector(new LineStyle(Color.black, 10, null, null));
            else if (feature.type == Obj.NAVLNE) Renderer.lineVector(new LineStyle(Color.black, 10, new float[] {25, 25}, null));
        }
        if (Renderer.zoom >= 15) {
            String str = "";
            String name = getName();
            if (name != null)
                str += name + " ";
            Double ort;
            if ((ort = (Double) getAttVal(feature.type, Att.ORIENT)) != null) {
                str += df.format(ort) + "º";
                if (!str.isEmpty())
                    Renderer.lineText(str, new Font("Arial", Font.PLAIN, 80), Color.black, -20);
            }
        }
    }

    private static void waterways() {
        Renderer.lineVector(new LineStyle(Symbols.Bwater, 20, (feature.geom.prim == Pflag.AREA) ? Symbols.Bwater : null));
    }

    private static void wrecks() {
        if (Renderer.zoom >= 14) {
            switch ((CatWRK) getAttEnum(feature.type, Att.CATWRK)) {
            case WRK_DNGR:
            case WRK_MSTS:
                Renderer.symbol(Areas.WreckD);
                break;
            case WRK_HULS:
                Renderer.symbol(Areas.WreckS);
                break;
            default:
                Renderer.symbol(Areas.WreckND);
            }
        }
    }
}
