local super = require "Object"

FilterRule = super:new()

local test = {}

test.numberIsEqualTo = 'number is equal to'
test.numberIsGreaterThanOrEqualTo = 'number is greater than or equal to'
test.numberIsLessThanOrEqualTo = 'number is less than or equal to'
test.numberIsGreaterThan = 'number is greater than'
test.numberIsLessThan = 'number is less than'

test.dateIsOn = 'date is on'
test.dateIsBefore = 'date is before'
test.dateIsAfter = 'date is after'

test.textContains = 'text contains'
test.textDoesNotContain = 'text does not contain'
test.textBeginsWith = 'text begins with'
test.textEndsWith = 'text ends with'
test.textIsEqualTo = 'text is equal to'

test.valueIsTrue = 'value is true'
test.valueIsFalse = 'value is false'
test.valueIsAbsent = 'value is absent'
test.valueIsPresent = 'value is present'

FilterRule.test = test

local defaults = {
}

local nilDefaults = {
    'artifact', 'test', 'testOptions',
}

function FilterRule:new()
    self = super.new(self)
    
    for k, v in pairs(defaults) do
        self:addProperty(k, v)
    end
    for _, k in pairs(nilDefaults) do
        self:addProperty(k)
    end
    
    return self
end

function FilterRule:unarchiveTestOptions(archived)
    local unarchived
    if archived then
        unarchived = {}
        for key, value in pairs(archived) do
            unarchived[key] = unarchive(value)
        end
    end
    self:setProperty('testOptions', unarchived)
end

function FilterRule:getTestGroups()
    return {
        {'Number', 'number', {test.numberIsEqualTo, test.numberIsGreaterThanOrEqualTo, test.numberIsLessThanOrEqualTo, test.numberIsGreaterThan, test.numberIsLessThan}},
        {'Text', 'text', {test.textIsEqualTo, test.textContains, test.textDoesNotContain, test.textBeginsWith, test.textEndsWith}},
        {'Date', 'date', {test.dateIsOn, test.dateIsBefore, test.dateIsAfter}},
        {'Value', 'value', {test.valueIsPresent, test.valueIsAbsent, test.valueIsTrue, test.valueIsFalse}},
    }
end

local testTitles = {
    [test.numberIsEqualTo] = 'equals',
    [test.numberIsGreaterThanOrEqualTo] = 'is at least',
    [test.numberIsLessThanOrEqualTo] = 'is at most',
    [test.numberIsGreaterThan] = 'is greater than',
    [test.numberIsLessThan] = 'is less than',
    [test.textIsEqualTo] = 'is',
    [test.textContains] = 'contains',
    [test.textDoesNotContain] = 'does not contain',
    [test.textBeginsWith] = 'begins with',
    [test.textEndsWith] = 'ends with',
    [test.dateIsOn] = 'is on',
    [test.dateIsBefore] = 'is before',
    [test.dateIsAfter] = 'is after',
    [test.valueIsPresent] = 'is not blank',
    [test.valueIsAbsent] = 'is blank',
    [test.valueIsTrue] = 'is true',
    [test.valueIsFalse] = 'is false',
}

function FilterRule:getTestTitle(testIdentifier)
    return testTitles[testIdentifier]
end

local fn = LogicLibrary.functions

local function numberEvaluator(func)
    return function(sequence, options)
        local value = Sequence:newWithScalar(options.value)
        return func(sequence, value)
    end
end

local function dateEvaluator(func)
    return function(sequence, options)
        local value = options.value
        local startValue = value and Sequence:newWithScalar(Date:get(value:year(), value:month(), value:day()))
        local endValue = value and Sequence:newWithScalar(Date:get(value:year(), value:month(), value:day()) + Duration:get(0, 0, 1))
        return func(sequence, startValue, endValue)
    end
end

local function textEvaluator(func)
    return function(sequence, options)
        local value = Sequence:newWithScalar(options.value)
        if not options.caseSensitive then
            sequence = fn.UPPER(sequence)
            value = fn.UPPER(value)
        end
        return func(sequence, value)
    end
end

local sequenceEvaluators = {
    [test.numberIsEqualTo] = numberEvaluator(function(sequence, value)
        return sequence:____eq(value)
    end),
    [test.numberIsGreaterThanOrEqualTo] = numberEvaluator(function(sequence, value)
        return sequence:____ge(value)
    end),
    [test.numberIsLessThanOrEqualTo] = numberEvaluator(function(sequence, value)
        return sequence:____le(value)
    end),
    [test.numberIsGreaterThan] = numberEvaluator(function(sequence, value)
        return sequence:____gt(value)
    end),
    [test.numberIsLessThan] = numberEvaluator(function(sequence, value)
        return sequence:____lt(value)
    end),
    [test.dateIsOn] = dateEvaluator(function(sequence, startValue, endValue)
        return LogicLibrary.conjunction(sequence:____ge(startValue), sequence:____le(endValue))
    end),
    [test.dateIsBefore] = dateEvaluator(function(sequence, startValue, endValue)
        return sequence:____lt(startValue)
    end),
    [test.dateIsAfter] = dateEvaluator(function(sequence, startValue, endValue)
        return sequence:____gt(endValue)
    end),
    [test.textContains] = textEvaluator(function(sequence, value)
        return fn.CONTAINS(sequence, value)
    end),
    [test.textDoesNotContain] = textEvaluator(function(sequence, value)
        return fn.NOT(fn.CONTAINS(sequence, value))
    end),
    [test.textBeginsWith] = textEvaluator(function(sequence, value)
        return fn.SUB(sequence, Sequence:newWithScalar(1), fn.LENGTH(value)):____eq(value)
    end),
    [test.textEndsWith] = textEvaluator(function(sequence, value)
        return fn.SUB(sequence, fn.LENGTH(value):__unm()):____eq(value)
    end),
    [test.textIsEqualTo] = textEvaluator(function(sequence, value)
        return sequence:____eq(value)
    end),
    [test.valueIsTrue] = function(sequence) return sequence end,
    [test.valueIsFalse] = function(sequence) return fn.NOT(sequence) end,
    [test.valueIsAbsent] = function(sequence) return fn.NOT(fn.ISVALUE(sequence)) end,
    [test.valueIsPresent] = function(sequence) return fn.ISVALUE(sequence) end,
}

function FilterRule:evaluate(dataset)
    local artifact = self:getArtifact()
    local sequence = (artifact and artifact:evaluate(dataset)) or Sequence:newWithArray({})
    local test = self:getTest()
    if sequenceEvaluators[test] then
        sequence = sequenceEvaluators[test](sequence, self:getTestOptions() or {})
    end
    return sequence
end

function FilterRule:setArtifact(artifact)
    self:setProperty('artifact', artifact)
end

function FilterRule:getArtifact()
    return self:getProperty('artifact') or IndexArtifact:new()
end

function FilterRule:setTest(test)
    self:setProperty('test', test)
end

function FilterRule:getTest()
    local test = self:getProperty('test')
    if not sequenceEvaluators[test] then
        test = FilterRule.test.valueIsPresent
    end
    return test
end

function FilterRule:getTestOptions()
    return self:getProperty('testOptions')
end

function FilterRule:setNumberTestOptions(value)
    self:setProperty('testOptions', { value = value })
end

function FilterRule:setTextTestOptions(value, caseSensitive)
    self:setProperty('testOptions', { value = value, caseSensitive = caseSensitive })
end

function FilterRule:setDateTestOptions(value)
    self:setProperty('testOptions', { value = value })
end

function FilterRule:setValueTestOptions()
    self:setProperty('testOptions', {})
end

return FilterRule