#####################  scale.rb -- brent@mbari.org  #######################
# $Source: /home/cvs/ESP/gen2/software/esp/lib/scale.rb,v $
#    Copyright (C) 2003 MBARI
#    MBARI Proprietary Information. All rights reserved.
# $Id: scale.rb,v 1.6 2005/06/10 07:10:19 brent Exp $
#
#     Defines abstract 
#       high-level operations on linear axis with a measurement scale
#
############################################################################

require 'slide'

class Scale < Slide
#A Scale is a Slide with a linear numeric position mapping to 
#input and display positions in some user friendly unit

  class Error < Scale::Error; end  
  
  def with(units, axisMap)
  #defines the scale for this axis.
  #the scale must at least have at least to numeric positions
  #to define the linear relationship between raw counts and the user's units
    super axisMap
    minCounts=maxCounts=minUnits=maxUnits=nil
    @sortedMap.each {|entry|
      counts = entry.first
      labels = legend[counts]
      labels = [labels] unless labels.is_a? Array
      numericLabels = labels.select {|label| label.kind_of? Numeric}
      case numericLabels.size
        when 0  #ignore positions that have no numeric labels
        when 1  #there may be at most one numeric label for any position
          unless minCounts
            minCounts, minUnits = counts, numericLabels.first
          else
            unless maxCounts
              maxCounts, maxUnits = counts, numericLabels.first
            else
              raise Error.new \
                "#{name} has more than two numbered positions", self
            end
          end
        else
          raise Error.new \
            "#{name} has >1 numeric value for raw position #{counts}", self
      end
    }
    raise Error.
      new "#{name} must have two numbered positions", self unless maxCounts
    @base = axisMap.directory[0] ? 0 : minUnits
    @unitSuffix, @format = units.is_a?(Array) ? units : [units, "%.3f"]
    @countsPerUnit=Float(maxCounts-minCounts)/(maxUnits-minUnits)
    @zero=minCounts-@countsPerUnit*minUnits
    self
  end
  alias_method :scale, :with
  
  attr_reader :countsPerUnit, :zero, :base
  attr_accessor :unitSuffix, :format

  def amount
    rawAmount rawPosition
  end 
  
  def position
    at amount
  end
  
  def moveUp units, tmpCfg=nil, maxDuration=@maxDuration
  # add (possibily negative) offset (in axis units) to current position
    moveTo amount+units, tmpCfg, maxDuration
  end
  alias_method :move, :moveUp
  
  def moveDown units, tmpCfg=nil, maxDuration=@maxDuration
  # subtract (possibily negative) offset (in axis units) to current position
    moveTo amount-units, tmpCfg, maxDuration
  end

  def rawAmount counts
  #position corresponding to raw counts
    Float(counts - @zero)/@countsPerUnit
  end
  
  def rawId counts, slop=nil
  #given raw position in counts, return:
  # corresponding position
  #slop is ignored
    at rawAmount counts
  end

  class Position < LinearAxis::Position
    def initialize axis, base, offset=nil
      @axis = axis
      if base.kind_of? Numeric  #argument is actually the desired position
        @offset = @axis.countsPerUnit * (base - (@base = @axis.base))
        @offset += offset if offset
        @offset = @offset.round
      else
        @base = base; @offset = offset
      end
    end
    def amount
      @axis.rawAmount raw
    end  
    def to_s
      if @base.kind_of? Numeric
        "#{@axis.format % amount}#{@axis.unitSuffix}"
      else
        if !@offset or @offset==0
          "#{@base}"
        else
          axisTo_s
        end
      end
    end
  end

  protected
  def baseRaw base
    #map numeric base positions to raw count corresponding to position
    base.kind_of?(Numeric) ? at(base).raw : super
  end      
  
end #Scale class


