local super = require "Object"

Rect = super:new()

local Rect = Rect
local _min = math.min
local _max = math.max

function Rect:new(rect)
    self = super.new(self)
    
    if rect then
        self:initialize(rect)
    end
    
    return self
end

function Rect.__eq(rect1, rect2)
    return (rect1.left == rect2.left) and (rect1.bottom == rect2.bottom) and (rect1.right == rect2.right) and (rect1.top == rect2.top)
end

function Rect:point(x, y)
    return Rect:new{left = x, bottom = y, right = x, top = y}
end

function Rect:zero()
    return Rect:point(0, 0)
end

function Rect:initialize(rect)
    self.left = Rect.minx(rect)
    self.bottom = Rect.miny(rect)
    self.right = Rect.maxx(rect)
    self.top = Rect.maxy(rect)
end

function Rect:__tostring()
    return '{ left = ' .. self.left .. ', bottom = ' .. self.bottom .. ', right = ' .. self.right .. ', top = ' .. self.top .. ' }'
end

function Rect:archive()
    local typeName, properties = self:class(), {
        left = self.left,
        bottom = self.bottom,
        right = self.right,
        top = self.top
    }
    return typeName, properties
end

function Rect:minx()
    return _min(self.left, self.right)
end

function Rect:midx()
    return (self.left + self.right) / 2
end

function Rect:maxx()
    return _max(self.left, self.right)
end

function Rect:width()
    return self.right - self.left
end

function Rect:miny()
    return _min(self.bottom, self.top)
end

function Rect:midy()
    return (self.bottom + self.top) / 2
end

function Rect:maxy()
    return _max(self.bottom, self.top)
end

function Rect:height()
    return self.top - self.bottom
end

function Rect:copy()
    return Rect:new(self)
end

function Rect:normalize()
    return Rect:new(self)
end

function Rect:mapPointX(fraction)
    return self:minx() + fraction * self:width()
end

function Rect:mapPointY(fraction)
    return self:miny() + fraction * self:height()
end

function Rect:mapPoint(xFraction, yFraction)
    return self:mapPointX(xFraction), self:mapPointY(yFraction)
end

function Rect:inset(delta)
    return Rect:new{
        left = self.left + delta.left,
        bottom = self.bottom + delta.bottom,
        right = self.right - delta.right,
        top = self.top - delta.top,
    }
end

function Rect:expand(delta)
    return Rect:new{
        left = self.left - delta.left,
        bottom = self.bottom - delta.bottom,
        right = self.right + delta.right,
        top = self.top + delta.top,
    }
end

function Rect:insetXY(dx, dy)
    return Rect:new{
        left = self.left + dx,
        bottom = self.bottom + dy,
        right = self.right - dx,
        top = self.top - dy,
    }
end

function Rect:offset(dx, dy)
    return Rect:new{
        left = self.left + dx,
        bottom = self.bottom + dy,
        right = self.right + dx,
        top = self.top + dy,
    }
end

function Rect:union(other)
    return Rect:new{
        left = _min(self.left, other.left),
        bottom = _min(self.bottom, other.bottom),
        right = _max(self.right, other.right),
        top = _max(self.top, other.top),
    }
end

function Rect:intersection(other)
    return Rect:new{
        left = _max(self.left, other.left),
        bottom = _max(self.bottom, other.bottom),
        right = _min(self.right, other.right),
        top = _min(self.top, other.top),
    }
end

function Rect:intersects(other)
    if _max(self.left, other.left) <= _min(self.right, other.right) then
        if _max(self.bottom, other.bottom) <= _min(self.top, other.top) then
            return true
        end
    end
    return false
end

function Rect:contains(x, y)
    return (self.left <= x and x <= self.right and self.bottom <= y and y <= self.top)
end

return Rect
