/* 
Copyright Paul James Mutton, 2005, http://www.jibble.org/

This file is part of DOFCalc.

This software is dual-licensed, allowing you to choose between the GNU
General Public License (GPL) and the www.jibble.org Commercial License.
Since the GPL may be too restrictive for use in a proprietary application,
a commercial license is also provided. Full license information can be
found at http://www.jibble.org/licenses/

*/

import javax.microedition.midlet.MIDlet;
import javax.microedition.lcdui.*;
import javax.microedition.rms.*;

public class DOFCalc extends MIDlet implements CommandListener {
    
    public static final String STORE_NAME = "DOFCalc";
    
    private TextField focalLengthTextField = new TextField("Focal Length (mm)", "50", 4, TextField.NUMERIC);
    private TextField apertureTextField = new TextField("Aperture (f-number)", "4", 4, TextField.DECIMAL);
    private TextField distanceTextField = new TextField("Distance (metres)", "10", 4, TextField.DECIMAL);
    private TextField sensorCropTextField = new TextField("Sensor Crop", "1.6", 4, TextField.DECIMAL);
    private Command exitCommand = new Command("Exit", Command.EXIT, 2);
    private Command helpCommand = new Command("About", Command.HELP, 3);
    private Command calcCommand = new Command("Calculate", Command.OK, 1);
    private Form mainForm;
    
    private StringItem lensItem = new StringItem("Settings", "");
    private StringItem totalItem = new StringItem("Total DoF", "");
    private StringItem nearLimitItem = new StringItem("Near limit", "");
    private StringItem farLimitItem = new StringItem("Far limit", "");
    private StringItem frontItem = new StringItem("DoF in front", "");
    private StringItem behindItem = new StringItem("DoF behind", "");
    private StringItem hyperfocalItem = new StringItem("Hyperfocal dist.", "");
    private Command backCommand = new Command("Back", Command.BACK, 1);
    private Form resultsForm;
    
    
    public DOFCalc() {
        mainForm = new Form("DOFCalc");
        resultsForm = new Form("DOFCalc");

        mainForm.append("Digital SLR DoF Calculator");
        mainForm.append(focalLengthTextField);
        mainForm.append(apertureTextField);
        mainForm.append(distanceTextField);
        mainForm.append(sensorCropTextField);
        mainForm.addCommand(calcCommand);
        mainForm.addCommand(exitCommand);
        mainForm.addCommand(helpCommand);
        mainForm.setCommandListener(this);
        
        resultsForm.append(lensItem);
        resultsForm.append(nearLimitItem);
        resultsForm.append(farLimitItem);
        resultsForm.append(totalItem);
        resultsForm.append(frontItem);
        resultsForm.append(behindItem);
        resultsForm.append(hyperfocalItem);
        resultsForm.addCommand(backCommand);
        resultsForm.append("* Percentages may not total 100 due to rounding errors. Other errors may be introduced by limitations in numerical operations.");
        resultsForm.setCommandListener(this);
        
    }
    
    protected void startApp() {
        // Load previous IP/MAC from RecordStore.
        try {
            RecordStore rs = RecordStore.openRecordStore(STORE_NAME, false);
            if (rs != null && rs.getNumRecords() == 4) {
                focalLengthTextField.setString(new String(rs.getRecord(1)));
                apertureTextField.setString(new String(rs.getRecord(2)));
                distanceTextField.setString(new String(rs.getRecord(3)));
                sensorCropTextField.setString(new String(rs.getRecord(4)));
            }
            rs.closeRecordStore();
        }
        catch (Exception e) {
            // Ignore and stick with defaults.
        }
        
        Display.getDisplay(this).setCurrent(mainForm);
    }
    
    public void commandAction(Command cmd, Displayable s) {
        if (cmd == exitCommand) {
            destroyApp(false);
            notifyDestroyed();
        }
        else if (cmd == backCommand) {
            Display.getDisplay(this).setCurrent(mainForm);
        }
        else if (cmd == calcCommand) {
            
            String focalLengthStr = focalLengthTextField.getString();
            String apertureStr = apertureTextField.getString();
            String distanceStr = distanceTextField.getString();
            String sensorCropStr = sensorCropTextField.getString();
            
            double focalLength = 0;
            double aperture = 0;
            double distance = 0;
            double sensorCrop = 1;
            try {
                focalLength = Integer.parseInt(focalLengthStr);
                aperture = Double.parseDouble(apertureStr);
                distance = Double.parseDouble(distanceStr);
                sensorCrop = Double.parseDouble(sensorCropStr);
            }
            catch (NumberFormatException e) {
                alert("Error", "One of the entered fields is not a number");
                return;
            }
            
            if (focalLength < 8 || focalLength > 2400) {                
                alert("Error", "Focal length must be between 8mm and 2400mm");
                return;
            }
            if (aperture < 0.7 || aperture > 99) {
                alert("Error", "Aperture must be between f/0.7 and f/99");
                return;
            }
            if (distance < 0.01 || distance > 9999) {
                alert("Error", "Distance must be between 0.01m and 9999m");
                return;
            }
            if (sensorCrop < 0.1 || sensorCrop > 10) {
                alert("Error", "Sensor crop factor must be between 0.1 and 10");
                return;
            }
            
            // NOW DO THE MATHS!
            
            // Do all calculations in metres (because that's sensible).
            focalLength /= 1000;
            // Let the circle of confusion be 0.03mm for 35mm film.
            double coc = 0.03 / 1000;
            // Divide the circle of confusion by the crop factor for non-full frame DSLRs.
            coc /= sensorCrop;
            
            double hyperfocal = (focalLength * focalLength) / (aperture * coc) + focalLength;
            double nearLimit = ((hyperfocal - focalLength) * distance) / (hyperfocal + distance - 2 * focalLength);
            
            boolean infinite = false;
            if ((hyperfocal - distance) < 0.00000001) {
                infinite = true;
            }            
            
            double farLimit = ((hyperfocal - focalLength) * distance) / (hyperfocal - distance);
            double frontPercent = (distance - nearLimit) / (farLimit - nearLimit) * 100;
            double behindPercent = (farLimit - distance) / (farLimit - nearLimit) * 100;
            double total = farLimit - nearLimit;
            
            lensItem.setText(focalLengthStr + "mm f/" + apertureStr + " at " + distanceStr + "m");
            if (infinite) {
                farLimitItem.setText("Infinity");
                totalItem.setText(sigFigger(nearLimit) + "m to Infinity");
                frontItem.setText(sigFigger(distance - nearLimit) + "m");
                behindItem.setText("Infinite");
            }
            else {
                farLimitItem.setText(sigFigger(farLimit) + "m");
                totalItem.setText(sigFigger(total) + "m");
                frontItem.setText(sigFigger(distance - nearLimit) + "m (" + sigFigger(frontPercent) + "%)");
                behindItem.setText(sigFigger(farLimit - distance) + "m (" + sigFigger(behindPercent) + "%)");
            }
            hyperfocalItem.setText(sigFigger(hyperfocal) + "m");
            nearLimitItem.setText(sigFigger(nearLimit) + "m");
            
            // Save settings for next time.
            try {
                // Save input to be used next time the MIDlet is run.
                try {
                    RecordStore.deleteRecordStore(STORE_NAME);
                } catch(RecordStoreNotFoundException anye) {
                    // ignore
                }
                RecordStore rs = RecordStore.openRecordStore(STORE_NAME, true);
                rs.addRecord(focalLengthStr.getBytes(), 0, focalLengthStr.getBytes().length);
                rs.addRecord(apertureStr.getBytes(), 0, apertureStr.getBytes().length);
                rs.addRecord(distanceStr.getBytes(), 0, distanceStr.getBytes().length);
                rs.addRecord(sensorCropStr.getBytes(), 0, sensorCropStr.getBytes().length);
                rs.closeRecordStore();
            }
            catch (Exception e) {
                // Ignore.
            }
            
            // Now show the form.
            Display.getDisplay(this).setCurrent(resultsForm);
        }
        else if (cmd == helpCommand) {
            alert("About", "DOFCalc MIDlet for SLR cameras. Copyright Paul Mutton 2005. http://www.jibble.org");
            return;
        }
    }
    
    private String sigFigger(double d) {
        // J2ME seems to lack any nice number formatting classes, so consider this a kludge.
        String value = String.valueOf(d);
        int index = value.indexOf('.');
        if (index >= 0 && value.indexOf('E') < 0) {
            // The number has a decimal place in it and no exponent.
            if (index >= 3) {
                value = value.substring(0, index);
            }
            else if (value.charAt(0) != '0' && value.length() > 4) {
                value = value.substring(0, 4);
            }
            else if (value.length() > 8) {
                value = value.substring(0, 8);
            }
        }
        return value;
    }
    
    private void alert(String title, String message) {
        Alert alert = new Alert(title);
        alert.setString(message);
        Display.getDisplay(this).setCurrent(alert);
    }
    
    protected void destroyApp(boolean unconditional) {
        
    }
    
    protected void pauseApp() {
        
    }
    
}