--[[
  Copyright (c) 2024 by Plexim GmbH
  All rights reserved.

  A free license is granted to anyone to use this software for any legal
  non safety-critical purpose, including commercial applications, provided
  that:
  1) IT IS NOT USED TO DIRECTLY OR INDIRECTLY COMPETE WITH PLEXIM, and
  2) THIS COPYRIGHT NOTICE IS PRESERVED in its entirety.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.
--]]

local U = require('common.utils')

local VersionNumber = {}

function VersionNumber.stripVersion(rawString, numSignificantDigits)
  -- Create a pattern to capture the first 'significantDigits' parts of the version
  local pattern = '^'
  for i = 1, numSignificantDigits do
    pattern = pattern..'(%d+)'
    if i < numSignificantDigits then
      pattern = pattern..'%.'        -- Add dot after each part except the last
    end
  end

  -- Capture the matching parts
  local result = {rawString:match(pattern)}
  if #result == 0 then
    return rawString -- the pattern was too long, just return the original
  end

  -- Return the joined result with dots
  return table.concat(result, '.')
end

--[[
    Utility function for creating a JLink version number with the expected
    format of 'V7.94a'. This function provides a custom regex to match
    '7', '94', 'a'.
--]]
function VersionNumber.newJLink(versionAsString)
  return VersionNumber.new(versionAsString, 'V(%d+).(%d+)(%a)')
end

--[[ Create version number fron CGT string: '20.2.1.LTS']]
function VersionNumber.newCGT(versionAsString)
  return VersionNumber.new(versionAsString, '(%d+).(%d+).(%d+).[LS]TS')
end

--[[
    Returns a VersionNumber object which can be used to compare versions
    or nil if the input is not a valid VersionNumber. 
    (In the case of Target.Version = 'dev', we do not want to error.)

    An opt_regex can be supplied for complex version the numbers, the default
    works for the common case: 1.23.4.5, etc.
--]]
function VersionNumber.new(versionAsString, opt_regex)
  regex = opt_regex or '^(%d+).(%d+).(%d+).(%d+)'  -- default for '01.23.444.5'
  regex = regex..'.(%d+).(%d+)'                -- pad to ensure minimum # of groups
  if not (type(versionAsString) == 'string') then
    U.warning('Version must be provided as a string: Major.Minor.opt_bugfix.opt_hash')
    return
  end

  local major, minor, bugfix, hash = string.match(
    versionAsString..'.0.0.0.0.0.0',  -- append to fill in missing minor/bugfix/hash as zero
    regex)

  if not (major and minor and bugfix and hash) then
    -- No valid format found, log a warning and return nil (will not error).
    -- However, suppress this warning for 'dev' which is the normal case
    -- for the TSP version in main.
    if versionAsString ~= 'dev' and versionAsString ~= 'rel' then 
      U.warning('Version number is not provided in a valid format: '..versionAsString)
    end
    return
  end

  local versionNumber = {
    -- 'numbers' are stored as numbers, unless
    -- they can't be resolved as a number (ex: the 'b' in JLink V7.5b), 
    -- in which case that bit is stored as a string.
    major = tonumber(major) or major,
    minor = tonumber(minor) or minor,
    bugfix = tonumber(bugfix) or bugfix,
    hash = tonumber(hash) or hash,
    versionAsString = versionAsString,
    _isVersionNumber = true, -- private flag for tracking datatype, used in compare metamethods
  }

  function versionNumber:debug()
    return 'Version Number: '..self.major..'.'..self.minor..'.'..self.bugfix..'.'..self.hash
  end

  function versionNumber:majorMinorAsHex()
    U.devWarning('This function is probably not what you want and should be deprecated/removed!')
    -- convert Major.Minor to a hex number:
    -- 1.8 >> 0108
    return '%04X' % {256 * self.major + self.minor}
  end

  function versionNumber:majorMinorAs4DigitString()
    -- convert Major.Minor to 4 digit "hex" number
    -- 1.8 >> 0108  and  10.45 >> 1045
    if self.major < 100 and self.minor < 100 then
      return '%04d' % {100 * self.major + self.minor}
    end
    -- otherwise these numbers are too big to represent in 4 digits:
    error('This version number cannot be represented in 4 digits: '..self.versionAsString)
  end

  local versionNumber_mt = {
    __tostring = function (a)
      return versionAsString
    end,
    __eq = function (a, b)
      return a.major == b.major
         and a.minor == b.minor
         and a.bugfix == b.bugfix
         and a.hash == b.hash
    end,
    __lt = function (a, b)
      if  type(a) == 'table' and a._isVersionNumber
      and type(b) == 'table' and b._isVersionNumber then
        return (a.major < b.major)
           or (a.major == b.major and a.minor < b.minor)
           or (a.major == b.major and a.minor == b.minor and a.bugfix < b.bugfix)
           or
           (a.major == b.major and a.minor == b.minor and a.bugfix == b.bugfix and a.hash < b.hash)
      else
        error('VersionNumber cannot compare against other types')
      end
    end,
    __le = function (a, b)
      if  type(a) == 'table' and a._isVersionNumber
      and type(b) == 'table' and b._isVersionNumber then
        return (a.major < b.major)
           or (a.major == b.major and a.minor < b.minor)
           or (a.major == b.major and a.minor == b.minor and a.bugfix < b.bugfix)
           or
           (a.major == b.major and a.minor == b.minor and a.bugfix == b.bugfix and a.hash <= b.hash)
      else
        error('VersionNumber cannot compare against other types')
      end
    end,
  }

  setmetatable(versionNumber, versionNumber_mt)

  return versionNumber
end

return VersionNumber
