001/*
002 * $Id: OptionMaster.java,v 1.15 2011/03/24 16:06:34 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2011
007 * Space Science and Engineering Center (SSEC)
008 * University of Wisconsin - Madison
009 * 1225 W. Dayton Street, Madison, WI 53706, USA
010 * https://www.ssec.wisc.edu/mcidas
011 * 
012 * All Rights Reserved
013 * 
014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015 * some McIDAS-V source code is based on IDV and VisAD source code.  
016 * 
017 * McIDAS-V is free software; you can redistribute it and/or modify
018 * it under the terms of the GNU Lesser Public License as published by
019 * the Free Software Foundation; either version 3 of the License, or
020 * (at your option) any later version.
021 * 
022 * McIDAS-V is distributed in the hope that it will be useful,
023 * but WITHOUT ANY WARRANTY; without even the implied warranty of
024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025 * GNU Lesser Public License for more details.
026 * 
027 * You should have received a copy of the GNU Lesser Public License
028 * along with this program.  If not, see http://www.gnu.org/licenses.
029 */
030
031package edu.wisc.ssec.mcidasv.startupmanager.options;
032
033import java.io.BufferedReader;
034import java.io.BufferedWriter;
035import java.io.File;
036import java.io.FileReader;
037import java.io.FileWriter;
038import java.io.IOException;
039import java.util.ArrayList;
040import java.util.Collection;
041import java.util.Collections;
042import java.util.HashMap;
043import java.util.Map;
044import java.util.Set;
045
046import edu.wisc.ssec.mcidasv.startupmanager.StartupManager;
047import edu.wisc.ssec.mcidasv.startupmanager.StartupManager.Platform;
048
049public enum OptionMaster {
050    /** The lone OptionMaster instance. */
051    INSTANCE;
052
053    // TODO(jon): write CollectionHelpers.zip() and CollectionHelpers.zipWith()
054    public final Object[][] blahblah = {
055        { "HEAP_SIZE", "Memory", "512m", Type.MEMORY, OptionPlatform.ALL, Visibility.VISIBLE },
056        { "JOGL_TOGL", "Enable JOGL", "1", Type.BOOLEAN, OptionPlatform.UNIXLIKE, Visibility.VISIBLE },
057        { "USE_3DSTUFF", "Enable 3D controls", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
058        { "DEFAULT_LAYOUT", "Load default layout", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
059        { "STARTUP_BUNDLE", "Defaults", "", Type.DIRTREE, OptionPlatform.ALL, Visibility.VISIBLE },
060        /**
061         * SLIDER_TEST seems unnecessary...?
062         */
063//        { "SLIDER_TEST", "Slider Test", "50P", Type.SLIDER, OptionPlatform.ALL, Visibility.VISIBLE },
064        /**
065         * TODO: DAVEP: TomW's windows machine needs SET D3DREND= to work properly.
066         * Not sure why, but it shouldn't hurt other users.  Investigate after Alpha10
067         */
068        { "D3DREND", "Enable Direct3D", "", Type.BOOLEAN, OptionPlatform.WINDOWS, Visibility.VISIBLE },
069        // mcidasv enables this (the actual property is "visad.java3d.geometryByRef")
070        // by default in mcidasv.properties.
071        { "USE_GEOBYREF", "Enable access to geometry by reference", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
072        { "USE_IMAGEBYREF", "Enable access to image data by reference", "1", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
073        { "USE_NPOT", "Enable Non-Power of Two (NPOT) textures", "0", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
074        // temp bandaid for people suffering from permgen problems.
075        { "USE_CMSGC", "Enable concurrent mark-sweep garbage collector", "0", Type.BOOLEAN, OptionPlatform.ALL, Visibility.VISIBLE },
076    };
077
078    /**
079     * {@link Option}s can be either platform-specific or applicable to all
080     * platforms. Options that are platform-specific still appear in the 
081     * UI, but their component is not enabled.
082     */
083    public enum OptionPlatform { ALL, UNIXLIKE, WINDOWS };
084
085    /**
086     * The different types of {@link Option}s.
087     * @see TextOption
088     * @see BooleanOption
089     * @see MemoryOption
090     */
091    public enum Type { TEXT, BOOLEAN, MEMORY, DIRTREE, SLIDER };
092
093    /** 
094     * Different ways that an {@link Option} might be displayed.
095     */
096    public enum Visibility { VISIBLE, HIDDEN };
097
098    /** Maps an option ID to the corresponding object. */
099    private final Map<String, Option> optionMap;
100
101    OptionMaster() {
102        normalizeUserDirectory();
103        optionMap = buildOptions(blahblah);
104//        readStartup();
105    }
106
107    /**
108     * Creates the specified options and returns a mapping of the option ID
109     * to the actual {@link Option} object.
110     * 
111     * @param options An array specifying the {@code Option}s to be built.
112     * 
113     * @return Mapping of ID to {@code Option}.
114     * 
115     * @throws AssertionError if the option array contained an entry that
116     * this method cannot build.
117     */
118    private Map<String, Option> buildOptions(final Object[][] options) {
119        // TODO(jon): seriously, get that zip stuff working! this array 
120        // stuff is BAD.
121        Map<String, Option> optMap = new HashMap<String, Option>();
122
123        for (Object[] arrayOption : options) {
124            String id = (String)arrayOption[0];
125            String label = (String)arrayOption[1];
126            String defaultValue = (String)arrayOption[2];
127            Type type = (Type)arrayOption[3];
128            OptionPlatform platform = (OptionPlatform)arrayOption[4];
129            Visibility visibility = (Visibility)arrayOption[5];
130
131            Option newOption;
132            switch (type) {
133                case TEXT:
134                    newOption = new TextOption(id, label, defaultValue, 
135                        platform, visibility);
136                    break;
137                case BOOLEAN:
138                    newOption = new BooleanOption(id, label, defaultValue, 
139                        platform, visibility);
140                    break;
141                case MEMORY:
142                    newOption = new MemoryOption(id, label, defaultValue, 
143                        platform, visibility);
144                    break;
145                case DIRTREE:
146                    newOption = new DirectoryOption(id, label, defaultValue, platform, visibility);
147                    break;
148                case SLIDER:
149                    newOption = new SliderOption(id, label, defaultValue, platform, visibility);
150                    break;
151                default:
152                     throw new AssertionError(type + 
153                         " is not known to OptionMaster.buildOptions()");
154            }
155            optMap.put(id, newOption);
156        }
157        return optMap;
158    }
159
160    /**
161     * Converts a {@link Platform} to its corresponding 
162     * {@link OptionPlatform} type.
163     * 
164     * @return The current platform as a {@code OptionPlatform} type.
165     * 
166     * @throws AssertionError if {@link StartupManager#getPlatform()} 
167     * returned something that this method cannot convert.
168     */
169    // a lame-o hack :(
170    protected OptionPlatform convertToOptionPlatform() {
171        Platform platform = StartupManager.INSTANCE.getPlatform();
172        switch (platform) {
173            case WINDOWS: return OptionPlatform.WINDOWS;
174            case UNIXLIKE: return OptionPlatform.UNIXLIKE;
175            default: 
176                throw new AssertionError("Unknown platform: " + platform);
177        }
178    }
179
180    /**
181     * Returns the {@link Option} mapped to {@code id}.
182     * 
183     * @param id The ID whose associated {@code Option} is to be returned.
184     * 
185     * @return Either the {@code Option} associated with {@code id}, or 
186     * {@code null} if there was no association.
187     */
188    public Option getOption(final String id) {
189        return optionMap.get(id);
190    }
191
192    // TODO(jon): getAllOptions and optionsBy* really need some work.
193    // I want to eventually do something like:
194    // Collection<Option> = getOpts().byPlatform(WINDOWS, ALL).byType(BOOLEAN).byVis(HIDDEN)
195    public Collection<Option> getAllOptions() {
196        return Collections.unmodifiableCollection(optionMap.values());
197    }
198
199    public Collection<Option> optionsByPlatform(
200        final Set<OptionPlatform> platforms) 
201    {
202        if (platforms == null)
203            throw new NullPointerException();
204
205        Collection<Option> allOptions = getAllOptions();
206        Collection<Option> filteredOptions = 
207            new ArrayList<Option>(allOptions.size());
208        for (Option option : allOptions)
209            if (platforms.contains(option.getOptionPlatform()))
210                filteredOptions.add(option);
211//      return Collections.unmodifiableCollection(filteredOptions);
212        return filteredOptions;
213    }
214
215    public Collection<Option> optionsByType(final Set<Type> types) {
216        if (types == null)
217            throw new NullPointerException();
218
219        Collection<Option> allOptions = getAllOptions();
220        Collection<Option> filteredOptions = 
221            new ArrayList<Option>(allOptions.size());
222        for (Option option : allOptions)
223            if (types.contains(option.getOptionType()))
224                filteredOptions.add(option);
225//      return Collections.unmodifiableCollection(filteredOptions);
226        return filteredOptions;
227    }
228
229    public Collection<Option> optionsByVisibility(
230        final Set<Visibility> visibilities) 
231    {
232        if (visibilities == null)
233            throw new NullPointerException();
234
235        Collection<Option> allOptions = getAllOptions();
236        Collection<Option> filteredOptions = 
237            new ArrayList<Option>(allOptions.size());
238        for (Option option : allOptions)
239            if (visibilities.contains(option.getOptionVisibility()))
240                filteredOptions.add(option);
241//        return Collections.unmodifiableCollection(filteredOptions);
242        return filteredOptions;
243    }
244
245    private void normalizeUserDirectory() {
246        Platform platform = StartupManager.INSTANCE.getPlatform();
247        File dir = new File(platform.getUserDirectory());
248        File prefs = new File(platform.getUserPrefs());
249
250        if (!dir.exists())
251            dir.mkdir();
252
253        if (!prefs.exists()) {
254            try {
255                File defaultPrefs = new File(platform.getDefaultPrefs());
256                StartupManager.INSTANCE.copy(defaultPrefs, prefs);
257            } catch (IOException e) {
258                System.err.println("Non-fatal error copying user preference template: "+e.getMessage());
259            }
260        }
261    }
262
263    public void readStartup() {
264        String contents;
265        String line;
266
267        File script = 
268            new File(StartupManager.INSTANCE.getPlatform().getUserPrefs());
269        System.err.println("reading "+script);
270        if (script.getPath().length() == 0)
271            return;
272
273        try {
274            BufferedReader br = new BufferedReader(new FileReader(script));
275            while ((line = br.readLine()) != null) {
276                if (line.startsWith("#"))
277                    continue;
278
279                contents = line.replace("=\"", "=");
280                String[] chunks = contents.replace("SET ", "").split("=");
281                if (chunks.length == 2) {
282                    Option option = getOption(chunks[0]);
283                    if (option != null)
284                        option.fromPrefsFormat(line);
285                }
286            }
287            br.close();
288        } catch (IOException e) {
289            System.err.println("Non-fatal error reading the user preferences: "+e.getMessage());
290        }
291    }
292
293    public void writeStartup() {
294        File script = 
295            new File(StartupManager.INSTANCE.getPlatform().getUserPrefs());
296        if (script.getPath().length() == 0)
297            return;
298
299        // TODO(jon): use filters when you've made 'em less stupid
300        String newLine = 
301            StartupManager.INSTANCE.getPlatform().getNewLine();
302        OptionPlatform currentPlatform = convertToOptionPlatform();
303        StringBuilder contents = new StringBuilder();
304        for (Object[] arrayOption : blahblah) {
305            Option option = getOption((String)arrayOption[0]);
306            OptionPlatform platform = option.getOptionPlatform();
307            if (platform == OptionPlatform.ALL || platform == currentPlatform)
308                contents.append(option.toPrefsFormat() + newLine);
309        }
310
311        try {
312            BufferedWriter out = 
313                new BufferedWriter(new FileWriter(script));
314            out.write(contents.toString());
315            out.close();
316        } catch (IOException e) {
317            e.printStackTrace();
318        }
319    }
320}