local locMap = {}

-- Internationalization

local api = {

   apiLocationMap    = 'locationMap',
   apiAddLocation    = 'addLocation',
   apiAddObject      = 'addObject',
   apiGetMapValue    = 'getMapValue',
   apiGetMapValueSet = 'getMapValueSet',

}

local errMsgs = {

   anError       = 'Fehler',
   unknownMap    = 'Keine Karte für Region %s vorhanden',
   noMapImage    = 'Kein Kartenbild spezifiziert',
   wrongLat      = 'Breite %f liegt außerhalb der Kartenbegrenzungen',
   wrongLong     = 'Länge %f liegt außerhalb der Kartenbegrenzungen',
   wrongXBorders = 'Fehlende oder falsche horizontale Kartengrenzen',
   wrongYBorders = 'Fehlende oder falsche vertikale Kartengrenzen',
   noObject      = 'Kein Objekt angegeben',
   noXPos        = 'Keine horizontale Lage angegeben',
   noYPos        = 'Keine vertikale Lage angegeben',
   noParam       = 'Kein Parameter für getMapValue angegeben',
   notDefined    = 'Parameter nicht definiert',

}

local mapDocs = {

   tableClass    = 'prettytable',
   name          = 'Name',
   description   = 'Beschreibung',
   projection    = 'Projektion',
   top           = 'oben',
   bottom        = 'unten',
   left          = 'links',
   right         = 'rechts',
   default       = 'Standardkarte',
   relief        = 'Physische Karte',
   mark          = 'Marker',
   marksize      = 'Markergröße',
   linear        = 'Plattkarte',
   nonlinear     = 'Nichtlineare Projektion',

}

-- Style aliases

local mapStyles = {

   mitte         = "margin: 0 auto !important",
   center        = "margin: 0 auto !important",
   left          = "clear: left; margin: 0 1em 1em 0; float: left",
   links         = "clear: left; margin: 0 1em 1em 0; float: left",
   right         = "clear: right; margin: 0 0 1em 1em; float: right",
   rechts        = "clear: right; margin: 0 0 1em 1em; float: right",

}

local labelStyles = {

   bold          = "font-weight: bold",
   fett          = "font-weight: bold",
   italic        = "font-style: italic",
   kursiv        = "font-style: italic",
   underline     = "text-decoration: underline",
   letterspacing = "letter-spacing: 0.1em",
   wordspacing   = "word-spacing: 0.5em",
   smallcaps     = "font-variant: small-caps",
   uppercase     = "text-transform: uppercase",
   region        = "font-weight: bold; text-transform: uppercase; color: #646464",
   subregion     = "font-weight: bold; color: #646464",
   waterbody     = "font-weight: bold; font-style: italic; letter-spacing: 0.1em; text-transform: uppercase; color: #2A6DB5",
   mountain      = "font-weight: bold; font-style: italic; letter-spacing: 0.1em; color: #704040",

}

local labelPositions1 = {

   ["1"]         = "left: 0; bottom: msize_px;",
   ["2"]         = "left: msize3_px; bottom: -2px;",
   ["3"]         = "left: msize5_px; top: -2em; height: 4em;",
   right         = "left: msize5_px; top: -2em; height: 4em;",
   rechts        = "left: msize5_px; top: -2em; height: 4em;",
   ["4"]         = "left: msize3_px; top: -2px;",
   ["5"]         = "left: 0; top: msize_px;",
   ["6"]         = "left: -3em; top: msize3_px;",
   bottom        = "left: -3em; top: msize3_px;",
   unten         = "left: -3em; top: msize3_px;",
   ["7"]         = "right: 0; top: msize_px;",
   ["8"]         = "right: msize3_px; top: -2px;",
   ["9"]         = "right: msize5_px; top: -2em; height: 4em;",
   left          = "right: msize5_px; top: -2em; height: 4em;",
   links         = "right: msize5_px; top: -2em; height: 4em;",
   ["10"]        = "right: msize3_px; bottom: -2px;",
   ["11"]        = "right: 0; bottom: msize_px;",
   ["12"]        = "left: -3em; bottom: msize3_px;",
   top           = "left: -3em; bottom: msize3_px;",
   oben          = "left: -3em; bottom: msize3_px;",
   center        = "top: -2em; height: 4em; left: -3em;",
   mitte         = "top: -2em; height: 4em; left: -3em;",

}

local labelPositions2 = {

   ["1"]         = "text-align: left;",
   ["2"]         = "text-align: left;",
   ["3"]         = "text-align: left; height: 4em;",
   right         = "text-align: left; height: 4em;",
   rechts        = "text-align: left; height: 4em;",
   ["4"]         = "text-align: left;",
   ["5"]         = "text-align: left;",
   ["6"]         = "text-align: center;",
   bottom        = "text-align: center;",
   unten         = "text-align: center;",
   ["7"]         = "text-align: right;",
   ["8"]         = "text-align: right;",
   ["9"]         = "text-align: right; height: 4em;",
   left          = "text-align: right; height: 4em;",
   links         = "text-align: right; height: 4em;",
   ["10"]        = "text-align: right;",
   ["11"]        = "text-align: right;",
   ["12"]        = "text-align: center;",
   top           = "text-align: center;",
   oben          = "text-align: center;",
   center        = "text-align: center; height: 4em;",
   mitte         = "text-align: center; height: 4em;",

}

-- Local functions, please do not call them directly

local function split(s)

   local split = mw.text.split(s, ';')
   local result = {}
   for key,value in pairs(split) do
       tr = mw.text.trim(value)
       if tr ~=  then table.insert(result, tr) end
   end
   return result;

end

local function analyzeLabelStyle(style)

   local split = split(style)
   for key,value in pairs(split) do
       if labelStyles[value] ~= nil then split[key] = labelStyles[value] end
   end
   return table.concat(split, '; ') .. ';'

end

local function analyzeMapStyle(style)

   local split = split(style)
   for key,value in pairs(split) do
       if mapStyles[value] ~= nil then split[key] = mapStyles[value] end
   end
   return table.concat(split, '; ') .. ';'

end

local function setLocation(x, y, name, label, mark, marksize, labelStyle, labelWrap, labelPosition)

   local lmarksize = math.floor(marksize + 0.5)
   local msize = math.floor((marksize - 1) / 2 + 0.5)
   local msize3 = math.floor((marksize + 2) / 2 + 0.5)
   local msize5 = math.floor((marksize + 4) / 2 + 0.5)
   local halfMarkSize = -msize .. 'px'
   -- Setting a marker

local sCode = '

'
   if mark ~= 'none' then
sCode = sCode .. '
'
       .. '' .. name .. ''
.. '
'
   end
   -- Adding a label
   if (label ~= ) and (label ~= 'none') then
sCode = sCode .. '

<td style="border: none; padding: 0; vertical-align: middle; ' .. labelPositions2[labelPosition] else -- Automatic Estimation if y<=0.5 then sCode = sCode .. 'top: ' .. msize3 .. 'px; ' else sCode = sCode .. 'bottom: ' .. msize3 .. 'px; ' end if x<0.25 then sCode = sCode .. 'text-align: left; left: ' .. math.floor(3 - 60*x)/10 .. 'em;' .. '"><td style="border: none; padding: 0; vertical-align: middle; text-align: left;' else if x<0.75 then sCode = sCode .. 'text-align: center; left: -3em;' .. '">

' .. label .. '
'
   end
sCode = sCode .. '

'

   return sCode

end

local function baseMap(mapImage, description, mapStyle, width, caption, captionStyle, captionInnerBorder, captionOuterBorder, x, y, name, label, mark, marksize, labelStyle, labelWrap, labelPosition, places)

local sCode = '

' if caption ~= then sCode = sCode .. '' end sCode = sCode .. '

' else sCode = sCode .. '0;">' end sCode = sCode .. '
' else sCode = sCode .. ' width: auto !important;">' end sCode = sCode .. '
' .. description .. '
' .. description .. '
'
   if (x<0) or (x>1) or (y<0) or (y>1) then
sCode = sCode .. '
' .. 'Fehlerhafte Koordinate ' .. name .. '
'
   else
       sCode = sCode .. setLocation(x, y, name, label, mark, marksize, labelStyle, labelWrap, labelPosition)
   end
sCode = sCode .. places .. '
' .. caption .. '

'

   return sCode

end

-- Handling regional map data

local function getMapData(id)

   local region = require('Modul:Location map data ' .. id)
   if (region ~= nil) and (region.data ~= nil) then
       region.data['id'] = id
       return region.data
   else
       return nil
   end

end

local function getMapObject(id)

   local region = require('Modul:Location map data ' .. id)
   if (region ~= nil) and (region.data ~= nil) then
       region.data['id'] = id
       return region
   else
       return nil
   end

end

local function linearX(data, long)

   local left = data['left']
   local right = data['right']
   if (data == nil) or (left == nil) or (right == nil) or  (left == right) then
       -- Error
       return -1
   else if left < right then
           return (long - left) / (right - left)
       else
           if long < 0 then
               return (360 + long - left) / (360 + right - left)
           else
               return (long - left) / (360 + right - left)
           end
       end
   end

end

local function linearY(data, lat)

   local top = data['top']
   local bottom = data['bottom']
   if (data == nil) or (top == nil) or (bottom == nil) or  (top == bottom) then
       -- Error
       return -1
   else
       return (lat - top) / (bottom - top)
   end

end

local function getX(anObject, long, lat)

   if anObject.x ~= nil then
       return anObject.x(lat, long)
   else
   	return linearX(anObject.data, long)
   end

end

local function getY(anObject, long, lat)

   if anObject.y ~= nil then
       return anObject.y(lat, long)
   else
   	return linearY(anObject.data, lat)
   end

end

local function getMapImage(data, which)

   local image = data['default']
   if (which ~= ) and (data[which] ~= nil) and (data[which] ~=  ) then
       image = data[which]
   end
   return image

end

-- Parameters and error handling

local function argCheck(param, altValue)

   if param == nil then
       return altValue
   else
       local val = mw.text.trim(param)
       if val ==  then val = altValue end
       return val
   end

end

local function errorStr(s)

   return 'Fehler im Modul Location map: ' .. s .. ''

end

-- Map functions

local function apiLocationMap(args)

   local map = argCheck(args['map'], 'missing')
   local success, mObject = pcall(getMapObject, map)
   if not success then
       return errorStr(string.format(errMsgs['unknownMap'], map))
   else
       -- Error handling
       local errorMsgs = {}
       success = true
       
       -- Parameters check
       local mData = mObject.data
       local description = 
       if mData['description'] ~= nil then description = mData['description'] end
       local lat = tonumber(argCheck(tostring(args['lat']), 0))
       local long = tonumber(argCheck(tostring(args['long']), 0))
       local x = getX(mObject, long, lat)
       if (x<0) or (x>1) then
           success = false
           if x == -1 then
               table.insert(errorMsgs, errorStr(errMsgs['wrongXBorders']))
           else
               table.insert(errorMsgs, errorStr(string.format(errMsgs['wrongLong'], long)))
           end
       end
       local y = getY(mObject, long, lat)
       if (y<0) or (y>1) then
           success = false
           if y == -1 then
               table.insert(errorMsgs, errorStr(errMsgs['wrongYBorders']))
           else
               table.insert(errorMsgs, errorStr(string.format(errMsgs['wrongLat'], lat)))
           end
       end
       local maptype = argCheck(args['maptype'], 'default')
       local mapImage = argCheck(args['alternativeMap'], getMapImage(mData, maptype))
       if (mapImage == nil) or (mapImage == ) then
           success = false
           table.insert(errorMsgs, errorStr(errMsgs['noMapImage']))
       end
       if not success then
           return table.concat(errorMsgs, '
') else local name = argCheck(args['name'], ) local label = argCheck(args['label'], ) -- Checking width syntax local width = argCheck(tostring(args['width']), ) if (string.match(width, '^%d+$') == nil) and (string.match(width, '^%d*x%d+$') == nil) then width = '200x200' end if mData['mark'] ~= nil then mark = mData['mark'] else mark = 'Reddot.svg' end mark = argCheck(args['mark'], mark) if mData['marksize'] ~= nil then marksize = mData['marksize'] else marksize = 5 end local marksize = argCheck(args['marksize'], marksize) local mapStyle = argCheck(args['mapStyle'], 'mitte') local labelStyle = argCheck(args['labelStyle'], ) local labelBackground = argCheck(args['labelBackground'], ) if labelBackground ~= then labelBackground = 'background: ' .. labelBackground if labelStyle ~= then labelStyle = labelStyle .. '; ' .. labelBackground else labelStyle = labelBackground end end local labelWrap = argCheck(args['labelWrap'], 'auto') local labelPosition = argCheck(args['labelPosition'], 'auto') local caption = argCheck(args['caption'], ) local captionStyle = argCheck(args['captionStyle'], ) local captionInnerBorder = argCheck(args['captionInnerBorder'], '1px solid #cccccc') local captionOuterBorder = argCheck(args['captionOuterBorder'], '1px solid #cccccc') local places = argCheck(args['places'], )
           return baseMap(mapImage, description, mapStyle, width, caption, captionStyle,
           	captionInnerBorder, captionOuterBorder, x, y, name, label, mark, marksize,
           	labelStyle, labelWrap, labelPosition, places)
       end
   end

end

local function apiAddLocation(args)

   local map = argCheck(args['map'], 'missing')
   local success, mObject = pcall(getMapObject, map)
   if not success then
       return errorStr(string.format(errMsgs['unknownMap'], map))
   else
       -- Error handling
       local errorMsgs = {}
       success = true
       -- Parameters check
       local mData = mObject.data
       local lat = tonumber(argCheck(tostring(args['lat']), 0))
       local long = tonumber(argCheck(tostring(args['long']), 0))
       local x = getX(mObject, long, lat)
       if (x<0) or (x>1) then
           success = false
           if x == -1 then
               table.insert(errorMsgs, errorStr(errMsgs['wrongXBorders']))
           else
               table.insert(errorMsgs, errorStr(string.format(errMsgs['wrongLong'], long)))
           end
       end
       local y = getY(mObject, long, lat)
       if (y<0) or (y>1) then
           success = false
           if y == -1 then
               table.insert(errorMsgs, errorStr(errMsgs['wrongYBorders']))
           else
               table.insert(errorMsgs, errorStr(string.format(errMsgs['wrongLat'], lat)))
           end
       end
       if not success then
           return table.concat(errorMsgs, '
') else local name = argCheck(args['name'], ) local label = argCheck(args['label'], )
           if mData['mark'] ~= nil then mark = mData['mark'] else mark = 'Reddot.svg' end
           mark = argCheck(args['mark'], mark)
           if mData['marksize'] ~= nil then marksize = mData['marksize'] else marksize = 5 end
           local marksize = argCheck(args['marksize'], marksize)
           local mapStyle = argCheck(args['mapStyle'], 'mitte')
           local labelStyle = argCheck(args['labelStyle'], )
           local labelBackground = argCheck(args['labelBackground'], )
           if labelBackground ~= then
               labelBackground = 'background: ' .. labelBackground
               if labelStyle ~= then
                   labelStyle = labelStyle .. '; ' .. labelBackground
               else
                   labelStyle = labelBackground
               end
           end
           local labelWrap = argCheck(args['labelWrap'], 'auto')
           local labelPosition = argCheck(args['labelPosition'], 'auto')
           return setLocation(x, y, name, label, mark, marksize, labelStyle, labelWrap, labelPosition)
       end
   end

end

local function apiAddObject(args)

   local anObject = argCheck(args['object'], )
   if anOject ==  then
       return errorStr(errMsgs['noObject'])
   else
       local success = true
       local errorMsgs = {}
       
       local right = argCheck(args['right'], )
       local left = argCheck(args['left'], )
       if (right == ) and (left == ) then
           success = false
           table.insert(errorMsgs, errorStr(errMsgs['noXPos']))
       end
       local top = argCheck(args['top'], )
       local bottom = argCheck(args['bottom'], )
       if (top == ) and (bottom == ) then
           success = false
           table.insert(errorMsgs, errorStr(errMsgs['noYPos']))
       end
       if not success then
           return table.concat(errorMsgs, '
') else local objectStyle = argCheck(args['objectStyle'], ) local objectBackground = argCheck(args['objectBackground'], ) if objectBackground ~= then objectBackground = 'background: ' .. objectBackground if objectStyle ~= then objectStyle = objectStyle .. '; ' .. objectBackground else objectStyle = objectBackground end end

sCode = '

' .. anObject .. '

'

           return sCode
       end
   end

end

-- Documentation of map data

local function apiGetMapValue(args)

   local map = argCheck(args['map'], 'missing')
   local success, mData = pcall(getMapData, map)
   if not success then
       return errorStr(string.format(errMsgs['unknownMap'], map))
   else
       local param = argCheck(args['param'], )
       if param ==  then
           return errorStr(errMsgs['noParam'])
       else
           if mData[param] == nil then
               return errMsgs['anError']
           else
               return mData[param]
           end
       end
   end

end

local function apiGetMapValueSet(args)

   local map = argCheck(args['map'], 'missing')
   local success, mData = pcall(getMapData, map)
   if not success then
       return errorStr(string.format(errMsgs['unknownMap'], map))
   else
       local paramList = {'name', 'description', 'projection', 'top', 'bottom',
          'left', 'right', 'default', 'relief', 'mark', 'marksize'}

local sCode = '

' for i = 1, #paramList, 1 do sCode = sCode .. '' else local v = mData[paramList[i]] if mapDocs[v] ~= nil then v = mapDocs[v] end sCode = sCode .. v .. '' end end return sCode .. '

' .. mapDocs[paramList[i]] .. ''
           if mData[paramList[i]] == nil then
sCode = sCode .. errMsgs['notDefined'] .. '

'

   end

end

-- API function calls

locMap[api.apiLocationMap] = function (frame)

   return apiLocationMap(frame.args)

end

locMap[api.apiAddLocation] = function (frame)

   return apiAddLocation(frame.args)

end

locMap[api.apiAddObject] = function (frame)

   return apiAddObject(frame.args)

end

locMap[api.apiGetMapValue] = function (frame)

   return apiGetMapValue(frame.args)

end

locMap[api.apiGetMapValueSet] = function (frame)

   return apiGetMapValueSet(frame.args)

end

-- Example for usage in a Lua script

function locMap.exampleLuaCall()

   local frame = {}
   frame.args = {
       map   = 'de',
       lat   = 52.51789,
       long  = 13.38873,
       name  = 'Berlin',
       label = 'Berlin',
   }
   return locMap.locationMap(frame)

end

return locMap