package simulate.units;

/**
 * Superclass for all unit classes.  These classes provide a means for indicating
 * the physical units of a given quantity, and present methods for converting between units
 *
 * By convention, each subclass of any unit will contain a static field named UNIT, which is a handle
 * to an instance of that unit.  One can access an instance of any unit class through this static member.
 *
 * Each general unit type (i.e., dimension) is defined as an abstract class.  Each of these abstract classes
 * contains an inner static subclass (named Sim) that defines the unit as derived from the basic simulation units (amu-A-ps)
 * Thus an instance of the simulation units for any dimensioned quantity can be accessed by 
 * the handle Unit.Energy.Sim.UNIT (e.g. for the energy unit).  Simulation units can also be accessed through the
 * Sim subclass of the UnitSystem class.
 *
 * Each unit has a toPixels method that can be used to specify how a value of
 * a quantity in that unit would be scaled to render it some way on screen.
 * The scaling factor is declared static in the Sim subclass of that unit.
 */
public abstract class Unit {
    
    public Unit() {}
    
    public abstract double toSim(double x);
    public abstract double fromSim(double x);
    
    public abstract String toString();
    public abstract String symbol();
    
    public abstract double toPixels(double x);
    
    public interface D2 {  //marks a unit defined for a 2-dimensional space
    /**
     * FALSE_DEPTH is the size of a phony 3rd dimension ascribed to a 2-dimensional simulation
     * to permit its results to be reported in more familiar 3-dimensional units
     */
        static final double FALSE_DEPTH = 5.0;
    }  
    public interface D3 {}  //marks a unit defined for a 3-dimensional space (e.g., most pressure and volume units)

    /**
     * Null unit used for dimensionless quantities
     */
    public static class Null extends Unit {
        public static final Null UNIT = new Null();
        public static double TO_PIXELS = 1.0;
        public double toSim(double x) {return x;}
        public double fromSim(double x) {return x;}
        public String toString() {return "dimensionless";}
        public String symbol() {return "";}
        public double toPixels(double x) {return x*TO_PIXELS;}
    }
    
    /**
     * Simulation unit of mass is the amu (atomic mass unit, 1/AVOGADRO grams)
     */
    public static abstract class Mass extends Unit {
        public double to(Mass u, double x) {return u.fromSim(this.toSim(x));}
        public double from(Mass u, double x) {return u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Mass {
            public static final Mass UNIT = Amu.UNIT;
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return UNIT.toString();}
            public String symbol() {return UNIT.symbol();} 
        }
    }

    /**
     * Simulation unit of time is the Angstrom
     */
    public static abstract class Length extends Unit {
        public double to(Length u, double x) {return u.fromSim(this.toSim(x));}
        public double from(Length u, double x) {return u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Length {
            public static final Length UNIT = Angstrom.UNIT;
            public static double TO_PIXELS = 10.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return UNIT.toString();}
            public String symbol() {return UNIT.symbol();} 
        }
    }

    /**
     * Simulation unit of time is the picosecond
     */
    public static abstract class Time extends Unit {
        public double to(Time u, double x) {return u.fromSim(this.toSim(x));}
        public double from(Time u, double x) {return u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Time {
            public static final Time UNIT = Picosecond.UNIT;
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return UNIT.toString();}
            public String symbol() {return UNIT.symbol();} 
        }
    }
        
    /**
     * Simulation unit of charge is (amu-A^3/ps^2)^(1/2)
     */
    public static abstract class Charge extends Unit {
        public double to(Time u, double x) {return u.fromSim(this.toSim(x));}
        public double from(Time u, double x) {return u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Charge {
            public static final Charge UNIT = new Sim();
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return "sim units";}
            public String symbol() {return "(amu-A^3/ps^2)^(1/2)";} 
        }
    }

    /**
     * Simulation unit of electrostatic dipole moment is (amu-A^5/ps^2)^(1/2)
     */
    public static abstract class Dipole extends Unit {
        public double to(Time u, double x) {return u.fromSim(this.toSim(x));}
        public double from(Time u, double x) {return u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Dipole {
            public static final Dipole UNIT = new Sim();
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return "sim units";}
            public String symbol() {return "(amu-A^5/ps^2)^(1/2)";} 
        }
    }

    /**
     * Simulation unit of energy is amu-A^2/ps^2
     */
    public static abstract class Energy extends Unit {
        public double to(Energy u, double x) {return u.fromSim(this.toSim(x));}
        public double from(Energy u, double x) {return u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Energy {
            public static final Energy UNIT = new Sim();
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return "sim units";}
            public String symbol() {return "amu-A^2/ps^2";};  
        }
    }
    
    /**
     * Simulation unit of temperature is the simulation energy unit,
     * obtained by multiplying the temperature by Boltzmann's constant
     */
    public static abstract class Temperature extends Energy {
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        public static final class Sim extends Temperature {
            public static final Temperature UNIT = new Sim();
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return "times kB, sim units";}
            public String symbol() {return "kB amu-A^2/ps^2";};  
        }
    }
    
    /**
     * Simulation unit of (3D) pressure is (amu-A/ps^2)/A^2 = amu/(A-ps^2)
     */
    public static abstract class Pressure extends Unit {
        public double to(Pressure u, double x) {return u.fromSim(this.toSim(x));}
        public double from(Pressure u, double x) {return u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Pressure {
            public static final Pressure UNIT = new Sim();
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return "sim units";}
            public String symbol() {return "amu/(A-ps^2)";};  
        }
    }
    
    /**
     * Simulation unit of (2D) pressure is (amu-A/ps^2)/A = amu/ps^2
     */
    public static abstract class Pressure2D extends Pressure implements D2 {
        //if converting to a "false" 3D pressure, divide by false depth
        public double to(Pressure u, double x) {
            return (u instanceof D3) ? u.fromSim(this.toSim(x/Unit.D2.FALSE_DEPTH)) : u.fromSim(this.toSim(x));
        }
        //if converting from a "false" 3D pressure, multiply by false depth
        public double from(Pressure u, double x) {
            return (u instanceof D3) ? u.toSim(this.fromSim(x*Unit.D2.FALSE_DEPTH)) : u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Pressure2D {
            public static final Pressure2D UNIT = new Sim();
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return "sim units";}
            public String symbol() {return "amu/ps^2";};  
        }
    }
    
    /**
     * Simulation unit of (3D) volume is A^3
     */
    public static abstract class Volume extends Unit {
        public double to(Volume u, double x) {return u.fromSim(this.toSim(x));}
        public double from(Volume u, double x) {return u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Volume {
            public static final Volume UNIT = new Sim();
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return "sim units";}
            public String symbol() {return "A^3";};  
        }
    }
    
    /**
     * Simulation unit of (2D) volume is A^2
     */
    public static abstract class Volume2D extends Volume implements D2 {
        //if converting to a "false" 3D volume, multiply by false depth
        public double to(Volume u, double x) {
            return (u instanceof D3) ? u.fromSim(this.toSim(x*Unit.D2.FALSE_DEPTH)) : u.fromSim(this.toSim(x));
        }
        //if converting from a "false" 3D volume, divide by false depth
        public double from(Volume u, double x) {
            return (u instanceof D3) ? u.toSim(this.fromSim(x/Unit.D2.FALSE_DEPTH)) : u.toSim(this.fromSim(x));}
        public double toPixels(double x) {return this.toSim(x)*Sim.TO_PIXELS;}
        
        public static final class Sim extends Volume2D {
            public static final Volume2D UNIT = new Sim();
            public static double TO_PIXELS = 1.0;
            public double toSim(double x) {return x;}
            public double fromSim(double x) {return x;}
            public String toString() {return "sim units";}
            public String symbol() {return "A^2";};  
        }
    }
    
    //others to be defined include velocity, momentum, etc.
    //also need an easy way to add prefixes and do molar quantities without redefining a new
    //unit for each
}