require "bundler"
require "gemnasium/parser/patterns"

module Gemnasium
  module Parser
    class Gemfile
      attr_reader :content

      def initialize(content)
        @content = content
      end

      def dependencies
        @dependencies ||= [].tap do |deps|
          gem_matches.each do |match|
            dep = dependency(match)
            deps << dep if dep
          end
        end
      end

      def gemspec
        @gemspec = if gemspec_match
          opts = Patterns.options(gemspec_match["opts"])
          path = opts["path"]
          name = opts["name"] || "*"
          File.join(*[path, "#{name}.gemspec"].compact)
        end
      end

      def gemspec?
        !!gemspec
      end

      private
        def gem_matches
          @gem_matches ||= matches(Patterns::GEM_CALL)
        end

        def matches(pattern)
          [].tap{|m| content.scan(pattern){ m << Regexp.last_match } }
        end

        def dependency(match)
          opts = Patterns.options(match["opts"])
          return nil if exclude?(match, opts)
          clean!(match, opts)
          name, reqs = match["name"], [match["req1"], match["req2"]].compact
          Bundler::Dependency.new(name, reqs, opts).tap do |dep|
            line = content.slice(0, match.begin(0)).count("\n") + 1
            dep.instance_variable_set(:@line, line)
          end
        end

        def groups(match)
          group = group_matches.detect{|m| in_block?(match, m) }
          group && Patterns.values(group[:grps])
        end

        def in_block?(inner, outer)
          outer.begin(:blk) <= inner.begin(0) && outer.end(:blk) >= inner.end(0)
        end

        def group_matches
          @group_matches ||= matches(Patterns::GROUP_CALL)
        end

        def exclude?(match, opts)
          git?(match, opts) || github?(match, opts) || path?(match, opts)
        end

        def git?(match, opts)
          opts["git"] || in_git_block?(match)
        end

        def github?(match, opts)
          opts["github"]
        end

        def in_git_block?(match)
          git_matches.any?{|m| in_block?(match, m) }
        end

        def git_matches
          @git_matches ||= matches(Patterns::GIT_CALL)
        end

        def path?(match, opts)
          opts["path"] || in_path_block?(match)
        end

        def in_path_block?(match)
          path_matches.any?{|m| in_block?(match, m) }
        end

        def path_matches
          @path_matches ||= matches(Patterns::PATH_CALL)
        end

        def clean!(match, opts)
          opts["group"] ||= opts.delete("groups")
          opts["group"] ||= groups(match)
          groups = Array(opts["group"]).flatten.compact
          runtime = groups.empty? || !(groups & Parser.runtime_groups).empty?
          opts["type"] ||= runtime ? :runtime : :development
        end

        def gemspec_match
          return @gemspec_match if defined?(@gemspec_match)
          @gemspec_match = content.match(Patterns::GEMSPEC_CALL)
        end
    end
  end
end
