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

module Embulk
  module Input

    class Wendelininput < InputPlugin
      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.alwaysYesDownload()
        @dataset_utils.deleteSplitOperationControlFile()
        @dataset_utils.deleteSplitOperationFile()
        @dataset_utils.deleteCompletedFile()
        @dataset_utils.createReportFile()
        @logger.info("[always yes mode] Download operation will overwrite previous local dataset.", print=TRUE)
      end

      def self.askUserForAction(task, action, show_message)
	option = @dataset_utils.getConfiguration(action, task['tool_dir'])
	valid_option = option != DatasetUtils::OPTION_ABORT ? TRUE : FALSE
	if action == DatasetUtils::OPTION_RESUME
	  action_message =  "#{DatasetUtils::OPTION_RESUME}: Resume. Continues download from last file."
	  if show_message
	    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) if option == DatasetUtils::OPTION_ABORT
	    puts
	  end
	else
	  action = DatasetUtils::OPTION_UPDATE
	  action_message = "#{DatasetUtils::OPTION_UPDATE}: Update. Checks for changes in dataset."
	  if show_message
	    puts
	    @logger.info("This dataset was already downloaded.", print=TRUE)
	    @logger.info("What do you want to do?", print=TRUE) if option == DatasetUtils::OPTION_ABORT
	    puts
	  end
	end
	while not valid_option
	  @logger.info("Please select an option [#{action}, #{DatasetUtils::OPTION_DOWNLOAD}, #{DatasetUtils::OPTION_ABORT}]", print=TRUE)
	  @logger.info(action_message, print=TRUE)
	  @logger.info("#{DatasetUtils::OPTION_DOWNLOAD}: Download. Downloads the dataset from scratch.", print=TRUE)
	  @logger.info("#{DatasetUtils::OPTION_ABORT}: Abort operation.", print=TRUE)
	  option = gets
	  option = option.chomp
	  if not [action, DatasetUtils::OPTION_DOWNLOAD, DatasetUtils::OPTION_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 != DatasetUtils::OPTION_RESUME
	    task['data_streams'] = @dataset_utils.getRemoteChangedDataStreams(task['data_streams'], @data_set)
	    self.warnConflicts(task['data_streams'], task['data_set']) if action != DatasetUtils::OPTION_RESUME
	    @dataset_utils.deleteCompletedFile()
	    if task['data_streams'].empty?
	      @logger.info("Your downloaded dataset is already up to date.", print=TRUE)
	    end
	  when DatasetUtils::OPTION_DOWNLOAD
	    @dataset_utils.deleteSplitOperationControlFile()
	    @dataset_utils.deleteSplitOperationFile()
	    @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 DatasetUtils::OPTION_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)
	  @batch_size= config.param('batch_size', :integer)
	  @logger.info("Dataset name: #{@data_set}")
	  @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
	  task = {
		 'erp5_url' => @erp5_url,
		 'data_set' => @data_set,
		 'chunk_size' => DatasetUtils::CHUNK_SIZE + 10,
		 'output_path' => @output_path,
		 'tool_dir' => @tool_dir,
		 'always_yes_mode' => config.param('always_yes', :string)
	       }
	  if task['chunk_size'] == 0
	    task['chunk_size'] = DatasetUtils::CHUNK_SIZE
	  end
	  @logger.info("Download chunk size relies on server file chunks.")
	  @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.partialIngestionFileExist()
	    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
	  task['user'], task['password'] = @dataset_utils.getCredentials(@tool_dir)
	  @wendelin = WendelinClient.new(@erp5_url, task['user'], task['password'])
	  @logger.info("Getting remote file list from dataset '#{@data_set}'...", print=TRUE)
	  data_stream_count = @wendelin.getDataStreamCount(@data_set)
      if data_stream_count["status_code"] == 0
	    if data_stream_count["result"] == 0
	      @logger.error("No valid data found for data set " + @data_set, print=TRUE)
	      @logger.abortExecution(error=FALSE)
	    end
      else
        @logger.error(data_stream_count["error_message"], print=TRUE)
        @logger.abortExecution()
	  end
      data_stream_list = @wendelin.getDataStreamList(@data_set, data_stream_count["result"], @batch_size)
      if data_stream_list["status_code"] == 0
	    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
		if task['always_yes_mode'] != "true"
		  self.askUserForAction(task, action=DatasetUtils::OPTION_UPDATE, show_message=TRUE)
		else
		  self.alwaysYesDownload()
		end
	      end
	    elsif not @dataset_utils.partialIngestionFileExist()
	      if task['always_yes_mode'] != "true"
	        self.askUserForAction(task, action=DatasetUtils::OPTION_RESUME, show_message=TRUE)
	      else
	        self.alwaysYesDownload()
	      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
	      puts
	      self.askUserForAction(task, action=DatasetUtils::OPTION_UPDATE, show_message=FALSE)
	    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.deletePartialIngestionFile()
	  @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']
	@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
	remote_file = task['data_streams'][@index]
	reference = remote_file["reference"]
	size = remote_file["full-size"]
	large_hash = remote_file["large-hash"]
	data_stream_chunk_list = remote_file["data-stream-list"]
	renamed = remote_file["status"] == DatasetUtils::STATUS_RENAMED
	deleted = large_hash.to_s == DatasetUtils::DELETE
	begin
	  if deleted
	    entry = [reference, "", @data_set, DatasetUtils::DELETE, renamed]
	    page_builder.add(entry)
	  elsif renamed
	    new_reference = remote_file["new_reference"]
	    entry = [reference, new_reference, @data_set, TRUE, renamed]
  	    page_builder.add(entry)
	  else
      chunk_size = data_stream_chunk_list[0]["size"] #first chunk size
	    @logger.info("Discarding local change on '#{remote_file["path"]}'", print=TRUE) if task['discard_changes']
	    @logger.info("Getting content from remote #{reference}", print=TRUE)
	    resume_split = @dataset_utils.splitOperationFileExist(reference) ? @dataset_utils.getLastSplitOperation(DatasetUtils::DOWNLOAD, reference, large_hash, chunk_size) : 0
      n_chunk = resume_split == 0 ? 0 : resume_split+1
      split = n_chunk > 0
      if split
        @logger.info("Resuming interrupted split download...", print=TRUE)
      else
        if data_stream_chunk_list.length > 1
          @logger.info("Downloading large file split in chunks...", print=TRUE)
        else
          @logger.info("Downloading...", print=TRUE)
        end
      end
      data_stream_chunk_list.each_with_index do |data_stream_chunk, index|
        #skip datastreams/chunks already downloaded
        if n_chunk == index
          content = ""
          @wendelin.eachDataStreamContentChunk(data_stream_chunk["id"], chunk_size + 10, 0, data_stream_chunk_list.length > 1) do |chunk|
            content = chunk.nil? || chunk.empty? ? "" : Base64.encode64(chunk)
          end
          begin_of_file = n_chunk == 0
          split = n_chunk > 0
          @dataset_utils.createSplitOperationControlFile(reference) if split
          entry = [reference, content, @data_set, begin_of_file, renamed]
          page_builder.add(entry)
          @dataset_utils.saveSplitOperation(DatasetUtils::DOWNLOAD, reference, n_chunk, large_hash, chunk_size) if split
          n_chunk += 1
        end
      end
      @logger.info("Done", print=TRUE) if data_stream_chunk_list.length > 1
	  end
	  page_builder.finish
	  @dataset_utils.deleteSplitOperationFile(reference) if split
	rescue java.lang.OutOfMemoryError
	  @logger.logOutOfMemoryError(reference)
	  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
	@dataset_utils.deleteSplitOperationControlFile(reference) if return_value != DatasetUtils::RUN_DONE
	if return_value == DatasetUtils::RUN_DONE
	  if deleted
	    @dataset_utils.deleteFromReport(reference, return_value)
	  else
      file_path = renamed ? @dataset_utils.referenceToPath(new_reference, @data_set_directory, @data_set) : @dataset_utils.referenceToPath(reference, @data_set_directory, @data_set)
      hash = @dataset_utils.getHash(file_path).to_s
	    @dataset_utils.addToReport(reference, return_value, size, hash, large_hash, task['data_set'], new_reference)
	  end
	end
	return {return_value => reference}
      end
    end
  end
end
