# Copyright (c) 2023 M.J.N. Corino, The Netherlands
#
# This software is released under the MIT license.

###
# wxRuby3 buildtools configuration
###

require 'tempfile'
require 'fileutils'

module WXRuby3

  class Stream

    class << self
      private

      def _stack
        Thread.current[:stream_transaction_stack] ||= []
      end

      def _start_transaction
        _stack << []
      end

      def _close_transaction
        _stack.pop
      end

      def _transaction
        _stack.last
      end

      def _commit
        _transaction.reject! { |stream| stream.save; true }
      end

      def _rollback
        _transaction.reject! { |stream| stream.remove; true } if _transaction
      end

      def _push(stream)
        _transaction << stream if _transaction
      end

    end
    def self.transaction(&block)
      _start_transaction
      begin
        block.call if block_given?
        _commit
      ensure
        _rollback # after successful transaction should be nothing left
        _close_transaction
      end
    end

    def self.rollback
      _rollback
    end

    module OutputMethods
      def <<(txt)
        lns = txt.split("\n", -1)
        last_ln = lns.pop
        lns.each {|ln| puts(ln) }
        indented_put(last_ln)
        self
      end

      def puts(txt='')
        if ::Array === txt
          txt.each { |ln| puts(ln) }
        elsif (lns = txt.split("\n", -1)).size>1
          lns.each { |ln| puts(ln) }
        else
          indented_put(txt).puts
          @indent_next = true
        end
        self
      end

      def iputs(txt='', lvl_inc=1)
        indent(lvl_inc) do
          puts(txt)
        end
        self
      end

      def indent(lvl_inc=1, &block)
        prev_level = @indent_level
        begin
          @indent_level += lvl_inc
          @indent_next = true
          block.call
        ensure
          @indent_level = prev_level
        end
      end
    end

    include OutputMethods

    def initialize(path, indent: 2)
      if path
        @path = path
        @fullpath = File.expand_path(path)
        @name = File.basename(path)
        @ext = File.extname(path).sub(/^\./, '')
      else
        @path = @fullpath = @name = @ext = ''
      end
      @fout = Tempfile.new(@name)
      Stream.__send__(:_push, self)
      @indent = indent
      @indent_level = 0
      @indent_next = true
    end

    attr_reader :path, :fullpath, :name, :ext

    def save
      if @fout
        fgen = @fout
        @fout = nil
        fgen.close(false) # close but do NOT unlink
        if File.exist?(@fullpath)
          # create temporary backup
          ftmp = Tempfile.new(@name)
          ftmp_name = ftmp.path.dup
          ftmp.close(true) # close AND unlink
          FileUtils::mv(@fullpath, ftmp_name) # backup existing file
          # replace original
          begin
            # rename newly generated file
            FileUtils.mv(fgen.path, @fullpath)
            # preserve file mode
            FileUtils.chmod(File.lstat(ftmp_name).mode, @fullpath)
          rescue Exception
            # restore backup
            FileUtils.mv(ftmp_name, @fullpath)
            raise
          end
          # remove backup
          File.unlink(ftmp_name)
        else
          # make sure the file's folder exists
          FileUtils.mkdir_p(File.dirname(@fullpath))
          # just rename newly generated file
          FileUtils.mv(fgen.path, @fullpath)
          # set default mode for new files
          FileUtils.chmod(0666 - File.umask, @fullpath)
        end
      end
    end

    def remove
      if @fout
        @fout.close(true)
        @fout = nil
      end
    end

    private

    def indented_put(s)
      @fout << (' ' * ((@indent_level) * @indent)) if @indent_next
      @fout << s
      @indent_next = false
      @fout
    end

  end

  class CodeStream < Stream

    class Doc

      include Stream::OutputMethods

      def initialize(stream)
        @stream = stream
        @indent_level = 0
        @indent_next = true
      end

      private

      def indented_put(s)
        @stream << '# ' if @indent_next
        @stream << (' ' * ((@indent_level) * @stream.instance_variable_get('@indent'))) if @indent_next
        @stream << s
        @indent_next = false
        @stream
      end
    end

    def initialize(path, indent: 2)
      super
      @doc = nil
    end

    def doc
      @doc ||= Doc.new(self)
    end

  end

end
