#####################  valve.rb -- brent@mbari.org  ####################
# $Source: /home/cvs/ESP/gen2/software/esp/lib/valve.rb,v $
#    Copyright (C) 2003 MBARI
#    MBARI Proprietary Information. All rights reserved.
# $Id: valve.rb,v 1.17 2005/05/16 17:56:19 brent Exp $
#
#     Solenoid Valve control
#
#  This layers on top of Solenoid to define operations on 
#  solenoids being used to control valves
#
############################################################################

require 'solenoid'

class Valve < Solenoid
  
  class Error < StandardError; end

  def initialize(valveName, dwarf, valveNumber, valveMap)
  #return a new Valve object
  #need to insure that names are symbols in case Valve is part of a manifold
    super valveName.intern, dwarf, valveNumber, valveMap
  end
  alias_method :plumbing, :wiring

  Unknown = ['state unknown', :unknown, '?']
  Closed = [:closed,:close,:home,:shut]
  Opened = [:opened, :open, :openUp, :openedUp]
  OpenedDown = [:openedDown, :openDown]
  
  class NormallyClosedUnipolar < Solenoid::NonLatching
    def initialize
      super :off=>Closed, :down=>OpenedDown, :up=>Opened
    end
  end  
  NormallyClosed = NormallyClosedUnipolar
  
  class ManifoldElement < Solenoid::Latching
    Selected = [:selected, :opened, :select, :open]
    Bypassed = [:bypassed, :bypass, :common, :home]
    def initialize
      super :off=>Unknown, :down=>Bypassed, :up=>Selected 
    end
  end 

  
  class Manifold < AxisKernel  #an array of ManifoldElement Valves in series
  SeriesManifold = self  #default manifold configuration is a series 

    class Error < StandardError; end
  
    def initialize name, valveSeries, endName,
                    bypassed=:bypassed, selected=:selected
    #valveSeries is an array of ManifoldElements where each has
    #  a unique name and one position called bypassed, the
    #endName is the name of the configuration where all manifold
    #  valves are in their bypassed positions
      @name=name
      @endName = endName.intern
      names = valveSeries.collect {|v| v.name} << @endName
      sz = names.size
      names.uniq!
      if names.size != sz
        raise Error, "#{name} contains duplicate names"
      end      
      @series = valveSeries
      check (@bypassed = bypassed)
      check (@selected = selected)
      @series.each {|v| 
        raise Error, "#{v.name} appears In More than One Manifold" unless
           Valve.undefine v
      }
      Valve.define self
    end
    attr_reader :name, :series, :endName, :bypassed, :selected
    
    def configure
    #fail if missing the controller for any valve in the manifold
      @series.each {|valve| valve.configure}
    end
    
    def state
    #return the selected reagent(s)
    #if more than one are selected, an array is returned
      evalState :state
    end
    
    def goal
      evalState :goal
    end
    
    def goal= reagent
    #select a reagent on this manifold without waiting for settling
      byPass
      reagent = reagent.intern
      unless reagent == @endName
        @series.each {|v|
          if v.name == reagent
            v.goal= @selected
            return self
          end
        }
        raise Error, "#{name} has no selection: #{reagent}"
      end
      self
    end
    alias_method :state=, :goal=
    
    def select reagent
    #carefully select a reagent on this manifold
      Thread.log.recordMethod self, :select, [reagent]
      self.goal= reagent
      Valve.settle
      self
    end
    alias_method :to, :select

    #any undefined method is interpreted as a goal symbol!
    alias_method :method_missing, :select
  
    
    def forget
      @series.each {|v| v.forget}
      self
    end
    def home
      @series.each {|v| v.home}
      self
    end
    def reset
      Thread.log.recordMethod self, :reset
      home
      Valve.settle
      self
    end
    
    def list
    #return an array of all the manifold external connections
      @series.collect{|v| v.name}<<@endName
    end
    
    def wiring
    #return all plumbing connections
      @series.collect{|v| v.wiring}
    end
    alias_method :plumbing, :wiring
    
    def to_s
    #display the manifold's state
      if s=state
        'selects '+s.to_s
      else
        'in an unknown or transitory state'
      end
    end
    def asIRBtext
      "#{@name} #{type} #{self}"
    end
      
    def [] element
      i = @series.collect{|v| v.name}.index element
      i = element unless i
      @series[i]
    end
    def each(&block)
      @series.each &block
      self
    end

    protected
    def check name
      @series.each {|v|
        raise Error, "#{v} does not define a #{name} connection" unless
          v.map.directory[name]
      }
    end
    def byPass
      @series.each {|v| v.goal= @bypassed}
    end

    def evalState getter        
      @series.each {|v|
        case v.send getter
          when @bypassed
          when @selected
            return v.name
          else
            return nil
        end
      }
      @endName
    end
    
  end
  
  class << self  #make manifolds enumerate as valves
    rename_method :eachValve, :each
    
    def each (&block)
      eachValve &block
      Valve::Manifold.each &block
    end
  end
    
end
