require 'iniparse'

module Invoker
  module Parsers
    class Config
      PORT_REGEX = /\$PORT/

      attr_accessor :processes, :power_config
      attr_reader :filename

      # initialize takes a port form cli and decrements it by 1 and sets the
      # instance variable @port. This port value is used as the environment
      # variable $PORT mentioned inside invoker.ini. When method pick_port gets
      # fired it increments the value of port by 1, subsequently when pick_port
      # again gets fired, for another command, it will again increment port
      # value by 1, that way generating different ports for different commands.
      def initialize(filename, port)
        @filename = filename || autodetect_config_file
        print_message_and_abort if invalid_config_file?

        @port = port - 1
        @processes = load_config
        if Invoker.can_run_balancer?
          @power_config = Invoker::Power::Config.load_config()
        end
      end

      def http_port
        power_config && power_config.http_port
      end

      def dns_port
        power_config && power_config.dns_port
      end

      def https_port
        power_config && power_config.https_port
      end

      def tld
        power_config && power_config.tld
      end

      def autorunnable_processes
        process_to_run = processes.reject(&:disable_autorun)
        process_to_run.sort_by { |process| process.index }
      end

      def process(label)
        processes.detect { |pconfig| pconfig.label == label }
      end

      private

      def autodetect_config_file
        Dir.glob("{invoker.ini,Procfile.dev,Procfile}").first
      end

      def invalid_config_file?
        @filename.nil?
      end

      def load_config
        @filename = to_global_file if is_global?

        if is_ini?
          process_ini
        elsif is_procfile?
          process_procfile
        else
          print_message_and_abort
        end
      end

      def process_ini
        ini_content = File.read(@filename)
        document = IniParse.parse(ini_content)
        document.map do |section|
          check_directory(section["directory"])
          process_command_from_section(section)
        end
      end

      def process_procfile
        procfile = Invoker::Parsers::Procfile.new(@filename)
        procfile.entries.map do |name, command|
          section = { "label" => name, "command" => command }
          process_command_from_section(section)
        end
      end

      def print_message_and_abort
        Invoker::Logger.puts("\n Invalid config file. Invoker requires an ini or a Procfile.".colorize(:red))
        abort
      end

      def process_command_from_section(section)
        if supports_subdomain?(section)
          port = pick_port(section)
          if port
            command = replace_port_in_command(section['command'], port)
            section['port'], section['command'] = port, command
          end
        end

        make_pconfig(section)
      end

      def pick_port(section)
        if section['command'] =~ PORT_REGEX
          @port += 1
        elsif section['port']
          section['port']
        else
          nil
        end
      end

      def make_pconfig(section)
        pconfig = {
          label: section["label"] || section.key,
          dir: expand_directory(section["directory"]),
          cmd: section["command"]
        }
        pconfig['port'] = section['port'] if section['port']
        pconfig['disable_autorun'] = section['disable_autorun'] if section['disable_autorun']
        pconfig['index'] = section['index'].to_i if section['index']
        section_index = pconfig['index'].to_i
        if section_index
          pconfig['index'] = section_index
        else
          pconfig['index'] = 0
        end

        sleep_duration = section['sleep'].to_i
        if sleep_duration >= 0
          pconfig['sleep_duration'] = sleep_duration
        else
          pconfig['sleep_duration'] = 0
        end

        OpenStruct.new(pconfig)
      end

      def supports_subdomain?(section)
        (section['command'] =~ PORT_REGEX) || section['port']
      end

      def check_directory(app_dir)
        if app_dir && !app_dir.empty? && !File.directory?(expand_directory(app_dir))
          raise Invoker::Errors::InvalidConfig.new("Invalid directory #{app_dir}")
        end
      end

      def expand_directory(app_dir)
        File.expand_path(app_dir) if app_dir
      end

      def replace_port_in_command(command, port)
        if command =~ PORT_REGEX
          command.gsub(PORT_REGEX, port.to_s)
        else
          command
        end
      end

      def is_ini?
        File.extname(@filename) == '.ini'
      end

      def is_procfile?
        @filename =~ /Procfile/
      end

      def to_global_file
        File.join(Invoker::Power::Config.config_dir, "#{@filename}.ini")
      end

      def is_global?
        @filename =~ /^\w+$/ && File.exist?(to_global_file)
      end
    end
  end
end
