#!/usr/bin/env ruby
require 'optparse'
require 'tmpdir'
require 'erb'
require 'genZPK'
require 'xmlsimple'
include GenZPK

# Initialize the hash to store the various command line options
options = {}

opt_parser = OptionParser.new do |opt|

  opt.banner = "Usage: genZPK -s SYSTEM -v ZPK_VERSION -n PACKAGE_NAME DIR"
  opt.separator "Initial configuration: genZPK --configure "
  opt.separator ""


  opt.on("-s","--system SYSTEM","which system should checks be performed for") do |system|
    options[:system] = system
  end

  opt.on("-v","--app-version ZPK_VERSION","version of zpk to create") do |version|
    options[:version] = version
  end

  opt.on("-n","--name PACKAGE_NAME","name of application to deploy") do |name|
    options[:name] = name
  end

  opt.on("-p","--pull", "run \"git pull origin master\" prior to packaging") do
    options[:pull] = true
  end

  opt.on("--configure","perform the initial configuration to setup checks prior to packaging") do
    GenZPK::doConfigure
    exit 0
  end


  opt.on("--version", "prints the version of this application") do
    puts GenZPK::VERSION
    exit 0
  end

  opt.on("-h","--help","help") do
    puts opt_parser
  end
end

# Parse options
begin
  opt_parser.parse!
rescue OptionParser::InvalidOption
  # The user goofed. Send themt ot he documentation.
  warn "Required parameters not set or invalid argument. Please run genZPK --help for more info."
  exit -1
end

# Read in the working directory for the PHP code to be packaged appending a '/' if missing
dir = String(ARGV[0]).end_with?('/') ? String(ARGV[0]) : String(ARGV[0]) + "/"


# Ensure all the required conditions to run are true
requireCondition File.exists?(File.expand_path '~/.genZPK/checks.xml'), "Unable to locate configuration files. Please run genZPK --configure"
requireCondition (File.read(File.expand_path('~/.genZPK/checks.xml')).gsub(/\s+/, "") != ""), "Configuration empty. Please run genZPK --configure"
requireCondition getZdpack.not_nil?, "unable to locate zdpack executable. Please check zend server installation at /usr/local/zend/"
requireCondition dir.not_nil?,"Required parameters not set or invalid argument. Please run genZPK --help for more info."
requireCondition options[:system].not_nil?,"Required parameters not set or invalid argument. Please run genZPK --help for more info."
requireCondition options[:version].not_nil?,"Required parameters not set or invalid argument. Please run genZPK --help for more info."
requireCondition options[:name].not_nil?,"Required parameters not set or invalid argument. Please run genZPK --help for more info."
requireCondition File.exists?(dir), "Error: Directory \"#{dir}\" does not exist."

# Read in the checks.xml file in using xml-simple. KeyAttr specifies that instead of using a zero-based array to
# store sub-nodes, they should be loaded into a hash where the 'name' field is the key. Setting ForceArray to false
# removes all arrays unless there are multiple nodes of the same name that KeyAttr doesn't apply to.,
configHash = XmlSimple.xml_in(File.expand_path( '~/.genZPK/checks.xml'), {'KeyAttr' => 'name', 'ForceArray'=>false})

# If the user specified to pull down new sorcce from git, change the directory and do. Exit on failure.
if options[:pull] == true
  Dir.chdir dir do
    requireCondition system("git pull origin master"), "Unable to update code via git. Exiting..."
  end
end

# If the name key ixists in the hash, that means there is only a single
# entry in it so the name of the system doesn't need to be specified.
if configHash["system"].has_key? "name"
  # Ensure that the system specified on the command line matches the system in the config file. Exit otherwise.
  if not configHash["system"]["name"] == options[:system]
    warn "Could not find system \"#{options[:system]}\" in configuration. Please run genZPK --configure to setup checks for this system or verify the system name."
    exit -1
  end

  # If configHash["system"]["checks"]["check"] isn't an array, there is only one check and an array consisting
  # of that checvk should be loaded into checks
  if configHash["system"]["checks"]["check"].is_a? Array
    checks = configHash["system"]["checks"]["check"]
  else
    checks = Array.new
    checks.push configHash["system"]["checks"]["check"]
  end


  # Check is any subdomains are setup
  if configHash["system"].has_key? "subdomains"

    # Same logic as above. Ensure than subdomainChecks is always an array.
    if configHash["system"]["subdomains"]["file"].is_a? Array
      subdomainChecks = configHash["system"]["subdomains"]["file"]
    else
      subdomainChecks = Array.new
      subdomainChecks.push configHash["system"]["subdomains"]["file"]
    end
  else
    # If there aren't any defined, set it to nil.
    subdomainChecks = nil
  end
else # There are multiple systems configured
  # Check to make sure system exists, exit otherwise.
  if not configHash["system"].has_key? options[:system]
    warn "Could not find system \"#{options[:system]}\" in configuration. Please run genZPK --configure to setup checks for this system or verify the system name."
    exit -1
  end

  # Ensure checks is an array if there is only a single check.
  if configHash["system"][options[:system]]["checks"]["check"].is_a? Array
    checks = configHash["system"][options[:system]]["checks"]["check"]
  else
    checks = Array.new
    checks.push configHash["system"][options[:system]]["checks"]["check"]
  end

  # If there are subdomain checks defined, make sure they are saved as an array, otherwise nil.
  if configHash["system"][options[:system]].has_key? "subdomains"
    if configHash["system"][options[:system]]["subdomains"]["file"].is_a? Array
      subdomainChecks = configHash["system"][options[:system]]["subdomains"]["file"]
    else
      subdomainChecks = Array.new
      subdomainChecks.push configHash["system"][options[:system]]["subdomains"]["file"]
    end
  else
    subdomainChecks = nil
  end
end

puts "Running checks"
puts "-" * 80

successes = 0

# For every check
for i in 0..(checks.size - 1)
  check = checks[i]
  desc      = check["desc"]
  filepath  = check["file"]
  matchval  = check["value"]

  # Make sure regular expression is valid, exit otherwise.
  begin
    regex     = /#{check["regex"]["content"]}/
  rescue RegexpError => e
    puts "Invalid regular in configuration. Please run genZPK --configure\n#{e.message}"
    exit -1
  end

  # match groups should be 1 indexed in the config file. Correct that here.
  matchgrp  = Integer(check["regex"]["group"]) - 1

  display   = desc

  # Display an error if the file cannot be found and proceed to the next check
  if not File.exists?(dir + filepath)
    display << "." * (80 - "FAIL".size - desc.size)
    display << "FAIL\n"
    display << "\tFile not found: #{dir + filepath}"
    puts display
    puts "-" * 80
    next
  end

  # Read in the contents of the file to check and scan it with the given regexp
  contents = File.read(dir + filepath)
  matches = contents.scan(regex)

  results = ""
  passes = 0

  # For every match
  for i in 0..(matches.size - 1)
    match = matches[i]

    #Ensure the match is the right value and increment the number of passed matches
    if not match[matchgrp] == matchval
      results << "\t#{dir + filepath}\n"
      results << "\tValue found: #{match[matchgrp]}\n"
      results << "\tRequired value: #{matchval}\n\n"
    else
      passes += 1
    end
  end

  # If ever match passed
  if passes == matches.size
    # Display a success
    display << "." * (80 - "SUCCESS".size - desc.size)
    display << "SUCCESS"
    puts display

    # and increment the number of successfl checks
    successes += 1
  else # otherwise display that it failed and proceed to the next check
    display << "." * (80 - "FAIL".size - desc.size)
    display << "FAIL"
    puts display
    puts results.chomp
  end
  puts "-" * 80
end

# If no all the checks passed, exit the program.
if not successes == checks.size
  puts "#{successes} of #{checks.size} checks passed. Please check files listed above and try again."
  exit -1
end

puts "All checks passed. Continuing..."

puts "\n\n"

if not subdomainChecks.nil?
  puts "Verifying subdomains"
  puts "-" * 80

  successes = 0

# For every subdomain to check
  for i in 0..(subdomainChecks.size - 1)

    check     = subdomainChecks[i]
    path      = check["path"]

    display   = dir + path

    # Ensure the file exists
    if File.exists?(dir + path)
      # read in its contents
      contents = File.read(dir + path)
      fieldPasses = 0

      # For each field-value pair in this file
      check.each do |field, value|
        # make sure it isn't the file path
        if field != "path"
          # If the field is defined
          if not configHash["definitions"]["definition"][field].nil?
            # Make sure the regular expession is valid, otherwise exit.
            begin
              regex     = /#{configHash["definitions"]["definition"][field]["regex"]["content"]}/
            rescue RegexpError => e
              puts "Invalid regular in configuration. Please run genZPK --configure\n#{e.message}"
              exit -1
            end

            # Offset the match group to be zero-indexed
            matchgrp  = Integer(configHash["definitions"]["definition"][field]["regex"]["group"]) - 1

            # scan the file for a match
            matches = contents.scan(regex)

            # If there are no matches, let the user know and continue to the next field
            if matches == []
              if display.end_with? "\n"
                display << "\tField not found in file: #{field}\n"
              else
                display << "\n\tField not found in file: #{field}\n"
              end
            else # There were matches
              # Ensure the value is correct and if it isn't display an error and continue to the next field
              if matches[0][matchgrp] == value
                fieldPasses += 1
              else
                if display.end_with? "\n"
                  display << "\tField Failed: #{field}\n"
                  display << "\t\tValue: #{matches[0][0]}\n"
                  display << "\t\tDefined value: #{value}\n"
                else
                  display << "\n\tField Failed: #{field}\n"
                  display << "\t\tValue: #{matches[0][0]}\n"
                  display << "\t\tDefined value: #{value}\n"
                end
              end
            end
          else # the field isn't defined.
            # Let the user know and continue to the next field
            if display.end_with? "\n"
              display << "\tField not defined: #{field}\n"
            else
              display << "\n\tField not defined: #{field}\n"
            end
          end
        end
      end

      # Check if all the fields passed and display a success. The number of passes should be one less
      # than all the fields since we excluded path.
      if fieldPasses == (check.size - 1)
        display << "." * (80 - "SUCCESS".size - display.size)
        display << "SUCCESS"
        puts display
        puts "-" * 80
        successes += 1
      else # one or more fields failed. Let the user know
        tmpString = "#{fieldPasses} of #{check.size - 1} fields verified"
        display << tmpString
        display << "." * (80 - "#{fieldPasses} of #{check.size - 1} fields verified".size - "FAIL".size)
        display << "FAIL"
        puts display
        puts "-" * 80
      end
    else # the specified subdomain file couldn't be found. Display an error
      display << "." * (80 - "FAIL".size - display.size)
      display << "FAIL\n"
      display << "\tFile not found: #{dir + path}"
      puts display
      puts "-" * 80
    end
  end

  #Check to see if all the subdomains passed. If they didn't, exit.
  if not successes == subdomainChecks.size
    puts "#{successes} of #{subdomainChecks.size} subdomains passed. Please check files listed above and try again."
    exit -1
  end

  puts "All subdomains passed. Continuing...\n"
end

# Ensure the /scripts directory exists in the working directory
if not File.exists? dir + "/scripts"
  FileUtils.mkpath dir + "/scripts"
end

# get the templates for the deployment configuration and pre-activation script
#scriptTemplate = getTemplate 'pre_activate.php'
deploymentTemplate = getTemplate 'deployment.xml'

# These are used to populate the tamplates
releaseName = options[:name]
zpkVersion = options[:version]

# Generate the files
#script = ERB.new(scriptTemplate)
deployment = ERB.new(deploymentTemplate)

# Write out the files
#File.open(dir + '/scripts/pre_activate.php', 'w') {|f| f.puts(script.result(binding))}
File.open(dir + '/deployment.xml', 'w') {|f| f.puts(deployment.result(binding))}


zpkFile = "#{options[:name]}.zpk"

# Continue to increment the zpk suffix until an unused filename is found.
while File.exists? zpkFile
  # Pull the base filename and suffix from the filename.
  matches = /(.*\.zpk)\.?(.*)?/.match(zpkFile)

  # if no match found for the suffix, start at zero
  if matches[2]==""
    ver = 0
  else
    ver = Integer(matches[2])
  end

  # increment the suffix by 1
  zpkFile = "#{matches[1]}.#{ver + 1}"
end

# Make a temporary directory to work in
Dir.mktmpdir do |tempDir|
    # get the zdpack executable
    zdpack = getZdpack

    # temporarily remove the git repo from the working directory to prevent packaging it.
    system("mv #{dir}/.git #{tempDir}/")

    # package the zpk and output it to the temp directory
    system("#{zdpack} pack --output-dir=#{tempDir} #{dir}")

    # Move the git repository back and place the zpk in the current directory
    system("mv #{tempDir}/.git #{dir}/")
    system("mv #{tempDir}/#{options[:name]}.zpk ./#{zpkFile}")

    # remove the generated files from the working directory
    system("rm -r #{dir + '/scripts'}")
    system("rm #{dir + '/deployment.xml'}")
end

# Let the user know that nothing screwed up and it worked
puts "Successfully created ZPK at ./#{zpkFile}"