package de.awolf.dof;

/**
 * Depth of field calculation based on the formulas from wikipedia.
 *
 * @see <a href="http://en.wikipedia.org/wiki/Depth_of_field#DOF_formulas">http://en.wikipedia.org/wiki/Depth_of_field</a>
 */
public class DepthOfFieldCalculator {

    /**
     * Calculates hyperfocal distance, far and near limit.
     *
     * @param input input parameters
     * @return hyperfocal distance, far and near limit wrapped in a DofOutput object
     */
    public DofOutput calculate(DofInput input) {
        double hyperfocalDistance = hyperfocalDistance(input.getfNumber(), input.getCircleOfConfusion(), input.getFocalLength());
        double nearLimit = nearLimit(input.getfNumber(), input.getCircleOfConfusion(), input.getFocalLength(), input.getSubjectDistance());
        double farLimit = farLimit(input.getfNumber(), input.getCircleOfConfusion(), input.getFocalLength(), input.getSubjectDistance());

        return new DofOutput(hyperfocalDistance, nearLimit, farLimit);
    }

    /**
     * Calculates the hyperfocal distance.
     *
     * @param fNumber f-number given by focal length / diameter of the entrance pupil
     * @param circleOfConfusion circle of confusion in millimeter
     * @param focalLength focal length in millimeter
     * @return hyperfocal distance in millimeter
     */
    protected double hyperfocalDistance(double fNumber, double circleOfConfusion, double focalLength) {
        return ((focalLength * focalLength) / (fNumber * circleOfConfusion)) + focalLength;
    }

    /**
     * Calculates the near limit.
     *
     * @param fNumber f-number given by focal length / diameter of the entrance pupil
     * @param circleOfConfusion circle of confusion in millimeter
     * @param focalLength focal length in millimeter
     * @param subjectDistance distance to subject in millimeter
     * @return near distance in millimeter
     */
    protected double nearLimit(double fNumber, double circleOfConfusion, double focalLength, double subjectDistance) {
        double hyperfocalDistance = hyperfocalDistance(fNumber, circleOfConfusion, focalLength);
        double numerator = subjectDistance * (hyperfocalDistance - focalLength);
        double denominator = (hyperfocalDistance - focalLength) + (subjectDistance - focalLength);
        return numerator / denominator;
    }

    /**
     * Calculates the far limit.
     * Note: if subject distance is greater then the hyperfocal distance the far limit is infinite.
     * For easier usage i avoided throwing an exception and return Double.MAX_VALUE in this case.
     *
     * @param fNumber f-number given by focal length / diameter of the entrance pupil
     * @param circleOfConfusion circle of confusion in millimeter
     * @param focalLength focal length in millimeter
     * @param subjectDistance distance to subject in millimeter
     * @return far distance in millimeter
     */
    protected double farLimit(double fNumber, double circleOfConfusion, double focalLength, double subjectDistance) {
        double hyperfocalDistance = hyperfocalDistance(fNumber, circleOfConfusion, focalLength);

        if (subjectDistance >= hyperfocalDistance) {
            return Double.MAX_VALUE;
        } else {
            double numerator = subjectDistance * (hyperfocalDistance - focalLength);
            double denominator = (hyperfocalDistance - focalLength) + (focalLength - subjectDistance);
            return numerator / denominator;
        }
    }
}
