module Tmux
  # A session is a single collection of pseudo terminals under the
  # management of {Tmux tmux}. Each session has one or more {Window
  # windows} linked to it. A {Window window} occupies the entire
  # screen and may be split into rectangular {Pane panes}, each of
  # which is a separate pseudo terminal (the pty(4) manual page
  # documents the technical details of pseudo terminals). Any number
  # of tmux instances may connect to the same session, and any number
  # of {Window windows} may be present in the same session. Once all
  # sessions are {Session#kill killed}, tmux exits.
  class Session
    include Comparable

    # @return [Options]
    def self.options(session)
      OptionsList.new(:session, session, true)
    end

    # Creates a new {Window window}.
    #
    # @option args [Boolean] :after_number (false) If true, the new
    #   {Window window} will be inserted at the next index up from the
    #   specified number (or the {Client#current_window current}
    #   {Window window}), moving {Window windows} up if necessary
    # @option args [Boolean] :kill_existing (false) Kill an existing
    #   {Window window} if it conflicts with a desired number
    # @option args [Boolean] :make_active (true) Switch to the newly
    #   generated {Window window}
    # @option args [String] :name Name of the new {Window window}
    #   (optional)
    # @option args [Number] :number Number of the new {Window window}
    #   (optional)
    # @option args [String] :command Command to run in the new {Window
    #   window} (optional)
    #
    # @tmux new-window
    # @return [Window] The newly created {Window window}
    def create_window(args = {})
      args = {
        :kill_existing => false,
        :make_active   => true,
        :after_number  => false,
      }.merge(args)

      flags = []
      # flags << "-d" unless args[:make_active]
      flags << "-a" if args[:after_number]
      flags << "-k" if args[:kill_existing]
      flags << "-n '#{args[:name]}'" if args[:name] # FIXME escaping
      flags << if args[:number]
                 "-t #{identifier}:#{args[:number]}"
               else
                 "-t #{identifier}"
               end
      flags << args[:command] if args[:command]

      @server.invoke_command "new-window #{flags.join(" ")}"
      new_window = current_window
      unless args[:make_active]
        select_last_window
      end
      # return Window.new(self, num)
      return new_window
    end


    # @!attribute [r] current_window
    #
    # @see Client#current_window
    # @return (see Client#current_window)
    def current_window
      # TODO figure out when tmux added "(active)" in list-windows and
      # use that when possible
      any_client.current_window
    end

    # @!attribute [r] current_pane
    #
    # @see Client#current_pane
    # @return (see Client#current_pane)
    def current_pane
      any_client.current_pane
    end

    # Returns a {Client client} that is displaying the session.
    #
    # @return [Client, nil] A {Client client} that is displaying the session.
    def any_client
      @server.clients({:session => self}).first
    end

    # @return [Boolean]
    def ==(other)
      self.class == other.class && @server == other.server && @name == other.name
    end

    # @return [Number]
    def hash
      [@server.hash, @number].hash
    end

    # @return [Boolean]
    def eql?(other)
      self == other
    end

    def <=>(other)
      return nil unless other.is_a?(Session)
      [@server, @name] <=> [other.server, other.name]
    end

    # The session name.
    #
    # Setting this will rename the session.
    #
    # @todo escape name
    # @tmux rename-session
    # @return [String]
    attr_reader :name

    # @return [Server]
    attr_reader :server

    # @return [OptionsList]
    attr_reader :options

    # @return [StatusBar]
    attr_reader :status_bar
    def initialize(server, name)
      @server, @name = server, name
      @status_bar = StatusBar.new(self)
      @options = OptionsList.new(:session, self, false)
    end

    # @!attribute name
    #
    # @return [String]
    def name=(new_name)
      raise ArgumentError if new_name.to_s.strip.empty?
      ret = @server.invoke_command("rename-session -t #{identifier} '#{new_name}'")

      if ret.start_with?("duplicate session:")
        raise RuntimeError, ret
      end

      @name = new_name
    end

    # @!attribute [r] identifier
    #
    # @return [String]
    def identifier
      @name
    end

    # Locks the session.
    #
    # @tmux lock-session
    # @return [void]
    # @tmuxver &gt;=1.1
    def lock
      @server.check_for_version!("1.1")

      @server.invoke_command "lock-session -t #{identifier}"
    end

    # @!attribute [r] num_windows
    #
    # @return [Integer]
    def num_windows
      @server.sessions_information[@name][:num_windows]
    end

    # @!attribute [r] creation_time
    #
    # @return [Time]
    def creation_time
      @server.sessions_information[@name][:creation_time]
    end
    alias_method :created_at, :creation_time

    # @!attribute [r] width
    #
    # @return [Integer]
    def width
      @server.sessions_information[@name][:width]
    end

    # @!attribute [r] height
    #
    # @return [Integer]
    def height
      @server.sessions_information[@name][:height]
    end

    # @!attribute [r] attached
    #
    # @return [Boolean]
    def attached
      @server.sessions_information[@name][:attached]
    end
    alias_method :attached?, :attached

    # @!attribute [r] clients
    #
    # @return [Array<Client>] All {Client clients}
    def clients
      @server.clients({:session => self})
    end

    # Attach to a session. Replaces the ruby process.
    #
    # @return [void]
    # @tmux attach
    def attach
      exec "#{Tmux::BINARY} attach -t #{identifier}"
    end

    # Kills the session.
    #
    # @tmux kill-session
    # @return [void]
    def kill
      @server.invoke_command "kill-session -t #{identifier}"
    end

    # @tmux list-windows
    # @tmuxver &gt;=1.1
    # @param [Hash] search Filters the resulting hash using {FilterableHash#filter}
    # @return [Hash] A hash with information for all windows
    # @return [Hash]
    def windows_information(search = {})
      @server.check_for_version!("1.1")

      hash = {}
      output = @server.invoke_command "list-windows -t #{identifier}"
      output.each_line do |session|
        # TODO make use of the layout information
        # TODO make use of pane count value in later tmux versions
        params = session.match(/^(?<num>\d+): (?<name>.*)( \(\d+ panes\))? \[(?<width>\d+)x(?<height>\d+)\](?: \[layout .+?\])?( @\d+)?(?<active> \(active\))?$/)
        next if params.nil? # >=1.3 displays layout information in indented lines
        num    = params[:num].to_i
        name   = params[:name]
        width  = params[:width].to_i
        height = params[:height].to_i
        active = !params[:active].nil?

        hash[num] = {:num => num, :name => name, :width => width, :height => height, :active => active}
      end
      hash.extend FilterableHash
      hash.filter(search)
    end

    # @!attribute [r] windows
    #
    # @tmux list-windows
    # @return [Hash{Number => Window}] All {Window windows}
    # @tmuxver &gt;=1.1
    def windows
      hash = {}
      @server.check_for_version!("1.1")

      windows_information.each do |num, information|
        hash[num] = Window.new(self, num)
      end
      hash
    end

    # @param [Hash] search Filters the resulting hash using {FilterableHash#filter}
    # @return [Hash] A hash with information for all buffers
    # @tmux list-buffers
    def buffers_information(search = {})
      hash = {}
      if @server.version < "1.5"
        buffers = @server.invoke_command "list-buffers -t #{identifier}"
      else
        buffers = @server.invoke_command "list-buffers"
      end

      buffers.each_line do |buffer|
        num, size = buffer.match(/^(\d+): (\d+) bytes/)[1..2]
        hash[num] = {:size => size}
      end
      hash.extend FilterableHash
      hash.filter(search)
    end

    # @!attribute [r] buffers
    #
    # @tmux list-buffers
    # @return [Array<Buffer>] All {Buffer buffers}
    # @note Beginning with tmux 1.5, {Buffer buffers} are global and
    #   not tied to a session anymore. That means that for 1.5 and
    #   higher, this method will return all buffers of the server,
    #   making it identical to {Server#buffers}.
    def buffers
      buffers_information.map do |num, information|
        Buffer.new(num, self)
      end
    end

    # @group Selecting

    # Select the last (previously selected) window.
    #
    # @return [Window]
    def select_last_window
      @server.invoke_command "last-window -t #{identifier}"
      current_window
    end

    # Selects the next (higher index) window
    #
    # @param [Number] num How many windows to move
    # @tmuxver &gt;=1.3
    # @return [Window]
    def select_next_window(num = 1)
      @server.invoke_command "select-window -t #{identifier}:+#{num}"
      current_window
    end

    # Selects the previous (lower index) window
    #
    # @param [Number] num How many windows to move
    # @tmuxver &gt;=1.3
    # @return [Window]
    def select_previous_window(num = 1)
      @server.invoke_command "select-window -t:-#{num}"
      current_window
    end

    # @endgroup
  end
end
