require 'digest/sha1'
require 'fileutils'
require_dependency 'plugin/metadata'
require_dependency 'plugin/auth_provider'

class Plugin::Instance

  attr_reader :auth_providers, :assets, :styles, :color_schemes
  attr_accessor :path, :metadata

  def self.find_all(parent_path)
    [].tap { |plugins|
      # also follows symlinks - http://stackoverflow.com/q/357754
      Dir["#{parent_path}/**/*/**/plugin.rb"].each do |path|
        source = File.read(path)
        metadata = Plugin::Metadata.parse(source)
        plugins << self.new(metadata, path)
      end
    }
  end

  def initialize(metadata=nil, path=nil)
    @metadata = metadata
    @path = path
    @assets = []
    @color_schemes = []

    # Automatically include all ES6 JS files
    if @path
      dir = File.dirname(@path)
      Dir.glob("#{dir}/assets/javascripts/**/*.js.es6") do |f|
        relative = f.sub("#{dir}/assets/", "")
        register_asset(relative)
      end
    end

  end

  def name
    metadata.name
  end

  # will make sure all the assets this plugin needs are registered
  def generate_automatic_assets!
    paths = []
    automatic_assets.each do |path, contents|
      unless File.exists? path
        ensure_directory path
        File.open(path,"w") do |f|
          f.write(contents)
        end
      end
      paths << path
    end

    delete_extra_automatic_assets(paths)

    paths
  end

  def delete_extra_automatic_assets(good_paths)
    return unless Dir.exists? auto_generated_path

    filenames = good_paths.map{|f| File.basename(f)}
    # nuke old files
    Dir.foreach(auto_generated_path) do |p|
      next if [".", ".."].include?(p)
      next if filenames.include?(p)
      File.delete(auto_generated_path + "/#{p}")
    end
  end

  def ensure_directory(path)
    dirname = File.dirname(path)
    unless File.directory?(dirname)
      FileUtils.mkdir_p(dirname)
    end
  end

  def auto_generated_path
    File.dirname(path) << "/auto_generated"
  end

  def after_initialize(&block)
    @after_initialize ||= []
    @after_initialize << block
  end

  def notify_after_initialize
    color_schemes.each do |c|
      ColorScheme.create_from_base(name: c[:name], colors: c[:colors]) unless ColorScheme.where(name: c[:name]).exists?
    end

    if @after_initialize
      @after_initialize.each do |callback|
        callback.call
      end
    end
  end

  def register_css(style)
    @styles ||= []
    @styles << style
  end

  def register_javascript(js)
    @javascripts ||= []
    @javascripts << js
  end

  def register_custom_html(hash)
    DiscoursePluginRegistry.custom_html ||= {}
    DiscoursePluginRegistry.custom_html.merge!(hash)
  end

  def register_asset(file, opts=nil)
    full_path = File.dirname(path) << "/assets/" << file
    assets << [full_path, opts]
  end

  def register_color_scheme(name, colors)
    color_schemes << {name: name, colors: colors}
   end

  def automatic_assets
    css = ""
    js = ""

    css = @styles.join("\n") if @styles
    js = @javascripts.join("\n") if @javascripts

    unless auth_providers.blank?
      auth_providers.each do |auth|
        overrides = ""
        overrides = ", titleOverride: '#{auth.title}'" if auth.title
        overrides << ", messageOverride: '#{auth.message}'" if auth.message
        overrides << ", frameWidth: '#{auth.frame_width}'" if auth.frame_width
        overrides << ", frameHeight: '#{auth.frame_height}'" if auth.frame_height

        js << "Discourse.LoginMethod.register(Discourse.LoginMethod.create({name: '#{auth.name}'#{overrides}}));\n"

        if auth.glyph
          css << ".btn-social.#{auth.name}:before{ content: '#{auth.glyph}'; }\n"
        end

        if auth.background_color
          css << ".btn-social.#{auth.name}{ background: #{auth.background_color}; }\n"
        end
      end
    end

    # Generate an IIFE for the JS
    js = "(function(){#{js}})();" if js.present?

    result = []
    result << [css, 'css'] if css.present?
    result << [js, 'js'] if js.present?

    result.map do |asset, extension|
      hash = Digest::SHA1.hexdigest asset
      ["#{auto_generated_path}/plugin_#{hash}.#{extension}", asset]
    end

  end


  # note, we need to be able to parse seperately to activation.
  # this allows us to present information about a plugin in the UI
  # prior to activations
  def activate!
    self.instance_eval File.read(path), path
    if auto_assets = generate_automatic_assets!
      assets.concat auto_assets.map{|a| [a]}
    end
    unless assets.blank?
      register_assets!
      # TODO possibly amend this to a rails engine
      Rails.configuration.assets.paths << auto_generated_path
      Rails.configuration.assets.paths << File.dirname(path) + "/assets"
    end


    public_data = File.dirname(path) + "/public"
    if Dir.exists?(public_data)
      target = Rails.root.to_s + "/public/plugins/"
      `mkdir -p #{target}`
      target << name.gsub(/\s/,"_")
      # TODO a cleaner way of registering and unregistering
      `rm -f #{target}`
      `ln -s #{public_data} #{target}`
    end
  end


  def auth_provider(opts)
    @auth_providers ||= []
    provider = Plugin::AuthProvider.new
    [:glyph, :background_color, :title, :message, :frame_width, :frame_height, :authenticator].each do |sym|
      provider.send "#{sym}=", opts.delete(sym)
    end
    @auth_providers << provider
  end


  # shotgun approach to gem loading, in future we need to hack bundler
  #  to at least determine dependencies do not clash before loading
  #
  # Additionally we want to support multiple ruby versions correctly and so on
  #
  # This is a very rough initial implementation
  def gem(name, version, opts = {})
    gems_path = File.dirname(path) + "/gems/#{RUBY_VERSION}"
    spec_path = gems_path + "/specifications"
    spec_file = spec_path + "/#{name}-#{version}.gemspec"
    unless File.exists? spec_file
      command = "gem install #{name} -v #{version} -i #{gems_path} --no-document --ignore-dependencies"
      if opts[:source]
        command << " --source #{opts[:source]}"
      end
      puts command
      puts `#{command}`
    end
    if File.exists? spec_file
      spec = Gem::Specification.load spec_file
      spec.activate
      unless opts[:require] == false
        require opts[:require_name] ? opts[:require_name] : name
      end
    else
      puts "You are specifying the gem #{name} in #{path}, however it does not exist!"
      exit(-1)
    end
  end

  protected

  def register_assets!
    assets.each do |asset, opts|
      if asset =~ /\.js$|\.js\.erb$|\.js\.es6$/
        if opts == :admin
          DiscoursePluginRegistry.admin_javascripts << asset
        else
          if opts == :server_side
            DiscoursePluginRegistry.server_side_javascripts << asset
          end
          DiscoursePluginRegistry.javascripts << asset
        end
      elsif asset =~ /\.css$|\.scss$/
        if opts == :mobile
          DiscoursePluginRegistry.mobile_stylesheets << asset
        elsif opts == :desktop
          DiscoursePluginRegistry.desktop_stylesheets << asset
        elsif opts == :variables
          DiscoursePluginRegistry.sass_variables << asset
        else
          DiscoursePluginRegistry.stylesheets << asset
        end

      elsif asset =~ /\.js\.handlebars$/
        DiscoursePluginRegistry.handlebars << asset
      end
    end
  end

end