require 'thor'

module Spade
  module CLI
    class Base < Thor

      class_option :working, :required => false,
        :default => Spade.discover_root(Dir.pwd),
        :aliases => ['-w'],
        :desc    => 'Root working directory.'

      class_option :verbose, :type => :boolean, :default => false,
        :aliases => ['-V'],
        :desc => 'Show additional debug information while running'

      class_option :require, :type => :array, :required => false,
        :aliases => ['-r'],
        :desc => "optional JS files to require before invoking main command"

      map  "-i" => "console", "--interactive" => "console"
      desc "console", "Opens an interactive JavaScript console"
      def console
        require 'readline'

        shell = Spade::Shell.new

        context do |ctx|
          shell.inject(ctx)
          puts "help() for help. quit() to quit."
          puts "Spade #{Spade::VERSION} (V8 #{V8::VERSION})"
          puts "WORKING=#{options[:working]}" if options[:verbose]

          trap("SIGINT") { puts "^C" }
          repl ctx
        end
      end

      map  "-e" => "exec"
      desc "exec [FILENAME]", "Executes filename or stdin"
      def exec(*)
        exec_args = ARGV.dup
        arg = exec_args.shift while arg != "exec" && !exec_args.empty?

        filename = exec_args.shift
        puts "Filename: #{filename}" if options[:verbose]

        if filename
          puts "Working: #{options[:working]}" if options[:verbose]
          filename = File.expand_path filename, Dir.pwd
          throw "#{filename} not found" unless File.exists?(filename)
          fp      = File.open filename
          source  = File.basename filename
          rootdir = Spade.discover_root filename

          caller_id = nil
          if rootdir
            json_path = File.join(rootdir, 'package.json')
            if File.exist?(json_path)
              package_json = JSON.parse(File.read(json_path))
              caller_id = "#{package_json['name']}/main"
            end
          end

          # peek at first line.  If it is poundhash, skip. else rewind file
          unless fp.readline =~ /^\#\!/
            fp.rewind
          end

          # Can't set pos on STDIN so we can only do this for files
          if options[:verbose]
            pos = fp.pos
            puts fp.read
            fp.pos = pos
          end
        else
          fp = $stdin
          source = '<stdin>'
          rootdir = options[:working]
          caller_id = nil
        end

        if options[:verbose]
          puts "source: #{source}"
          puts "rootdir: #{rootdir}"
          puts "caller_id: #{caller_id}"
        end

        begin
          # allow for poundhash
          context(:argv => exec_args, :rootdir => rootdir, :caller_id => caller_id) do |ctx|
            ctx.eval(fp, source) # eval the rest
          end
        rescue Interrupt => e
          puts; exit
        end
      end

      map  "server" => "preview"
      desc "preview", "Starts a preview server for testing"
      long_desc %[
        The preview command starts a simple file server that can be used to
        load JavaScript-based apps in the browser.  This is a convenient way to
        run apps in the browser instead of having to setup Apache on your
        local machine.  If you are already loading apps through your own web
        server (for ex using Rails) the preview server is not required.
      ]
      method_option :port, :type => :string, :default => '4020',
        :aliases => ['-p'],
        :desc => 'Port number'
      def preview
        require 'spade/server'
        trap("SIGINT") { Spade::Server.shutdown }
        Spade::Server.run(options[:working], options[:port]);
      end

      desc "update", "Update package info in the current project"
      def update
        Spade::Bundle.update(options[:working], :verbose => options[:verbose])
      end

      private

        def repl(ctx)
          ctx.reactor.next_tick do
            line = Readline.readline("spade> ", true)
            unless line.chomp.empty?
              begin
                result = ctx.eval(line, '<console>')
                puts ctx['inspectjs'].call(result)
              rescue V8::JSError => e
                puts e.message
                puts e.backtrace(:javascript)
              rescue StandardError => e
                puts e
                puts e.backtrace.join("\n")
              end
            end
            repl(ctx)
          end
        end

        # Loads a JS file into the context.  This is not a require; just load
        def load(cxt, libfile)
          begin
            content = File.readlines(libfile)
            content.shift if content.first && (content.first =~ /^\#\!/)
            cxt.eval(content*'')
            #cxt.load(libfile)
          rescue V8::JSError => e
            puts e.message
            puts e.backtrace(:javascript)
          rescue StandardError => e
            puts e
          end
        end

        # Initialize a context to work against.  This will load also handle 
        # autorequires
        def context(opts={})
          opts[:rootdir] ||= options[:working]
          opts[:verbose] = options[:verbose]
          Spade::MainContext.new(opts) do |ctx|

            requires = opts[:require]
            requires.each { |r| load(ctx, r) } if requires

            yield(ctx) if block_given?
          end
        end

        def method_missing(meth, *)
          if File.exist?(meth.to_s)
            ARGV.unshift("exec")
            invoke :exec
          else
            super
          end
        end

    end
  end
end