require_relative '../wendelin_client'
require_relative '../dataset_utils'
require 'fileutils'

module Embulk
  module Input

    class Wendelininput < InputPlugin

      UPDATE = "U"
      RESUME = "R"
      DOWNLOAD = "D"
      ABORT = "A"

      Plugin.register_input("wendelin", self)

      def self.warnConflicts(remote_streams, data_set)
	if not remote_streams.empty?
	  conflicts = @dataset_utils.getLocalConflicts(remote_streams, data_set)
	  if not conflicts.empty?
	    @logger.warn("CONFLICT: there are conflicts with some of your local changes.", print=TRUE)
	    puts "** press key **"
	    option = gets
	    @logger.warn("Conflicted changes:", print=TRUE)
	    @logger.warn(conflicts, print=TRUE)
	    puts
	    @logger.warn("Your local conflicted changes will be overwritten by current download.", print=TRUE)
	    @logger.warn("Do you want to continue? (y/n)", print=TRUE)
	    option = gets
	    option = option.chomp
	    if option == "n" or option == "N"
	      @logger.info("Download cancelled by user.", print=TRUE)
	      @logger.abortExecution(error=FALSE)
	    end
	    @dataset_utils.deleteStagedFile()
	  end
	end
      end

      def self.askUserForAction(task, action)
	if action == RESUME
	  action_message =  "#{RESUME}: Resume. Continues download from last file."
	else
	  action = UPDATE
	  action_message = "#{UPDATE}: Update. Checks for changes in dataset."
	end
	valid_option = FALSE
	while not valid_option
	  @logger.info("Please select an option [#{action}, #{DOWNLOAD}, #{ABORT}]", print=TRUE)
	  @logger.info(action_message, print=TRUE)
	  @logger.info("#{DOWNLOAD}: Download. Downloads the dataset from scratch.", print=TRUE)
	  @logger.info("#{ABORT}: Abort operation.", print=TRUE)
	  option = gets
	  option = option.chomp
	  if not [action, DOWNLOAD, ABORT].include? option
	    @logger.info("Invalid option", print=TRUE)
	  else
	    valid_option = TRUE
	  end
	end
	case option
	  when action
	    @logger.info("Checking remote changes and posible local conflicts...", print=TRUE) if action != RESUME
	    task['data_streams'] = @dataset_utils.getRemoteChangedDataStreams(task['data_streams'], @data_set)
	    self.warnConflicts(task['data_streams'], task['data_set']) if action != RESUME
	    @dataset_utils.deleteCompletedFile()
	    if task['data_streams'].empty?
	      @logger.info("Your downloaded dataset is already up to date.", print=TRUE)
	    end
	  when DOWNLOAD
	    @logger.info("Checking remote files and posible local conflicts...", print=TRUE)
	    self.warnConflicts(task['data_streams'], task['data_set'])
	    @dataset_utils.deleteCompletedFile()
	    @dataset_utils.createReportFile()
	  when ABORT
	    @logger.abortExecution()
	end
      end

      def self.transaction(config, &control)
	begin
	  @tool_dir = config.param('tool_dir', :string)
	  @logger = LogManager.instance()
	  @logger.setFilename(@tool_dir, "download")
	  @erp5_url = config.param('erp5_url', :string)
	  @data_set = config.param('data_set', :string)
	  @logger.info("Dataset name: #{@data_set}")
	  @user = config.param("user", :string, defualt: nil)
	  @logger.info("User: #{@user}")
	  @password = config.param("password", :string, default: nil)
	  @chunk_size = config.param('chunk_size', :float, default: 0) * DatasetUtils::MEGA
	  @output_path = config.param("output_path", :string,  :default => nil)
	  if not File.directory?(@output_path)
	    @logger.error("Output directory not found.", print=TRUE)
	    @logger.abortExecution()
	  end
	  @wendelin = WendelinClient.new(@erp5_url, @user, @password)
	  task = {
		 'erp5_url' => @erp5_url,
		 'data_set' => @data_set,
		 'user' => @user,
		 'password' => @password,
		 'chunk_size' => @chunk_size,
		 'output_path' => @output_path,
		 'tool_dir' => @tool_dir
	       }
	  if task['chunk_size'] == 0
	    task['chunk_size'] = DatasetUtils::CHUNK_SIZE
	  end
	  @logger.info("Chunk size set in #{task['chunk_size']/DatasetUtils::MEGA}MB")
	  @dataset_utils = DatasetUtils.new("")
	  task['data_set_directory'] = @dataset_utils.appendSlashTo(@output_path)
	  @data_set_directory = task['data_set_directory']
	  @dataset_utils = DatasetUtils.new(@data_set_directory)
	  if @dataset_utils.reportFileExist() && @dataset_utils.completedFileExist() && @dataset_utils.discardChangesFileExist() && ! @dataset_utils.initialIngestionFileExist()
	    task['discard_changes'] = @dataset_utils.discardChangesFileExist()
	    local_changes = @dataset_utils.getRemoteFileListForDiscardLocalChanges([], @data_set, check_changes=TRUE)
	    if local_changes.empty?
	      puts
	      @logger.info("No local changes to discard.", print=TRUE)
	      puts
	      @dataset_utils.deleteDiscardChangesFile()
	      @logger.abortExecution(error=FALSE)
	    end
	  end
	  @logger.info("Getting remote file list from dataset '#{@data_set}'...", print=TRUE)
	  data_stream_list = @wendelin.getDataStreams(@data_set)
	  if data_stream_list["status_code"] == 0
	    if data_stream_list["result"].empty?
	      @logger.error("No valid data found for data set " + @data_set, print=TRUE)
	      @logger.abortExecution(error=FALSE)
	    end
	    task['data_streams'] = data_stream_list["result"]
	  else
	    @logger.error(data_stream_list["error_message"], print=TRUE)
	    @logger.abortExecution()
	  end
	  @logger.info("Done", print=TRUE)

	  if @dataset_utils.reportFileExist()
	    if @dataset_utils.completedFileExist()
	      if task['discard_changes']
		puts
		@logger.warn("All your local changes will be discarded.", print=TRUE)
		@logger.warn("Do you want to continue? (y/n)", print=TRUE)
		option = gets
		option = option.chomp
		if option == "n" or option == "N"
		  @logger.info("Download cancelled by user.", print=TRUE)
		  @dataset_utils.deleteDiscardChangesFile()
		  @logger.abortExecution(error=FALSE)
		end
		@dataset_utils.deleteStagedFile()
		task['data_streams'] = @dataset_utils.getRemoteFileListForDiscardLocalChanges(task['data_streams'], @data_set,
											      check_changes=FALSE, changes=local_changes)
	      else
		puts
		@logger.info("This dataset was already downloaded. What do you want to do?", print=TRUE)
		puts
		self.askUserForAction(task, action=UPDATE)
	      end
	    elsif not @dataset_utils.initialIngestionFileExist()
	      puts
	      @logger.info("There was a previous attempt to download this dataset but it did not finish successfully.", print=TRUE)
	      @logger.info("What do you want to do?", print=TRUE)
	      puts
	      self.askUserForAction(task, action=RESUME)
	    else
	      if @dataset_utils.discardChangesFileExist()
		puts
		@logger.info("Discard changes feature do not apply in current dataset directory status.", print=TRUE)
		@logger.info("Continuing with dataset download.", print=TRUE)
	      end
	      puts
	      self.askUserForAction(task, action=UPDATE)
	    end
	  else
	    if @dataset_utils.discardChangesFileExist()
	      puts
	      @logger.info("Discard changes feature do not apply in current dataset directory status.", print=TRUE)
	      @logger.info("Continuing with dataset download.", print=TRUE)
	    end
	    if not @dataset_utils.dirEmpty(@data_set_directory)
	      puts
	      @logger.info("Dataset download directory is not empty! Its files could be overwritten: " + @data_set_directory, print=TRUE)
	      @logger.info("Continue with download? (y/n)", print=TRUE)
	      option = gets
	      option = option.chomp
	      if option == "n"
		@logger.info("Download cancelled by user.", print=TRUE)
		@logger.abortExecution(error=FALSE)
	      end
	      @logger.info("Checking remote files and posible local conflicts...", print=TRUE)
	      self.warnConflicts(task['data_streams'], task['data_set'])
	    end
	    @dataset_utils.createReportFile()
	  end
	  @dataset_utils.deleteInitialIngestionFile()
	  @dataset_utils.deleteDiscardChangesFile()
	  columns = [
	    Column.new(0, "reference", :string),
	    Column.new(1, "data_chunk", :string),
	    Column.new(2, "data_set", :string),
	    Column.new(3, "mode", :string),
	    Column.new(4, "rename", :string)
	  ]
	  resume(task, columns, task['data_streams'].length, &control)
	rescue Exception => e
	  @logger.error("An error occurred during operation: " + e.to_s, print=TRUE)
	  @logger.error(e.backtrace)
	  puts "[INFO] For more detailed information, please refer to the log file."
	  @logger.abortExecution()
	end
      end

      def self.resume(task, columns, count, &control)
	@logger = LogManager.instance()
	task_reports = yield(task, columns, count)
	@dataset_utils.showTaskReport(task_reports) if not task['discard_changes']
	next_config_diff = task_reports.map{|hash| hash[DatasetUtils::RUN_DONE]}.flatten.compact
	if(next_config_diff.length == count)
	  if(count > 0)
	    if task['discard_changes']
	      @logger.info("All local changes were discarded.", print=TRUE)
	    else
	      @logger.info("Dataset successfully downloaded.", print=TRUE)
	      @logger.info("#{count} files processed.", print=TRUE)
	      @logger.info("Dataset files are in dataset directory: " + @data_set_directory, print=TRUE)
	    end
	  end
	  @dataset_utils.createCompletedFile()
	else
	  if(count > 0)
	    puts
	    @logger.error("Some remote files could not be downloaded. Please check the details in the log file: " + @logger.getLogPath(), print=TRUE)
	    @logger.info("Please retry the operation for download those files.", print=TRUE)
	    puts
	  end
	end
	next_config_diff = {}
	return {DatasetUtils::RUN_DONE => next_config_diff}
      end

      def initialize(task, schema, index, page_builder)
	super
	@data_set = task['data_set']
	@chunk_size = task['chunk_size']
	@data_set_directory = task['data_set_directory']
	@wendelin = WendelinClient.new(task['erp5_url'], task['user'], task['password'])
	@logger = LogManager.instance()
	@dataset_utils = DatasetUtils.new(@data_set_directory)
      end

      def run
	data_stream = task['data_streams'][@index]
	id = data_stream["id"]
	ref = data_stream["reference"]
	size = data_stream["size"]
	hash = data_stream["hash"]
	renamed = data_stream["status"] == DatasetUtils::STATUS_RENAMED
	deleted = hash.to_s == DatasetUtils::DELETE
	begin
	  if deleted
	    entry = [ref, "", @data_set, DatasetUtils::DELETE, renamed]
	    page_builder.add(entry)
	  elsif renamed
	    new_reference = data_stream["new_reference"]
	    entry = [ref, new_reference, @data_set, TRUE, renamed]
  	    page_builder.add(entry)
	  else
	    @logger.info("Discarding local change on '#{data_stream["path"]}'", print=TRUE) if task['discard_changes']
	    @logger.info("Getting content from remote #{ref}", print=TRUE)
	    n_chunk = 0
	    @wendelin.eachDataStreamContentChunk(id, @chunk_size) do |chunk|
	      content = chunk.nil? || chunk.empty? ? "" : Base64.encode64(chunk)
	      begin_of_file = n_chunk == 0
	      entry = [ref, content, @data_set, begin_of_file, renamed]
	      page_builder.add(entry)
	      n_chunk += 1
	    end
	  end
	  page_builder.finish
	rescue java.lang.OutOfMemoryError
	  @logger.logOutOfMemoryError(ref)
	  return_value = DatasetUtils::RUN_ABORTED
	rescue Exception => e  
	  @logger.error(e.to_s, print=TRUE)
	  @logger.error(e.backtrace)
	  puts "[INFO] For more detailed information, please refer to the log file: " + @logger.getLogPath()
	  return_value = DatasetUtils::RUN_ERROR
	else
	  return_value = DatasetUtils::RUN_DONE
	end
	if return_value == DatasetUtils::RUN_DONE
	  if deleted
	    @dataset_utils.deleteFromReport(ref, return_value)
	  else
	    @dataset_utils.addToReport(ref, return_value, size, hash, task['data_set'], new_reference)
	  end
	end
	return {return_value => ref}
      end
    end
  end
end
