require 'win32/taskscheduler/time_calc_helper'
require 'spec_helper'

RSpec.describe Win32::TaskScheduler::TimeCalcHelper do
  let(:object) { klass.new }
  let(:klass) do
    Class.new do
      include Win32::TaskScheduler::TimeCalcHelper
    end
  end

  describe 'Format Test:' do
    context 'An Invalid Date-Time string' do
      time_str = 'An Invalid String'
      let(:time_details) { object.time_details(time_str) }
      it 'Returns an empty hash' do
        expect(time_details).to be_a(Hash)
        expect(time_details).to be_empty
      end
    end

    context 'A valid Date string' do
      time_str = 'P1Y2M3D'
      let(:time_details) { object.time_details(time_str) }
      it 'Returns a valid Hash with only Date values' do
        expect(time_details).to be_a(Hash)
        expect(time_details[:year]).to eql('1')
        expect(time_details[:month]).to eql('2')
        expect(time_details[:day]).to eql('3')
        expect(time_details[:hour]).to be_nil
        expect(time_details[:min]).to be_nil
        expect(time_details[:sec]).to be_nil
      end
    end

    context 'A valid Time string' do
      time_str = 'PT4H5M6S'
      let(:time_details) { object.time_details(time_str) }
      it 'Returns a valid Hash with only Time values' do
        expect(time_details).to be_a(Hash)
        expect(time_details[:year]).to be_nil
        expect(time_details[:month]).to be_nil
        expect(time_details[:day]).to be_nil
        expect(time_details[:hour]).to eql('4')
        expect(time_details[:min]).to eql('5')
        expect(time_details[:sec]).to eql('6')
      end
    end

    context 'A valid Date-Time string' do
      time_str = 'P1Y2M3DT4H5M6S'
      let(:time_details) { object.time_details(time_str) }
      it 'returns a valid Hash with values' do
        expect(time_details).to be_a(Hash)
        expect(time_details[:year]).to eql('1')
        expect(time_details[:month]).to eql('2')
        expect(time_details[:day]).to eql('3')
        expect(time_details[:hour]).to eql('4')
        expect(time_details[:min]).to eql('5')
        expect(time_details[:sec]).to eql('6')
      end
    end

    context 'An Unformatted Date-Time string' do
      time_str = 'P2M3D1YT6S5M4H'
      let(:time_details) { object.time_details(time_str) }
      it 'Returns a valid Hash with values' do
        expect(time_details).to be_a(Hash)
        expect(time_details[:year]).to eql('1')
        expect(time_details[:month]).to eql('2')
        expect(time_details[:day]).to eql('3')
        expect(time_details[:hour]).to eql('4')
        expect(time_details[:min]).to eql('5')
        expect(time_details[:sec]).to eql('6')
      end
    end

    context 'A Date-Time string with few parameters' do
      time_str = 'P1Y2MT3M4S'
      let(:time_details) { object.time_details(time_str) }
      it 'Returns a valid Hash with selected values' do
        expect(time_details).to be_a(Hash)
        expect(time_details[:year]).to eql('1')
        expect(time_details[:month]).to eql('2')
        expect(time_details[:day]).to be_nil
        expect(time_details[:hour]).to be_nil
        expect(time_details[:min]).to eql('3')
        expect(time_details[:sec]).to eql('4')
      end
    end
  end

  describe 'Year conversion:' do
    # Time.now to be a Non-Leap Year(0001-01-01 00:00:00), So that
    # Tests suites will be independant of their execution Time
    before(:each) do
      allow(Time).to receive(:now).and_return(Time.new(1))
    end

    let(:current_time) { Time.new(1) }
    context 'on given year' do
      time_str = 'P1Y'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected year' do
        expect(next_time.year).to eql(current_time.year + 1)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'on given months' do
      time_str = 'P12M'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected year' do
        expect(next_time.year).to eql(current_time.year + 1)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'on given days' do
      time_str = 'P365D'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected year' do
        expect(next_time.year).to eql(current_time.year + 1)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'by given year and month' do
      time_str = 'P1Y12M'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected year' do
        expect(next_time.year).to eql(current_time.year + 2)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'by given year, months and days' do
      time_str = 'P1Y12M365D'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'Returns the actual incremented day' do
        expect(next_time.year).to eql(current_time.year + 3)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end
  end

  describe 'Month conversion:' do
    # Time.now to be a Non-Leap Year(0001-01-01 00:00:00), So that
    # Tests suites will be independant of their execution Time
    before(:each) do
      allow(Time).to receive(:now).and_return(Time.new(1))
    end

    let(:current_time) { Time.new(1) }
    context 'on given month' do
      time_str = 'P1M'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected month' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month + 1)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'on given days' do
      # Since we are running test on 1st month, next month will occur in 31 days
      time_str = 'P31D'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected month' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month + 1)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'by given month and days' do
      # Since we are running test on 1st month, next month will occur in 31 days
      time_str = 'P1M31D'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected month' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month + 2)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end
  end

  describe 'Day conversion:' do
    # Time.now to be a Non-Leap Year(0001-01-01 00:00:00), So that
    # Tests suites will be independant of their execution Time
    before(:each) do
      allow(Time).to receive(:now).and_return(Time.new(1))
    end

    let(:current_time) { Time.new(1) }
    context 'on given days' do
      time_str = 'P1D'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected days' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day + 1)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'on given hours' do
      time_str = 'PT24H'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected days' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day + 1)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'on given minutes' do
      time_str = 'PT1440M'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected days' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day + 1)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'on given seconds' do
      time_str = 'PT86400S'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected days' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day + 1)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'By the given days, hours, minutes and seconds' do
      time_str = 'P1DT24H1440M86400S'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected days' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day + 4)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end
  end

  describe 'Minute conversion:' do
    # Time.now to be a Non-Leap Year(0001-01-01 00:00:00), So that
    # Tests suites will be independant of their execution Time
    before(:each) do
      allow(Time).to receive(:now).and_return(Time.new(1))
    end

    let(:current_time) { Time.new(1) }
    context 'on given minute' do
      time_str = 'PT1M'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected minutes' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min + 1)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'on given seconds' do
      time_str = 'PT60S'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected minutes' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min + 1)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'By given minutes and seconds' do
      time_str = 'PT1M60S'
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected minutes' do
        expect(next_time.year).to eql(current_time.year)
        expect(next_time.month).to eql(current_time.month)
        expect(next_time.day).to eql(current_time.day)
        expect(next_time.hour).to eql(current_time.hour)
        expect(next_time.min).to eql(current_time.min + 2)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end
  end

  describe 'Date-Time conversions:' do
    # For a single string given in leap/non-leap year
    time_str = 'P400Y500M600DT700H800M900S'

    context 'In a non leap year' do
      let(:current_time) { Time.new(1) }
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected values' do
        allow(Time).to receive(:now).and_return(Time.new(1))

        expect(next_time.year).to eql(current_time.year + 443)
        expect(next_time.month).to eql(current_time.month + 4)
        expect(next_time.day).to eql(current_time.day + 21)
        expect(next_time.hour).to eql(current_time.hour + 17)
        expect(next_time.min).to eql(current_time.min + 35)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end

    context 'In a leap year' do
      let(:current_time) { Time.new(0) }
      let(:time_seconds) { object.time_in_seconds(time_str) }
      let(:next_time) { current_time + time_seconds }
      it 'returns expected values' do
        allow(Time).to receive(:now).and_return(Time.new(0))

        # 1 Day less for the same string
        expect(next_time.year).to eql(current_time.year + 443)
        expect(next_time.month).to eql(current_time.month + 4)
        expect(next_time.day).to eql(current_time.day + 20)
        expect(next_time.hour).to eql(current_time.hour + 17)
        expect(next_time.min).to eql(current_time.min + 35)
        expect(next_time.sec).to eql(current_time.sec)
      end
    end
  end
end