#!/usr/bin/env ruby
require "fileutils"
require "yaml"

RDBMS = %w(MySQL SQLite3)
@src_top = File.dirname( File.expand_path(__FILE__) )

# PLUGINS TO BE INSTALLED AFTER RAILS-COMMAND
INST_PLUGINS = ["acts_as_tree", 
                "acts_as_list",
                "http://tools.assembla.com/svn/breakout/breakout/vendor/plugins/will_paginate"]

def setup_parameter
  print <<"EOF"
Set destination directory name and copy files.
gfdnavi will be installed to "/path_you_input/gfdnavi"
EOF
  dest_path = @params[:dest_top] ? File.dirname(@params[:dest_top]) : ENV["HOME"]
  # Win32 ##
  unless dest_path == ""
    if RUBY_PLATFORM =~ /mswin|mingw|bccwin/
      dest_path = File.join(ENV['RBPATH'],"..","..") 
    end
  end
  print "Please input destination path (default: #{dest_path}): "
  res = $stdin.gets.chomp!
  unless res == ""
    dest_path = res.sub(/\/$/,"")
  end
  ##########
  @params[:dest_top] = File.join(dest_path,"gfdnavi")

  @params[:skip_rdb] = false
  if File.exists?( File.join(dest_path, "gfdnavi", "config", "database.yml") )
    print "There already exists #{dest_path}/gfdnavi/config/database.yml.\n"
    print "Do you want to keep the settings? (yes/no, default:yes): "
    unless $stdin.gets.chomp! == "no"
      if !$load_inst_params
        install_params_file = File.join(dest_path,"gfdnavi","install_params.yml")
        if File.exists?( install_params_file )
          @params = YAML.load(File.read(install_params_file))
          $load_inst_params = true
        end
      end
      @params[:skip_rdb] = true
    end
  end
  if @params[:skip_rdb]
    print "skipped setting for database configurations\n"
  else
    print <<"EOF"

***
The database settings you will specify in the following will
be written in the file:

   #{dest_path}/gfdnavi/config/database.yml

YOU CAN MANUALLY EDIT IT LATER IF YOU LIKE.
EOF
    print <<"EOF"
***
Please specify the RDBMS you want to use.
EOF
    RDBMS.each_with_index{|name,i| print "#{i+1}. #{name}\n"}
    print "(Sorry, other RDBMSs are not supported in this installation script.)\n"
    while true
      rdb_type = @params[:rdb_type] || 2
      print "Select the RDBMS (default:#{rdb_type}): "
      type = $stdin.gets.chomp!
      rdb_type = type=="" ? rdb_type : type.to_i
      unless rdb_type >= 1 && rdb_type <= RDBMS.length
        print "Invalid number was input.\n"
      else
        break
      end
    end
    @params[:rdb_type] = rdb_type
    if RDBMS[rdb_type-1] == "MySQL"
      # CYGWIN PATCH BEGIN
      if /cygwin/ =~ RUBY_PLATFORM
        print <<"EOF"
 1. windows native MySQL
 2. MySQL compiled on cygwin
EOF
        while true
          rdb_plf = @params[:rdb_plf] || 1
          print "Select the RDBMS platform (default:#{rdb_plf}): "
          type = $stdin.gets.chomp!
          rdb_plf = type=="" ? rdb_plf : type.to_i
          unless [1,2].include?(rdb_plf)
            print "Invalid number\n"
          else
            break
          end
        end
        @params[:rdb_plf] = rdb_plf
      end
      # CYGWIN PATCH END
      print "Input the RDB user name for gfdnavi_* databases "
      print "(default: #{@params[:rdb_user]})" if @params[:rdb_user]
      print ": "
      rdb_user = $stdin.gets.chomp!
      @params[:rdb_user] = rdb_user unless rdb_user == ""
      print "Input the password for the databases: "
      @params[:rdb_password] = $stdin.gets.chomp!
    end
###
    # MySQL SETTINGS
    if RDBMS[rdb_type-1] == "MySQL"
      print <<"EOF"
You must finish settings of RDBMS before installing gfdnavi. 
If you have not, use another terminal now to set up
an RDBMS. If you would like to use mysql, I can show you how to do that.
EOF
      print "Would you like read the how-to for mysql? (yes/no, default:yes): "
      
      unless $stdin.gets.chomp! == "no"
        print <<"EOF"
***
>>>>> doc for mysql >>>>>
You can set mysql database as follows:

  % mysql -u root -p
  Enter password:
  -- type in password of root.
     (the user name specified with the -u option does not need to be root,
     but it must be a privileged one in order to create new databases.)
  mysql> create database gfdnavi_development;
  mysql> create database gfdnavi_test;
  mysql> create database gfdnavi_production;
  mysql> grant all on gfdnavi_development.* to 'davis'@'localhost' identified by 'hogehero';
  mysql> grant all on gfdnavi_test.* to 'davis'@'localhost' identified by 'hogehero';
  mysql> grant all on gfdnavi_production.* to 'davis'@'localhost' identified by 'hogehero';

Here, DO NOT forget to use your own password. DO NOT use 'hogehero'.
Note that the password will appear in the database.yml file
WITHOUT ENCRYPTION. So DO NOT USE YOUR LOGIN PASSWORD of any computer
system either.
<<<<< doc for mysql <<<<<

EOF
      end
    end
###
  end

  print "\n***\nGfdanvi configuration\n\n"
  @params[:skip_configure] = false
  if File.exists?( File.join(dest_path, "gfdnavi", "config", "gfdnavi.yml") )
    print "There already exists #{dest_path}/gfdnavi/config/gfdnavi.yml.\n"
    print "Do you want to keep the settings? (yes/no, default:yes): "
    unless $stdin.gets.chomp! == "no"
      @params[:skip_configure] = true
    end
  end
  if @params[:skip_configure]
    print "skipped setting for gfdnavi configurations\n"
  else
    print <<"EOF"

***
The gfdnavi settings you will specify in the following will
be written in the file:

   #{dest_path}/gfdnavi/config/gfdnavi.yml

YOU CAN MANUALLY EDIT IT LATER IF YOU LIKE.

EOF

    print "Set salt for encryption\n"
    while true
      print "input phrase (at least 5 characters): "
      salt = $stdin.gets.chomp!
      if salt.length < 5
        print "The phrase which you input is too short.\n"
      else
        break
      end
    end
    @params[:salt] = salt

    print "Set admin's email address\n"
    while true
      print "Input email address"
      print "(default: #{@params[:email]})" if @params[:email]
      print ": "
      email = $stdin.gets.chomp!
      email = @params[:email] if email == ""
      unless /^(\w|-)+@(\w|-)+\.(\w|-)+/ =~ email
        print "Invalid email address is input.\n"
      else
        break
      end
    end
    @params[:email] = email

    print <<"EOF"
Select server type to set default configuration parameters
The configurations in "config/gfdnavi.yml" will be set according to your choice of server type.
 1. private server
 2. open-access server (enable user accounts)
 3. open-access server (disable user accounts)
EOF
    while true
      print "Select the server type (default:1): "
      server_type = $stdin.gets.chomp!
      server_type = server_type=="" ? 1 : server_type.to_i
      unless [1,2,3].include?(server_type)
        print "Invalid number was input.\n"
      else
        break
      end
    end
    @params[:server_type] = server_type

    print <<"EOF"
Select Rails environment.
When you use Gfdnavi as an open-access server, production environment is strongly recommended.
If you are unsure, you should select development environment.
 1. development
 2. production
EOF
    while true
      print "Select environment (default:1): "
      rails_env = $stdin.gets.chomp!
      rails_env = rails_env=="" ? 1 : rails_env.to_i
      unless [1,2].include?(rails_env)
        print "Invalid number was input.\n"
      else
        break
      end
    end
    case rails_env
    when 1
      @params[:rails_env] = "development"
    when 2
      @params[:rails_env] = "production"
    end
  end
end

DIRS_TO_EXCLUDE = /#{@src_top}\/public\/data\/.+|#{@src_top}\/tmp\/.+|#{@src_top}\/public\/diagrams\/.+|#{@src_top}\/public\/temp_data\/.+/

def copy_files_in_dir(src, dest, sampledata=false)
  Dir.foreach(src) do |name|
    next if name == "." || name == ".."
    next if name == "install.rb"
    next if /~$/ =~ name
    dest_name = File.join(dest,name)
    src_name = File.join(src,name)
    if File.directory?(src_name) && !File.symlink?(src_name)
      next if !sampledata and (DIRS_TO_EXCLUDE =~ src_name)
      FileUtils.mkdir(dest_name) unless File.exist?(dest_name)
      copy_files_in_dir( src_name, dest_name, sampledata)
    else
      FileUtils.copy( src_name, dest_name ) if File.file?(src_name)
    end
  end
end
def copy_files(src, dest, sampledata=false)
  copy_files_in_dir(src, dest, sampledata)
end
def install_files
  dest_top = @params[:dest_top]
  if File.exists?( dest_top )
    unless File.directory?(dest_top)
      abort "#{dest_top} is not a directory."
    end
    # IF TARGET DIRECTORY ALREADY EXISTS
    print "#{dest_top} already exists.\n"
    while true
      print "Can I overwrite it? (yes/no): "
      case $stdin.gets.chomp! 
      when "yes"
        # OVERWRITE
        print "#{dest_top} will be overwritten.\n"
        break
      when "no"
        # KEEP CURRENT FILES
        print <<"EOF"
#{dest_top} will be kept as is.
Install process may fail
if you have not installed gfdnavi to the directory.
EOF
        return
      end
    end
    dest_existed = true
  else
    # CREATE TARGET DIRECTORY
    print "#{dest_top} does not exist.\n"
    print "Can I create the directories? (yes/no, default:yes): "
    unless $stdin.gets.chomp! == "no"
      FileUtils.makedirs(dest_top)
    else
      # CANCEL
      abort "operation was canceled."
    end
    dest_existed = false
  end
  Dir.chdir( File.join(@params[:dest_top],"..") )
  # CHECK IF RAILS-COMMAND WILL BE EXECUTED
  execute_rails = false
  if dest_existed && File.exists?( File.join(dest_top,'script/about') )
    print "Do you want execute \"rails\" command? (you seem to have already executed, but it's no problem to repeat it.)(no/yes, default:no): "
    if $stdin.gets.chomp! == "yes"
      execute_rails = true
    end
  else
    print "Do you want execute \"rails\" command? (yes/no, default:yes): "
    unless $stdin.gets.chomp! == "no"
      execute_rails = true
    end
  end
  unless execute_rails
    # IF NOT EXECUTING RAILS-COMMAND
    print <<"EOF"
Warning: You selected no.
         Make sure that you have already executed rails command,
         or execute rails later.
EOF
  else
    if @params[:skip_rdb]
      rails_option = "--skip"
    else
      rails_option = "--force"
    end
    # SET RAILS-COMMAND
    if RUBY_PLATFORM =~ /mswin|mingw|bccwin/
      # FOR WINDOWS
      rails_bin = "ruby " + File.join(ENV['RBPATH'],"rails")
      dev_null = "null"
    else
      # FOR UNIX-LIKE SYSTEMS
      rails_bin = "rails"
      dev_null = "/dev/null"
    end
    # EXECUTE RAILS-COMMAND
    railscommand = "#{rails_bin} gfdnavi #{rails_option} -d #{RDBMS[@params[:rdb_type]-1].downcase} > #{dev_null}"
    unless system(railscommand)
      abort "failed to execute \n  #{railscommand}\nat #{Dir.pwd}"
    end

    # INSTALL PLUGINS IF RAILS >= 2.0
    require "rubygems"
    begin
      if (gem "rails", ">=2.0")
        Dir.chdir("gfdnavi")
        # REMOTE PLUGINS
        skip_remote_plugin = false
        plugincommand = "ruby script/plugin install --force"
        INST_PLUGINS.each{|plgin| plugincommand << " " << plgin}
        # CHECK PROXY
        proxycommand = ""
        if http_proxy = (ENV["http_proxy"] || ENV["HTTP_PROXY"])
          print "use proxy for http connection\n"
          print "http_proxy = #{ENV["http_proxy"]}\n" if ENV["http_proxy"]
          print "HTTP_PROXY = #{ENV["HTTP_PROXY"]}\n" if ENV["HTTP_PROXY"]
        else
          print <<"EOF"

You need http connection to install remote plugins.
If you are not connected to internet,
please \"skip\" and try later.
Which do you choose? (direct / proxy / skip, default:direct):
EOF
          case $stdin.gets.chomp! 
          when "proxy"
            # SET HTTP PROXY
            print "enter proxy (http://your-proxy-sever:port):\n"
            http_proxy = $stdin.gets.chomp!
            ENV["http_proxy"] = http_proxy
          when "skip"
            # SKIP REMOTE PLUGINS
            print <<"EOF"
You need to execute following command at #{Dir.pwd} later.
  #{plugincommand}
EOF
            skip_remote_plugin = true
          end
        end
        unless skip_remote_plugin
          # INSTALL REMOTE PLUGINS
          print "install remote plugins...\n"
          unless system(plugincommand)
            print <<"EOF"
failed to execute 
  #{plugincommand}
with http_proxy = #{http_proxy}
at #{Dir.pwd}
Before you start the service, please try again.
EOF
          end
        end
      end
    rescue Gem::LoadError
      # NO NEED TO INSTALL PLUGINS IF RAILS 1.x
    end
  end
  copy_files( @src_top, dest_top )
  print "Do you want to install sample data? (yes/no, default:yes): "
  unless $stdin.gets.chomp! == "no"
    dest_sample_dir = File.join(dest_top,"public","data","samples")
    FileUtils.mkdir( dest_sample_dir ) unless File.exists?( dest_sample_dir )
    copy_files( File.join(@src_top,"public","data","samples"), dest_sample_dir , sampledata=true)
  end
end



def set_configure_files
  Dir.chdir( @params[:dest_top] )
  unless @params[:skip_rdb]
    database_file = File.join("config","database.yml")
    # RAILS COMMAND CREATES database.yml
    # THUS IT EXIST ALL THE TIME
    if File.exists?( database_file )
      FileUtils.move( database_file, database_file + ".bak" )
    end
    rdb_type = @params[:rdb_type]
    rdb_plf = @params[:rdb_plf]
    name = %w(mysql sqlite3)[rdb_type-1]
    File.open(database_file,"w"){|file|
      File.foreach( database_file + ".example" ){|line|
        if RDBMS[rdb_type-1] == "MySQL"
          if /^(\s*username:)/ =~ line
            line = "#$1 #{@params[:rdb_user]}\n"
          elsif /^(\s*password:)/ =~ line
            line = "#$1 #{@params[:rdb_password]}\n"
          end
          if RUBY_PLATFORM =~ /mswin|mingw|bccwin/
            # FOR WINDOWS
          else
            # FOR UNIX-LIKE PLATFORMS
            if rdb_plf == 1
              # FOR WINDOWS NATIVE MySQL WITH Cygwin Ruby
              if /^(\s*host:)/ =~ line
                # not Unix Socket but TCP/IP port
                line = "#$1 127.0.0.1\n"
              elsif /^(\s*socket:)/ =~ line
                # do nothing
              end
            else
              if /^(\s*socket:)/ =~ line
                # configuration for MySQL Unix Socket
                # search for MySQL Socket
                sockets = ["/tmp/mysql.sock", # default
                           "/var/lib/mysql/mysql.sock", # fedora, vine
                           "/var/run/mysqld/mysqld.sock" # debian
                          ]
                tmp_sockets = []
                sockets.each{|s|
                  tmp_sockets.push(s) if File.exist?(s)
                }
                if tmp_sockets > 1
                  raise "More than 1 MySQL sockets found!!!"
                elsif tmp_sockets == 1
                  line = "#$1 #{tmp_sockets[0]}"
                else
                  raise "MySQL socket not found!!!"
                end
              end
            end
          end
        elsif RDBMS[rdb_type-1] == "SQLite3"
          if /^(\s*adapter:)/ =~ line
            # CHANGE ADAPTER TO SQLite3
            line = "  adapter: sqlite3\n"
          elsif /^(\s*database:.*development)/ =~ line
            line = "  database: db/development.sqlite3"
          elsif /^(\s*database:.*test)/ =~ line
            line = "  database: db/test.sqlite3"
          elsif /^(\s*database:.*production)/ =~ line
            line = "  database: db/production.sqlite3"
          elsif /^(\s*username:)/ =~ line
            line = ""
          elsif /^(\s*password:)/ =~ line
            line = ""
          elsif /^(\s*host:)/ =~ line
            line = ""
          end
        end
        file.write line
      }
    }
  end

  unless @params[:skip_configure]
    config_file = File.join("config","gfdnavi.yml")
    if File.exists?( config_file )
      FileUtils.move( config_file, config_file + ".bak" )
    end
    File.open(config_file,"w") do |file|
      server_type = @params[:server_type]
      salt = @params[:salt]
      email = @params[:email]
      rails_env = @params[:rails_env]
      File.foreach( config_file + ".example" ){|line|
        if /^(passphrase:)/ =~ line
          line = "#$1 #{salt}\n"
        elsif /^(\s*email:) root@example\.org/ =~ line
          line = "#$1 #{email}\n"
        end
        if server_type == 1 # private
        elsif server_type > 1 # open
          if /^(disable_user:)/ =~ line
            if server_type == 3 # disable user accounts
              line = "#$1 true\n"
            end
          elsif /^(enable_email:)/ =~ line
            line = "#$1 true\n"
          elsif /^(:domain:) example\.org/ =~ line
            key = $1
            /[\w|-]*@(.+)/ =~ @email
            line = "#{key} #$1\n"
          end
        end
        if rails_env == "development"
        elsif rails_env == "production"
          if /^#(\s*RAILS_ENV:)/ =~ line
            line = "#$1 #{rails_env}\n"
          end
        end
        file.write line
      }
    end
  end

end

def execute_setup
  Dir.chdir(@params[:dest_top])
  begin
    if Variable.table_exists?
      print <<"EOF"
If you already have data in the gfdnavi database, skip this step,
and execute "rake update" manually if it is necessary.
EOF
      print "Do you skip? (no/yes, default:no): "
      if $stdin.gets.chomp! == "yes"
        print "skiped\n"
        return
      end
    end
  rescue
    #puts $!
  end
  # WHILE EXECUTION OF RAKE SETUP, WE NEED TO REPLY TO QUESTIONS
  # DO NOT OUTPUT TO /dev/null
  if RUBY_PLATFORM =~ /mswin|mingw|bccwin/
    rakecommand = "rake.bat setup"
  else
    rakecommand = "rake setup"
  end
  unless system(rakecommand)
    abort "failed to execute \"rake setup\" at #{Dir.pwd}\n"
  end
end


$load_inst_params = false
install_params_file = File.join(@src_top,"install_params.yml")
if File.exists?( install_params_file )
  @params = YAML.load(File.read(install_params_file))
  $load_inst_params = true
end
@params = Hash.new unless Hash === @params

print <<"EOF"
Welcome to Gfdnavi.
In order to install Gfdnavi,
there are the following steps.
Step 1: Setup parameters
Step 2: Execute rails and copy files"
Step 3: Create "database.yml" and "gfdnavi.yml"
Step 4: Execute "rake setup"
Step 5: Set root's password
press any key to continue
EOF
$stdin.gets

print "***************************************\nStep 1: Setup parameters\n"
setup_parameter
para = @params.dup
para.delete(:salt)
para.delete(:rdb_password)
File.open(install_params_file,"w"){|file|
  file.write para.to_yaml
}

print "***************************************\nStep 2: Execute rails and copy files\n"
install_files

print "***************************************\nStep 3: Create \"database.yml\" and \"gfdnavi.yml\"\n"
set_configure_files

print "***************************************\nStep 4: Execute rake setup\n"
require File.join(Dir.pwd, "config", "environment")
execute_setup

print <<"EOF"
Congraturations!
You sucessed to install gfdnavi to #{@params[:dest_top]}.
Before you start gfdnavi,
change the following configurations
 1. "config/database.yml"
 2. "config/gfdnavi.yml"
and check file pamissoin mode of their files (only webserver can read the files).
EOF
