001package armyc2.c5isr.renderer.symbolpicker;
002
003import android.content.Context;
004
005import java.io.BufferedReader;
006import java.io.IOException;
007import java.io.InputStream;
008import java.io.InputStreamReader;
009import java.util.Arrays;
010import java.util.HashSet;
011import java.util.Set;
012import java.util.Stack;
013
014import armyc2.c5isr.renderer.R;
015import armyc2.c5isr.renderer.utilities.MSInfo;
016import armyc2.c5isr.renderer.utilities.SymbolID;
017
018public class TreeManager {
019    private static final Set<String> SYMBOL_BLACKLIST = new HashSet<>(Arrays.asList(
020            // Symbols with no SVG or drawing in standard
021            "25342900", // Advance to contact
022            "25343000", // Capture
023            "25343100", // Conduct Exploitation
024            "25343200", // Control
025            "25343300", // Demonstration
026            "25343400", // Deny
027            "25343500", // Envelop
028            "25343600", // Escort
029            "25343700", // Exfiltrate
030            "25343800", // Infiltrate
031            "25343900", // Locate
032            "25350000", // Space debris
033            "25350100", "25350101", "25350102", "25350103", // Man made space debris
034            "25350200", "25350201", "25350202", "25350203", // Natural space debris
035            "46120313", // Hydrography Ports and Harbors Facilities
036            "46120301", // Hydrography Ports and Harbors Ports
037            "46120325", // Hydrography Ports and Harbors Shoreline Protection
038            "46120400", // Hydrography Aids to Navigation
039            "47", // Meteorological space
040
041            // Symbols with drawing in standard but no SVG
042            "10163601", // Floating Craft
043
044            // Symbols with ambiguous draw rules
045            "45162004" // Tropical Storm Wind Areas
046    ));
047
048    public Node mil2525Tree;
049
050    /** Reads the symbols from msd.txt and builds a tree.
051     * @param context Application context in which to use the tree.
052     * @param versions mil std 2525 versions to add to tree
053     * @throws IOException if there is an error reading msd.txt
054     */
055    public void buildTree(Context context, int[] versions) throws IOException {
056        mil2525Tree = new Node("Root", "XX", "XX", "XX");
057        for (int version : versions){
058            addToTree(context, version);
059        }
060    }
061
062    private void addToTree(Context context, int version) throws IOException {
063        Stack<Node> parentStack = new Stack<>();
064        Node child = mil2525Tree;
065        String line;
066        String symbolSet = "";
067
068        InputStream is;
069        if (version >= SymbolID.Version_2525E) {
070            is = context.getResources().openRawResource(R.raw.mse);
071        } else {
072            is = context.getResources().openRawResource(R.raw.msd);
073        }
074        BufferedReader br = new BufferedReader(new InputStreamReader(is));
075
076        while ((line = br.readLine()) != null) {
077            // count tabs to calculate nodeDepth
078            int nodeDepth = 1;
079            while (line.charAt(0) == '\t') {
080                line = line.substring(1);
081                nodeDepth++;
082            }
083
084            if (nodeDepth > parentStack.size()) {
085                parentStack.push(child);
086            }
087            while (nodeDepth < parentStack.size()) {
088                parentStack.pop();
089            }
090
091            // special case for parsing the Symbol Set codes since they're only 2 digits
092            if (nodeDepth == 1) {
093                String[] segments = line.split("\\t");
094                symbolSet = segments[0];
095
096                if (SYMBOL_BLACKLIST.contains(symbolSet)) {
097                    continue;
098                }
099
100                child = getChild(parentStack.peek(), symbolSet, "000000");
101                if (child == null) {
102                    child = new Node(MSInfo.parseSymbolSetName(symbolSet,version), String.valueOf(version), symbolSet, "000000");
103                    parentStack.peek().addChild(child);
104                }
105
106                if (segments[1].equals("Unspecified")) {
107                    // Ignore rest of line
108                    continue;
109                } else {
110                    // There is a subfolder on this line, add parent and continue parsing
111                    parentStack.push(child);
112                }
113            }
114
115            // skip "{Reserved for future use}" codes
116            if (!line.toLowerCase().contains("{reserved for future use}")) {
117                String[] segments = line.split("\t+");
118                String name;
119                if (nodeDepth == 1) {
120                    name = segments[1];
121                } else {
122                    name = segments[0];
123                }
124
125                // XXXXXX would indicate an error reading the file where it couldn't find 6 digits
126                String code = "XXXXXX";
127                // extract 6-digit decimal code from remainder of line segments
128                for (int i = 1; i < segments.length; i++) {
129                    if (segments[i].matches("\\d{6}")) {
130                        code = segments[i];
131                        break;
132                    }
133                }
134
135                if (SYMBOL_BLACKLIST.contains(symbolSet) || SYMBOL_BLACKLIST.contains(symbolSet + code)) {
136                    continue;
137                }
138
139                child = getChild(parentStack.peek(), symbolSet, code);
140                if (child == null) {
141                    child = new Node(name, String.valueOf(version), symbolSet, code);
142                    parentStack.peek().addChild(child);
143                }
144            }
145        }
146        br.close();
147    }
148
149    private Node getChild(Node parent, String symbolSet, String entityCode) {
150        for (Node child : parent.getChildren()){
151            if (child.getSymbolSetCode().equals(symbolSet) && child.getCode().equals(entityCode))
152                return child;
153        }
154        return null;
155    }
156}