% UNIT a helper class for implementing units in LTPDA.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% UNIT a helper class for implementing units in LTPDA.
%
% SUPERCLASSES: ltpda_nuo < ltpda_obj
%
% CONSTRUCTORS:
%
%   u = unit(str);
%
% EXAMPLES:
%
%       u = unit('m');            - Create a simple unit
%       u = unit('m^3');          - With an exponent
%       u = unit('m^1/2');
%       u = unit('m^1.5');
%       u = unit('pm^2');         - With a prefix
%       u = unit('m s^-2 kg');    - Multiple units
%       u = unit('m/s');          - Units with division
%       u = unit('m^.5 / s^2');
%
% SUPPORTED PREFIXES:      unit.supportedPrefixes
% SUPPORTED UNITS:         unit.supportedUnits
%
% VERSION:  $Id: unit.m,v 1.54 2011/08/16 04:52:19 hewitson Exp $
%
% SEE ALSO: ltpda_obj, ltpda_nuo
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%classdef unit

classdef (Hidden = true) unit < ltpda_nuo
  
  %----------------------------------------
  %- Private properties
  %----------------------------------------
  properties (SetAccess = protected)
    strs    = {}; % unit sign
    exps    = []; % exponent of the units
    vals    = []; % prefixes of the units (all SI prefixes are supported)
  end
  
  %----------------------------------------
  %- Public methods
  %----------------------------------------
  methods
    
    %----------------------------------------
    %- Constructor
    %----------------------------------------
    function u = unit(varargin)
      
      switch nargin
        case 0
          % Empty constructor
        case 1
          if ischar(varargin{1})
            % String input
            ustr = strtrim(varargin{1});
            if ~isempty(ustr)
              
              % Handle the output of char(unit)
              ustr = strtrim(strrep(strrep(ustr, '[', ' '), ']', ' '));
              
              % split on whitespace
              expr_unit = '([1a-zA-Z]+)';
              expr_frac = '([+-]?[0-9]*(\.[0-9]+)?(/-?[0-9]+)?)';
              expr = [' *' expr_unit '(\^(\(' expr_frac '\)|' expr_frac '))* *'];
              %expr = ' *([1a-zA-Z]+)(\^(\((-?[0-9]+(\/-?[0-9]+)?)\)|(-?[0-9]+(\/-?[0-9]+)?)))* *';
              tks = strtrim(regexp(ustr, expr, 'match'));
              ops = strtrim(regexp(ustr, expr, 'split'));
              
              % combine each unit
              for j=1:numel(tks)
                % Parse string
                if tks{j} == '1' % Special case for '1/s'
                  u2.strs = '';
                  u2.exps = [];
                  u2.vals = [];
                else
                  [us, exp, val] = unit.parse(tks{j});
                  u2.strs = {us};
                  u2.exps = exp;
                  u2.vals = val;
                end
                
                switch ops{j}
                  case ''
                    u.strs = [u.strs u2.strs];
                    u.exps = [u.exps u2.exps];
                    u.vals = [u.vals u2.vals];
                  case '+'
                    u2 = unit(u2);
                    u = u + u2;
                  case '-'
                    u2 = unit(u2);
                    u = u - u2;
                  case {'*', '.*'}
                    u2 = unit(u2);
                    u = u * u2;
                  case {'/', './'}
                    u2 = unit(u2);
                    u = u / u2;
                  otherwise
                    error('### Unknown operator [%s]', ops{j});
                end
              end
            end
            
          elseif isstruct(varargin{1})
            u = fromStruct(u, varargin{1});
            
          elseif isa(varargin{1}, 'double')
            
            if isempty(varargin{1})
            else
              s = unit.supportedUnits;
              if numel(varargin{1}) == numel(s)
                positions = not(varargin{1}==0);
                u.strs = s(positions);
                u.exps = varargin{1}(positions);
                u.vals = ones(1, sum(positions));
              else
                error('variable unit.supportedUnits does not match');
              end
            end
            
          elseif isa(varargin{1}, 'unit')
            u = varargin{1};
            
          elseif iscell(varargin{1})
            
            for kk=1:numel(varargin{1})
              u(kk) = unit(varargin{1}{kk});
            end
            
          else
            error('### Unknown single argument constructor. The constructor doesn''t support the class [%s]', class(varargin{1}));
          end
          
        case 2
          if  isa(varargin{1}, 'org.apache.xerces.dom.DeferredElementImpl') && ...
              isa(varargin{2}, 'history')
            u = fromDom(u, varargin{1}, varargin{2});

          elseif (isa(varargin{1}, 'unit') || ischar(varargin{1})) && ...
                 (isa(varargin{2}, 'unit') || ischar(varargin{2}))
            u = varargin{1};
            u = [u unit(varargin{2})];
            
          else
            error('### Unknown constructor method for two inputs.');
          end
          
        otherwise
          u = varargin{1};
          for i=2:numel(varargin)
            u = [u unit(varargin{i})];
          end
      end
      
    end % End constructor
    
  end % End public methods
  
  %----------------------------------------
  %- Private Static methods
  %----------------------------------------
  methods (Static=true, Access=private)
    
    %----------------------------------------
    %- Parse a unit definition string
    %----------------------------------------
    function [us, exp, val] = parse(ustr)
      
      % get exp
      [s,t] = strtok(ustr, '^');
      if ~isempty(t)
        % drop any ()
        t = strrep(t(2:end),'(','');
        t = strrep(t,')','');
        exp = eval(t);
      else
        exp = 1;
      end
      
      if length(s) > 1 && utils.helper.ismember(s(1), unit.supportedPrefixes) && utils.helper.ismember(s(2:end), unit.supportedUnits)
        % check for prefix
        sp  = s(1);
        val = unit.prefix2val(sp);
        sm  = s(2:end);
      elseif length(s) > 2 && utils.helper.ismember(s(1:2), unit.supportedPrefixes) && utils.helper.ismember(s(3:end), unit.supportedUnits)
        % special case for the prefix 'da'
        sp  = s(1:2);
        val = unit.prefix2val(sp);
        sm  = s(3:end);
      else
        val = 1;
        sp  = '';
        sm  = s;
      end
      
      % Check unit
      if ~utils.helper.ismember(sm, unit.supportedUnits)
        error(['### Unsupported unit: [' sm ']']);
      end
      
      % set unit string
      us  = [sm];
      
    end % End parse
    
    
    
    %----------------------------------------
    %- Get the value associated with a prefix
    %----------------------------------------
    function val = prefix2val(p)
      [pfxs, pfxvals] = unit.supportedPrefixes;
      val = pfxvals(strcmp(p, pfxs));
    end
    
    %----------------------------------------
    %- Get the prefix associated with a value
    %----------------------------------------
    function p = val2prefix(val)
      [pfxs, pfxvals] = unit.supportedPrefixes;
      res = val==pfxvals;
      if any(res)
        p = pfxs{val==pfxvals};
      else
        p = '';
      end
    end
  end % End static private methods
  
  %----------------------------------------
  %- Public static methods
  %----------------------------------------
  methods (Static=true)
    
    function out = VEROUT()
      out = '$Id: unit.m,v 1.54 2011/08/16 04:52:19 hewitson Exp $';
    end
    
    function ii = getInfo(varargin)
      ii = utils.helper.generic_getInfo(varargin{:}, 'unit');
    end
    
    function out = SETS()
      out = {'Default'};
    end
    
    function out = getDefaultPlist(set)
      switch lower(set)
        case 'default'
          out = plist();
        otherwise
          error('### Unknown set [%s]', set);
      end
    end
    
    function obj = initObjectWithSize(n,m)
      obj = unit.newarray([n m]);
    end
    
    %----------------------------------------
    %- Return a list of supported prefixes
    %----------------------------------------
    function varargout = supportedPrefixes
      pfxs = {...
        'y',  'z', 'a', 'f', 'p', 'n', 'u', 'm', 'c', 'd', '', ...
        'da', 'h', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'};
      pfxvals = [...
        1e-24 1e-21 1e-18 1e-15 1e-12 1e-9 1e-6 1e-3 1e-2 1e-1 1 ...
        10 100 1000 1e6 1e9 1e12 1e15 1e18 1e21 1e24];
      
      if nargout == 1
        varargout{1} = pfxs;
      elseif nargout == 2
        varargout{1} = pfxs;
        varargout{2} = pfxvals;
      else
        for kk=1:numel(pfxs)
          fprintf('%-3s[%g]\n', pfxs{kk}, pfxvals(kk));
        end
      end
      
    end
    
    %----------------------------------------
    %- Return a list of supported units
    %----------------------------------------
    function bu = supportedUnits
      ltpdaUnits = {'', 'm', 'kg', 's', 'A', 'K', 'mol', 'cd', ...
        'rad', 'deg', 'sr', 'Hz', 'N', 'Pa', 'J', 'W', 'C', 'V', 'F', ...
        'Ohm', 'S', 'Wb', 'T', 'H', 'degC', ...
        'Count', 'arb', 'Index'};
      
      prefs = getappdata(0, 'LTPDApreferences');
      userUnits = prefs.getMiscPrefs.getUnits;
      bu = [ltpdaUnits cell(1, userUnits.size)];
      for kk=0:userUnits.size-1
        bu{numel(ltpdaUnits) + kk + 1} = char(userUnits.get(kk));
      end
      
    end
    
  end
  
  %----------------------------------------
  %- Static hidden methods
  %----------------------------------------
  
  methods (Static = true, Hidden = true)
    varargout = loadobj(varargin)
    varargout = update_struct(varargin)
  end
  
  %----------------------------------------
  %- Private methods
  %----------------------------------------
  
  methods (Access = private)
  end
  
  %----------------------------------------
  %- Protected methods
  %----------------------------------------
  
  methods (Access = protected)
    varargout = fromStruct(varargin)
    varargout = fromDom(varargin)
  end
  
  methods (Hidden = true)
    varargout = attachToDom(varargin)
  end
end
