# encoding: ascii-8bit

module Bitcoin
  module Protocol
    # TxIn section of https://en.bitcoin.it/wiki/Protocol_documentation#tx
    class TxIn
      # previous output hash
      attr_accessor :prev_out_hash
      alias prev_out prev_out_hash
      def prev_out=(hash)
        @prev_out_hash = hash
      end

      # previous output index
      attr_accessor :prev_out_index

      # script_sig input Script (signature)
      attr_reader :script_sig
      attr_accessor :script_sig_length

      # signature hash and the address of the key that needs to sign it
      # (used when dealing with unsigned or partly signed tx)
      attr_accessor :sig_hash, :sig_address

      # segregated witness
      attr_accessor :script_witness

      alias script script_sig
      alias script_length script_sig_length

      # sequence
      attr_accessor :sequence

      DEFAULT_SEQUENCE = "\xff\xff\xff\xff".freeze
      NULL_HASH = "\x00" * 32
      COINBASE_INDEX = 0xffffffff

      def initialize(*args)
        @prev_out_hash, @prev_out_index, @script_sig_length,
        @script_sig, @sequence = *args
        @script_sig_length ||= 0
        @script_sig ||= ''
        @sequence ||= DEFAULT_SEQUENCE
        @script_witness = ScriptWitness.new
      end

      # compare to another txout
      def ==(other)
        @prev_out_hash == other.prev_out_hash &&
          @prev_out_index == other.prev_out_index &&
          @script_sig == other.script_sig &&
          @sequence == other.sequence
      rescue StandardError
        false
      end

      def is_final? # rubocop:disable Naming/PredicateName
        warn '[DEPRECATION] `TxIn.is_final?` is deprecated. Use `final?` instead.'
        final?
      end

      # returns true if the sequence number is final (DEFAULT_SEQUENCE)
      def final?
        sequence == DEFAULT_SEQUENCE
      end

      # parse raw binary data for transaction input
      def parse_data(data)
        buf = data.is_a?(String) ? StringIO.new(data) : data
        parse_data_from_io(buf)
        buf.pos
      end

      def self.from_io(buf)
        txin = new
        txin.parse_data_from_io(buf)
        txin
      end

      def parse_data_from_io(buf)
        @prev_out_hash, @prev_out_index = buf.read(36).unpack('a32V')
        @script_sig_length = Protocol.unpack_var_int_from_io(buf)
        @script_sig = buf.read(@script_sig_length)
        @sequence = buf.read(4)
      end

      def parsed_script
        @parsed_script ||= Bitcoin::Script.new(script_sig)
      end

      def to_payload(script = @script_sig, sequence = @sequence)
        [@prev_out_hash, @prev_out_index].pack('a32V') << Protocol.pack_var_int(script.bytesize) \
                                                       << script << (sequence || DEFAULT_SEQUENCE)
      end

      def to_hash(_options = {})
        t = { 'prev_out' => { 'hash' => @prev_out_hash.reverse_hth, 'n' => @prev_out_index } }
        if coinbase?
          t['coinbase']  = @script_sig.unpack('H*')[0]
        else # coinbase tx
          t['scriptSig'] = Bitcoin::Script.new(@script_sig).to_string
        end
        t['sequence'] = @sequence.unpack('V')[0] unless @sequence == "\xff\xff\xff\xff"
        t['witness'] = @script_witness.stack.map(&:bth) unless @script_witness.empty?
        t
      end

      def self.from_hash(input)
        previous_hash         = input['previous_transaction_hash'] || input['prev_out']['hash']
        previous_output_index = input['output_index'] || input['prev_out']['n']
        txin = TxIn.new([previous_hash].pack('H*').reverse, previous_output_index)
        txin.script_sig = if input['coinbase']
                            [input['coinbase']].pack('H*')
                          else
                            Script.binary_from_string(input['scriptSig'] || input['script'])
                          end
        input['witness'].each { |w| txin.script_witness.stack << w.htb } if input['witness']
        txin.sequence = [input['sequence'] || 0xffffffff].pack('V')
        txin
      end

      def self.from_hex_hash(hash, index)
        TxIn.new([hash].pack('H*').reverse, index, 0)
      end

      # previous output in hex
      def previous_output
        @prev_out_hash.reverse_hth
      end

      # check if input is coinbase
      def coinbase?
        (@prev_out_index == COINBASE_INDEX) && (@prev_out_hash == NULL_HASH)
      end

      # set script_sig and script_sig_length
      def script_sig=(script_sig)
        @script_sig_length = script_sig.bytesize
        @script_sig = script_sig
      end
      alias script= script_sig=

      def add_signature_pubkey_script(sig, pubkey_hex)
        self.script = Bitcoin::Script.to_signature_pubkey_script(sig, [pubkey_hex].pack('H*'))
      end
    end
  end
end
