module OrigenTesters
  module IGXLBasedTester
    class Base
      class FlowLine
        attr_accessor :type, :id, :cz_setup # cz_setup is a virtual attrib since it is not part of the regular flow line
        # cz_setup combine with instance name when characterize opcode is used

        # Map any aliases to the official names here, multiple aliases for a given attribute
        # are allowed
        ALIASES = {
          bin:            :bin_fail,
          softbin:        :sort_fail,
          soft_bin:       :sort_fail,
          sbin:           :sort_fail,
          name:           :tname,
          number:         :tnum,
          if_enable:      :enable,
          if_enabled:     :enable,
          enabled:        :enable,
          hi_limit:       :hilim,
          hi:             :hilim,
          lo_limit:       :lolim,
          lo:             :lolim,
          # Aliases can also be used to set defaults on multiple attributes like this,
          # use :value to refer to the value passed in to the given alias
          flag_false:     { device_condition: 'flag-false',
                            device_name:      :value
                         },
          flag_true:      { device_condition: 'flag-true',
                            device_name:      :value
                         },
          flag_false_any: { group_specifier: 'any-active',
                            group_condition: 'flag-false',
                            group_name:      :value
                             },
          flag_false_all: { group_specifier: 'all-active',
                            group_condition: 'flag-false',
                            group_name:      :value
                             },
          flag_true_any:  { group_specifier: 'any-active',
                            group_condition: 'flag-true',
                            group_name:      :value
                            },
          flag_true_all:  { group_specifier: 'all-active',
                            group_condition: 'flag-true',
                            group_name:      :value
                            },
          flag_clear:     { device_condition: 'flag-clear',
                            device_name:      :value
                         }
        }

        # Assign attribute defaults here, generally this should match whatever defaults
        # Teradyne has set whenever you create a new test instance, etc.
        DEFAULTS = {
          test:              {
            opcode: 'Test',
            result: 'Fail'
          },
          cz:                {
            opcode: 'characterize',
            result: 'None'
          },
          goto:              {
            opcode: 'goto'
          },
          nop:               {
            opcode: 'nop'
          },
          set_device:        {
            opcode: 'set-device'
          },
          set_error_bin:     {
            opcode: 'set-error-bin'
          },
          enable_flow_word:  {
            opcode: 'enable-flow-word'
          },
          disable_flow_word: {
            opcode: 'disable-flow-word'
          },
          logprint:          {
            opcode: 'logprint'
          },
          use_limit:         {
            opcode: 'Use-Limit',
            result: 'Fail'
          },
          flag_false:        {
            opcode: 'flag-false'
          },
          flag_false_all:    {
            opcode: 'flag-false-all'
          },
          flag_true:         {
            opcode: 'flag-true'
          },
          flag_true_all:     {
            opcode: 'flag-true-all'
          }
        }

        def self.define
          # Generate accessors for all attributes and their aliases
          self::TESTER_FLOWLINE_ATTRS.each do |attr|
            writer = "#{attr}=".to_sym
            reader = attr.to_sym
            attr_reader attr.to_sym unless method_defined? reader
            attr_writer attr.to_sym unless method_defined? writer
          end

          ALIASES.each do |_alias, val|
            if val.is_a? Hash
              if ((self::TESTER_FLOWLINE_ATTRS.map(&:to_sym)) & val.keys) == val.keys
                writer = "#{_alias}=".to_sym
                unless method_defined? writer
                  define_method("#{_alias}=") do |v|
                    val.each do |k, _v|
                      myval = _v == :value ? v : _v
                      send("#{k}=", myval)
                    end
                  end
                end
              end
            else

              if self::TESTER_FLOWLINE_ATTRS.include? "#{val}"
                writer = "#{_alias}=".to_sym
                reader = _alias.to_sym
                unless method_defined? writer
                  define_method("#{_alias}=") do |v|
                    send("#{val}=", v)
                  end
                end
                unless method_defined? reader
                  define_method("#{_alias}") do
                    send(val)
                  end
                end
              end
            end
          end
        end

        def initialize(type, attrs = {})
          @ignore_missing_instance = attrs.delete(:instance_not_available)
          self.cz_setup = attrs.delete(:cz_setup)
          @type = type
          # Set the defaults
          DEFAULTS[@type.to_sym].each do |k, v|
            send("#{k}=", v) if self.respond_to?("#{k}=")
          end
          # Then the values that have been supplied
          attrs.each do |k, v|
            send("#{k}=", v) if self.respond_to?("#{k}=")
          end
          # override test numbers if diff-friendly output desired
          if Origen.tester.diff_friendly_output?
            self.tnum = 0
          end
        end

        def parameter=(value)
          if (@type == :test || @test == :cz) && !@ignore_missing_instance
            if value.is_a?(String) || value.is_a?(Symbol)
              fail "You must supply the actual test instance object for #{value} when adding it to the flow"
            end
          end
          @parameter = value
        end

        def parameter
          # When referring to the test instance take the opportunity to refresh the current
          # version of the test instance
          @parameter = Origen.interface.identity_map.current_version_of(@parameter)
        end

        # Returns the fully formatted flow line for insertion into a flow sheet
        def to_s
          l = "\t"
          self.class::TESTER_FLOWLINE_ATTRS.each do |attr|
            if attr == 'parameter'
              ins = parameter
              if ins.respond_to?(:name)
                l += "#{ins.name}"
              else
                l += "#{ins}"
              end
              if type == :cz && cz_setup
                l += " #{cz_setup}\t"
              else
                l += "\t"
              end
            else
              l += "#{send(attr)}\t"
            end
          end
          "#{l}"
        end

        def job
          if !if_jobs.empty? && !unless_jobs.empty?
            fail "Both if and unless jobs have been defined for test: #{parameter}"
          elsif !if_jobs.empty?
            if_jobs.join(',')
          elsif !unless_jobs.empty?
            unless_jobs.map { |j| "!#{j}" }.join(',')
          else
            ''
          end
        end
        alias_method :jobs, :job

        def unless_enable=(*args)
        end
        alias_method :unless_enabled=, :unless_enable=

        def if_jobs
          @if_jobs ||= []
        end

        def unless_jobs
          @unless_jobs ||= []
        end

        def if_job=(jobs)
          [jobs].flatten.compact.each do |job|
            job = job.to_s.upcase
            if job =~ /!/
              self.unless_job = job
            else
              if_jobs << job unless if_jobs.include?(job)
            end
          end
        end
        alias_method :if_jobs=, :if_job=
        alias_method :add_if_jobs, :if_job=
        alias_method :add_if_job, :if_job=

        def unless_job=(jobs)
          [jobs].flatten.compact.each do |job|
            job = job.to_s.upcase
            job.gsub!('!', '')
            unless_jobs << job unless unless_jobs.include?(job)
          end
        end
        alias_method :unless_jobs=, :unless_job=
        alias_method :add_unless_jobs, :unless_job=
        alias_method :add_unless_job, :unless_job=

        def continue_on_fail
          self.result = 'None'
        end

        def continue_pass
          self.result = 'Pass'
        end

        def debug_assume_pass
          self.debug_assume = 'Pass'
        end

        def debug_assume_fail
          self.debug_assume = 'Fail'
        end

        #          def debug_sites
        #            self.debug_sites = "All"
        #          end

        def set_flag_on_fail
          self.flag_fail = "#{id}_FAILED"
        end

        def set_flag_on_pass
          self.flag_pass = "#{id}_PASSED"
        end

        def set_flag_on_ran
          self.flag_pass = "#{id}_RAN"
        end

        def run_if_any_passed(parent)
          parent.continue_on_fail
          self.flag_true_any = parent.set_flag_on_pass
        end

        def run_if_all_passed(parent)
          parent.continue_on_fail
          self.flag_true_all = parent.set_flag_on_pass
        end

        def run_if_any_failed(parent)
          parent.continue_on_fail
          self.flag_true_any = parent.set_flag_on_fail
        end

        def run_if_all_failed(parent)
          parent.continue_on_fail
          self.flag_true_all = parent.set_flag_on_fail
        end

        def run_if_all_(args)
          # code
        end

        def id
          @id || "#{parameter}_#{unique_counter}"
        end

        def unique_counter
          @unique_counter ||= self.class.unique_counter
        end

        def self.unique_counter
          @ix ||= -1
          @ix += 1
        end

        def test?
          @type == :test
        end
      end
    end
  end
end