# encoding: ascii-8bit

# Copyright 2022 Ball Aerospace & Technologies Corp.
# All Rights Reserved.
#
# This program is free software; you can modify and/or redistribute it
# under the terms of the GNU Affero General Public License
# as published by the Free Software Foundation; version 3 with
# attribution addendums as found in the LICENSE.txt
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# Modified by OpenC3, Inc.
# All changes Copyright 2022, OpenC3, Inc.
# All Rights Reserved
#
# This file may also be used under the terms of a commercial license 
# if purchased from OpenC3, Inc.

require 'openc3/packets/packet'
require 'openc3/packets/structure'
require 'openc3/system/system'

module OpenC3
  class Packet < Structure
    attr_accessor :packet_rate
  end

  # Base class for all virtual OpenC3 targets which must be implemented by
  # a subclass. Provides a framework and helper methods to implement a
  # virtual target which can cycle telemetry values and emit telemetry
  # packets.
  class SimulatedTarget
    attr_accessor :tlm_packets

    def initialize(target_name)
      @tlm_packets = {}

      # Generate copy of telemetry packets for this target
      System.telemetry.packets(target_name).each do |name, packet|
        @tlm_packets[name] = packet.clone
        @tlm_packets[name].enable_method_missing
      end

      # Set defaults, template, and id values
      @tlm_packets.each do |name, packet|
        packet.restore_defaults
        ids = packet.id_items
        ids.each do |id|
          packet.public_send((id.name + '=').to_sym, id.id_value)
        end
      end

      @current_cycle_delta = {}
    end

    def set_rates
      raise "Error: set_rates must be implemented by subclass"
    end

    def write(packet)
      raise "Error: write must be implemented by subclass"
    end

    def read(count_100hz, time)
      get_pending_packets(count_100hz)
    end

    def tick_period_seconds
      return 0.01 # Override this method to optimize
    end

    def tick_increment
      return 1 # Override this method to optimize
    end

    protected

    def set_rate(packet_name, rate)
      packet = @tlm_packets[packet_name.upcase]
      packet.packet_rate = rate if packet
    end

    def get_pending_packets(count_100hz)
      pending_packets = []

      # Determine if packets are due to be sent and add to pending
      @tlm_packets.each do |name, packet|
        if packet.packet_rate
          if (count_100hz % packet.packet_rate) == 0
            pending_packets << packet
          end
        end
      end

      pending_packets
    end

    def cycle_tlm_item(packet, item_name, min, max, first_delta)
      packet_name = packet.packet_name
      @current_cycle_delta[packet_name] ||= {}
      @current_cycle_delta[packet_name][item_name] ||= first_delta

      current_delta = @current_cycle_delta[packet_name][item_name]
      current_value = packet.read(item_name)
      updated_value = current_value + current_delta
      if updated_value < min
        updated_value = min
        @current_cycle_delta[packet_name][item_name] = -current_delta
      elsif updated_value > max
        updated_value = max
        @current_cycle_delta[packet_name][item_name] = -current_delta
      end
      packet.write(item_name, updated_value)
      updated_value
    end
  end
end
