require 'fileutils'
require 'fedux_org/stdlib/filesystem/exceptions'

# helper for file system
module FeduxOrg
  module Stdlib
    module Filesystem

      # The root directory of the project
      #
      # @return [String]
      #   the root directory
      def root_directory
        #::File.expand_path('../../../', __FILE__)
        raise Exceptions::InvalidUsageOfLibrary , "Sorry, but you need to define the root directory yourself"
      end

      # The temporary directory for the project
      #
      # @return [String]
      #   the directory created for the tests 
      def working_directory
        ::File.join(root_directory, 'tmp', 'test')
      end

      # Create temporary directory
      def create_working_directory
        FileUtils.mkdir_p(working_directory) unless ::File.exists? working_directory
      end

      # Delete temporary directory
      def delete_working_directory
        FileUtils.rm_rf(working_directory) if ::File.exists? working_directory
      end

      # Clean up test directory
      def cleanup_working_directory
        delete_working_directory
        create_working_directory
      end

      # Switch the current working directory to 
      # the temporary one and execute code block
      def switch_to_working_directory(&block)
        Dir.chdir(working_directory, &block)
      end

      # Create directory(ies)
      #
      # @param [String,Array] dir
      #   the directories to be created, multiple arguments are possible as well
      #
      # @return [String,Array] 
      #   returns a string if there was only one file given, and an array with 
      #   muliple files
      def create_directory(*dirs)
        raise_if_forbidden_path_for_create_operation(dirs)

        directories = expand_path(dirs.flatten)
        FileUtils.mkdir_p(directories)

        if directories.size == 1
          return directories.first
        else
          return directories
        end
      end

      # Delete directory(ies)
      #
      # @param [String, Array] dir
      #   the directories to be deleted, multiple arguments are possible as well
      #
      # @return [String,Array] 
      #   returns a string if there was only one file given, and an array with 
      #   muliple files
      def delete_directory(*dirs)
        raise_if_forbidden_path_for_delete_operation(dirs)

        directories = expand_path(dirs.flatten)
        FileUtils.rm_r(directories)

        if directories.size == 1
          return directories.first
        else
          return directories
        end
      end

      # Check existence of path(s)
      #
      # @param [String,Array] paths
      #   which path(s) should be checked, multiple arguments are possible as well
      #
      # @return [TrueClass,FalseClass]
      #   the result of all checks done
      def path_exists?(*paths)
        raise_if_forbidden_path_for_create_operation(paths)

        paths_expanded = expand_path(paths.flatten)
        paths_expanded.flatten.all? { |p| ::File.exists?(p) }
      end

      # Check absence of path(s)
      #
      # @param [String,Array] paths
      #   which path(s) should be checked, multiple arguments are possible as well
      #
      # @return [TrueClass,FalseClass]
      #   the result of all checks done
      def path_does_not_exist?(*paths)
        not path_exists?(paths)
      end

      # Create a single file
      #
      # @param [String] file_path
      #   the path for the new file (can include directories)
      #
      # @param [String] content
      #   the content written to the file
      #
      # @return [String]
      #   the path to the created file
      def create_file(path, content='')
        raise_if_forbidden_path_for_create_operation(path)

        file = expand_path(path).first
        directory = ::File.dirname(file)

        FileUtils.mkdir_p(directory) unless directory == '.'
        ::File.open(file, "wb") do |f|
          f.write content
        end

        file
      end

      # Delete a single file
      #
      # @param [String] file_path
      #   the path for the new file (can include directories)
      #
      # @param [String] content
      #   the content written to the file
      #
      # @return [String]
      #   the path to the created file
      def delete_file(*files)
        raise_if_forbidden_path_for_delete_operation(files)

        files_to_be_deleted = expand_path(files.flatten)
        FileUtils.rm(files_to_be_deleted)

        if files_to_be_deleted.size == 1
          return files_to_be_deleted.first
        else
          return files_to_be_deleted
        end
      end

      # Read the content of a file
      #
      # @param [String] file_path
      #   the path to the file
      #
      # @return [String,Binary] 
      #   the content of the file
      def read_file(path)
        raise_if_forbidden_path_for_create_operation(path)

        file_path = expand_path(path).first
        return ::File.read(file_path)
      end

      # Expand path based on temporary directory
      #
      # @param [String, Array, Multiple Values] paths
      #   the paths to be expanded
      #
      # @return [Array, String]
      #   the expanded arrays
      def expand_path(*paths)
        raise_if_forbidden_path_for_create_operation(paths)

        paths.flatten.map do |p| 
          case p 
          when /^~/
            ::File.expand_path(p) 
          else
            ::File.join(working_directory, p ) 
          end
        end
      end

      # Check if path is forbidden for delete operation
      # 
      # @param [String, Array] paths
      #   paths which should be checked
      #
      # @return [TrueClass, FalseClass]
      #   true if path is forbidden, false if path is not forbidden
      def raise_if_forbidden_path_for_create_operation(*paths)
        flattend_paths = paths.flatten
        strings = []
        regex = %r[\.\.]

        raise FileSystem::Exceptions::InvalidPath , "Sorry, but you cannot use paths matching \"#{strings.join(', ')}\"  or \"#{regex.to_s}\" here!" if path_matches?(strings, regex, flattend_paths)
      end

      # Check if path is forbidden for delete operation
      #
      # @param [String, Array] paths
      #   paths which should be checked
      #
      # @return [TrueClass, FalseClass]
      #   true if path is forbidden, false if path is not forbidden
      def raise_if_forbidden_path_for_delete_operation(*paths)
        flattend_paths = paths.flatten
        strings = %w[ / ]
        regex = %r[\.\.]

        raise FileSystem::Exceptions::InvalidPath , "Sorry, but you cannot use paths matching \"#{strings.join(', ')}\"  or \"#{regex.to_s}\" here!" if path_matches?(strings, regex, flattend_paths)
      end

      # Check if path matches
      #
      # @param [Array, String] strings
      #   strings which are checked against paths
      #
      # @param [Array, String] regex
      #   regex which is checked against path
      #
      # @param [Array, String] paths
      #   the paths to be checked
      #
      # @result [TrueClass, FalseClass] 
      #   true if path is valid, false if invalid
      def path_matches?(strings, regex, *paths)
        flattend_paths = paths.flatten
        flattend_strings = strings.flatten

        flattend_paths.any? { |f|  f =~ regex or flattend_strings.include?(f) }
      end
    end
  end
end