require 'strscan'
require_relative 'outputs'

module Oxidized
  class Model
    include Oxidized::Config::Vars

    class << self
      def inherited klass
        klass.instance_variable_set '@cmd',   Hash.new { |h,k| h[k] = [] }
        klass.instance_variable_set '@cfg',   Hash.new { |h,k| h[k] = [] }
        klass.instance_variable_set '@procs', Hash.new { |h,k| h[k] = [] }
        klass.instance_variable_set '@expect', []
        klass.const_set :CFG, CFG
      end
      def comment _comment='# '
        return @comment if @comment
        @comment = block_given? ? yield : _comment
      end
      def prompt _prompt=nil
        @prompt or @prompt = _prompt
      end
      def cfg *methods, &block
        [methods].flatten.each do |method|
          @cfg[method.to_s] << block
        end
      end
      def cfgs
        @cfg
      end
      def cmd _cmd=nil, &block
        if _cmd.class == Symbol
          @cmd[_cmd] << block
        else
          @cmd[:cmd] << [_cmd, block]
        end
      end
      def cmds
        @cmd
      end
      def expect re, &block
        @expect << [re, block]
      end
      def expects
        @expect
      end

      # calls the block at the end of the model, prepending the output of the
      # block to the output string
      #
      # @author Saku Ytti <saku@ytti.fi>
      # @since 0.0.39
      # @yield expects block which should return [String]
      # @return [void]
      def pre &block
        @procs[:pre] << block
      end

      # calls the block at the end of the model, adding the output of the block
      # to the output string
      #
      # @author Saku Ytti <saku@ytti.fi>
      # @since 0.0.39
      # @yield expects block which should return [String]
      # @return [void]
      def post &block
        @procs[:post] << block
      end

      # @author Saku Ytti <saku@ytti.fi>
      # @since 0.0.39
      # @return [Hash] hash proc procs :pre+:post to be prepended/postfixed to output
      def procs
        @procs
      end
    end

    attr_accessor :input, :node

    def cmd string, &block
      out = @input.cmd string
      return false unless out
      self.class.cmds[:all].each do |all_block|
        out = instance_exec Oxidized::String.new(out), string, &all_block
      end
      if vars :remove_secret
        self.class.cmds[:secret].each do |all_block|
          out = instance_exec Oxidized::String.new(out), string, &all_block
        end
      end
      out = instance_exec Oxidized::String.new(out), &block if block
      process_cmd_output out, string
    end

    def output
      @input.output
    end

    def send data
      @input.send data
    end

    def expect re, &block
      self.class.expect re, &block
    end

    def cfg
      self.class.cfgs
    end

    def prompt
      self.class.prompt
    end

    def expects data
      self.class.expects.each do |re, cb|
        if data.match re
          if cb.arity == 2
            data = instance_exec [data, re], &cb
          else
            data = instance_exec data, &cb
          end
        end
      end
      data
    end

    def get
      outputs = Outputs.new
      procs = self.class.procs
      self.class.cmds[:cmd].each do |command, block|
        out = cmd command, &block
        return false unless out
        outputs << out
      end
      procs[:pre].each do |pre_proc|
        outputs.unshift process_cmd_output(instance_eval(&pre_proc), '')
      end
      procs[:post].each do |post_proc|
        outputs << process_cmd_output(instance_eval(&post_proc), '')
      end
      outputs
    end

    def comment _comment
      data = ''
      _comment.each_line do |line|
        data << self.class.comment << line
      end
      data
    end

    private

    def process_cmd_output output, name
      output = Oxidized::String.new output if ::String === output
      output = Oxidized::String.new '' unless Oxidized::String === output
      output.set_cmd(name)
      output
    end

  end
end