#!/usr/bin/env ruby
# Copyright (c) 2009, Keith Salisbury (www.globalkeith.com)
# All rights reserved.
# 
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
# 
# Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 
# Neither the name of the original author nor the names of contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
# 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

require 'yaml'
require 'erb'
require 'fileutils'
require 'subtrac/version'
require 'subtrac/commands'
require 'subtrac/client'
require 'subtrac/project'
require 'subtrac/apache'
require 'subtrac/svn'
require 'subtrac/trac'
require 'subtrac/core_ext'

module Subtrac

  SUBTRAC_ROOT = "#{File.dirname(__FILE__)}/" unless defined?(SUBTRAC_ROOT)
  SUBTRAC_ENV = (ENV['SUBTRAC_ENV'] || 'development').dup unless defined?(SUBTRAC_ENV)
  USER_CONFIG_FILE = 'config/user.yml'

  class << self

    # User configured options
    def server_name
      @server_name ||= @APP_CONFIG[:server_name]
    end

    def server_name=(name)
      @APP_CONFIG[:server_name] = @server_name = name
    end

    def server_hostname
      @server_hostname ||= @APP_CONFIG[:server_hostname]
    end

    def server_hostname=(name)
      @APP_CONFIG[:server_hostname] = @server_hostname = name
    end

    def default_client
      @default_client ||= @APP_CONFIG[:default_client]
    end

    def default_client=(name)
      @APP_CONFIG[:default_client] = @default_client = name
    end

    def default_project
      @default_project ||= @APP_CONFIG[:default_project]
    end

    def default_project=(name)
      puts "Updating default_project to #{name}"
      @APP_CONFIG[:default_project] = @default_project = name
    end

    def default_project_type
      @default_project_type ||= @APP_CONFIG[:default_project_type]
    end

    def default_project_type=(name)
      puts "Updating default_project_type to #{name}"
      @APP_CONFIG[:default_project_type] = @default_project_type = name
    end
    

    # Filesystem directories
    def root
      Pathname.new(SUBTRAC_ROOT) if defined?(SUBTRAC_ROOT)
    end

    def public_path
      @public_path ||= self.root ? File.join(self.root, "public") : "public"
    end
    
    def public_path=(path)
      @public_path = path
    end

    def subtrac_path
      @subtrac_path ||= self.root ? File.join(self.root, "subtrac") : "subtrac"
    end

    def install_dir
      @install_dir ||= File.expand_path(@APP_CONFIG[:installation_dir])
    end

    def install_dir=(name)
      puts "Setting new install_dir variable to #{name}"
      @APP_CONFIG[:installation_dir] = @install_dir = name
    end
    
    def apache_conf_dir
      @apache_conf_dir ||= @APP_CONFIG[:apache_conf_dir]
    end

    def apache_conf_dir=(name)
      @APP_CONFIG[:apache_conf_dir] = @apache_conf_dir = name
    end

    def docs_dir
      @docs_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:docs])
    end

    def svn_dir
      @svn_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:svn])
    end

    def trac_dir
      @trac_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:trac])
    end

    def temp_dir
      @temp_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:temp])
    end

    def log_dir
      @log_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:log])
    end

    def locations_dir
      @locations_dir ||= File.join(install_dir,@APP_CONFIG[:dirs][:locations])
    end
    
    # Urls
    def svn_url
      @svn_url ||= @APP_CONFIG[:urls][:svn]
    end
    
    def trac_permissions
      @trac_permissions ||= @APP_CONFIG[:trac][:permissions]
    end
    
    # LDAP
    def enable_ldap
      @enable_ldap ||= @APP_CONFIG[:ldap][:enable]
    end

    def enable_ldap=(value)
      @APP_CONFIG[:ldap][:enable] = @enable_ldap = value
    end

    def ldap_bind_dn
      @ldap_bind_dn ||= @APP_CONFIG[:ldap][:bind_dn]
    end

    def ldap_bind_dn=(value)
      @APP_CONFIG[:ldap][:bind_dn] = @ldap_bind_dn = value
    end
    
    def ldap_bind_password
      @ldap_bind_password ||= @APP_CONFIG[:ldap][:bind_password]
    end

    def ldap_bind_password=(value)
      @APP_CONFIG[:ldap][:bind_password] = @ldap_bind_password = value
    end
    
    def ldap_bind_host
      @ldap_bind_host ||= @APP_CONFIG[:ldap][:bind_host]
    end

    def ldap_bind_host=(value)
      @APP_CONFIG[:ldap][:bind_host] = @ldap_bind_password = value
    end
    
  end

  # Loads the configuration YML file
  def self.load_config
    # TODO: We need to refactor this code so it will load the default configuration only if one has not been created
    puts "\n==== Loading configuration file ===="
    # load configuration file
    file_path = File.join(subtrac_path, USER_CONFIG_FILE)
    file_path = File.join(subtrac_path,"/config/config.yml") if not File.exists?(file_path)
    puts "Attempting to read config file: #{file_path}"
    begin
      yamlFile = YAML.load(File.read(file_path))
    rescue Exception => e
      raise StandardError, "Config #{file_path} could not be loaded."
    end
    if yamlFile
      if yamlFile[SUBTRAC_ENV]
        @APP_CONFIG = yamlFile[SUBTRAC_ENV]
      else
        raise StandardError, "config/config.yml exists, but doesn't have a configuration for #{SUBTRAC_ENV}."
      end
    else
      raise StandardError, "config/config.yml does not exist."
    end
    say("Configuration loaded successfully...")
  end

  # Write the changes configuration to disk
  def self.save_config
    # save the things that might have changed    
    file_path = File.join(subtrac_path, USER_CONFIG_FILE)
    user_config = {SUBTRAC_ENV => @APP_CONFIG}
    open(file_path, 'w') {|f| YAML.dump(user_config, f)}
  end

  # Install
  def self.install(args,options)
    puts "\n==== Installing development server files ===="
    
    if options.defaults then
      overwrite = options.clean
      confirm_default_client = true
    else
      # check where we are installing
      confirm_or_replace_value(:install_dir,"install_dir")
      
      unless !File.directory?(install_dir) 
        # Ask if the user agrees (yes or no)
        confirm_clean = agree("Err, it seems there's some stuff in there. You sure you want me to overwrite? [Y/n]") if options.clean
        overwrite = agree("Doubly sure? I can't undo this....[Y/n]") if confirm_clean
      end

      # confirm server
      confirm_or_replace_value(:server_name,"server_name")

      # ask for hostname
      confirm_or_replace_value(:server_hostname,"server_hostname")

      # default client/project name
      confirm_or_replace_value(:default_client,"default_client")
      confirm_or_replace_value(:default_project,"default_project")

    end
    
    say("Ok we're about to install now, these are the options you have chosen:
    installation directory: #{install_dir}
    overwrite: #{overwrite}
    server name: #{server_name}
    server hostname: #{server_hostname}
    default client: #{default_client}
    default project: #{default_project}")

    confirm = agree("Is this ok? [y/n]")  
    
    exit 0 if !confirm
    
    create_environment_directories(overwrite)
    #create_virtual_host()
    apache = Apache.new(binding)
    apache.create_virtual_host()
    
    install_common_files()
    configure_admin_user()
    
    # create default project
    create_project(default_project,default_client)

    # store any user preferences for later use
    save_config()

  end
  
  def self.create_project(project,client)
    
    # create default project
    project = Project.new(project,client,default_project_type)
    
    # get these out for binding...we'll tidy up later
    client = project.client

    # create the svn repo
    svn = Svn.new(svn_dir, binding)
    svn.create_project(project)
    
    # create the trac site
    trac = Trac.new(trac_dir, binding)
    trac.create_project(project)
    
    # create the apache configuration
    apache.create_project(project)

    # fix privileges
    give_apache_privileges()
    
  end

  private
  
  def self.give_apache_privileges
    # make sure apache can operate on these files
    `sudo chown -R www-data:www-data #{install_dir}`
  end
  
  # confirms the current value with the user or accepts a new value if required
  def self.confirm_or_replace_value(prop, name)
    method(prop).call # initialise the property
    current_value = instance_variable_get("@#{prop}")
    accept_default = agree("The default value for #{prop} @#{prop} is \"#{current_value}\". Is this ok? [y/n]")
    if !accept_default then
      answer = ask("What would you like to change it to?")
      send("#{prop}=", answer)
      #instance_variable_set(prop, answer)
    end
  end
  
  def self.install_common_files
    puts "\n==== Installing common files ===="
    # TODO: implement a mask for .svn folders
    # TODO: refactor /common to the app config
    FileUtils.cp_r(Dir.glob(File.join(subtrac_path, "common/.")),docs_dir)
    FileUtils.cp_r(Dir.glob(File.join(subtrac_path, "shared/.")),File.create_if_missing(File.join(trac_dir, ".shared")))
  end
  
  def self.configure_admin_user
    puts "\n==== Configure admin user ===="
    # create admin user
    passwd_file = File.join(install_dir, ".passwd")
    admin_user = ask("New admin user:  ") { |q| q.echo = true }
    admin_pass = ask("New password:  ") { |q| q.echo = "*" }
    admin_pass_confirm = ask("Re-type new password:  ") { |q| q.echo = "*" }
    if (admin_pass == admin_pass_confirm) then
      `htpasswd -c -b #{passwd_file} #{admin_user} #{admin_pass}`
      @APP_CONFIG[:admin_user] = admin_user
      @APP_CONFIG[:admin_pass] = admin_pass
      # ensure this guy is added to trac admin group
      @APP_CONFIG[:trac][:permissions][admin_user] = "admins"
    else
      # call the password chooser again
    end
  end

  def self.create_environment_directories(overwrite=false)
    puts "\n==== Creating new environment directories ===="
    FileUtils.rm_rf install_dir if overwrite
    File.create_if_missing install_dir
    # create the environment directories
    @APP_CONFIG[:dirs].each do |key, value|
      dir = File.join(install_dir,value)
      File.create_if_missing dir
    end
  end

end