--[[
  Copyright Tom Lukeywood 2018
    This file is part of Forest2D

    Forest2D is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Forest2D is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Forest2D.  If not, see <http://www.gnu.org/licenses/>.
]]--

World = {}

--Default values
World.SizeOfTile = 32
World.DefaultTileID = 2

function World.New(Renderer, Tileset, Animationset, TileSize, SizeW, SizeZ, SizeY, SizeX)

    local self = {}

    self.SizeX = SizeX
    self.SizeY = SizeY
    self.SizeZ = SizeZ
    self.SizeW = SizeW
    self.Tileset = Tileset

    self.SpriteList = {}

    function self.SpriteList.Push(Sprite)
      self.SpriteList[#self.SpriteList+1] = Sprite
    end

    function self.SpriteList.Pop()
      self.SpriteList[#self.SpriteList] = nil
    end

    self.Animationset = Animationset
    self.SizeOfTile = TileSize
    World.SizeOfTile = TileSize

    function self:GetTile(W, Z, Y, X, Member) return Forest2D.Database.get_tile(W, Z, Y, X, Member) end
    function self:SetTile(W, Z, Y, X, Value, Member) Forest2D.Database.set_tile(W, Z, Y, X, Value, Member) end
    function self:Init() Forest2D.Database.init_world(self.SizeW, self.SizeZ, self.SizeY, self.SizeX) end
    function self:Free() Forest2D.Database.free_world() end

    self:Init()

    --Set default camera values
    self.Camera = {}
    --Camera should fill screen
    self.Camera.Width = ((Forest2D.WINDOW_WIDTH) / self.SizeOfTile * self.SizeOfTile)
    self.Camera.Height = ((Forest2D.WINDOW_HEIGHT) / self.SizeOfTile * self.SizeOfTile)
    --Default camera position is top left corner
    self.Camera.PosX = 0
    self.Camera.PosY = 0

    function self:Load(Worldfile, SpriteListFile)
        -- Load tilemap data.
        local File = io.open(Worldfile, "r") local Contents = File:read("*a") io.close(File)
        local J, Buffer = 1, ''
        local TileIDs, AnimationIDs, SolidityStates = {}, {}, {}

        for I = 1, #Contents do
            if Contents:sub(I,I) == '{' then I = Inc(I)

                while Contents:sub(I,I) ~= ':' do
                    Buffer = Buffer .. Contents:sub(I,I)
                    I = Inc(I)
                end
                I = Inc(I)

                TileIDs[J] = tonumber(Buffer)
                Buffer = ''

                while Contents:sub(I,I) ~= ':' do
                    Buffer = Buffer .. Contents:sub(I,I)
                    I = Inc(I)
                end
                I = Inc(I)

                AnimationIDs[J] = tonumber(Buffer)
                Buffer = ''

                while Contents:sub(I,I) ~= '}' do
                    Buffer = Buffer .. Contents:sub(I,I)
                    I = Inc(I)
                end
                I = Inc(I)

                SolidityStates[J] = tonumber(Buffer)
                Buffer = ''
                J = Inc(J)
            end
        end
        J = 1
        for W = 1, self.SizeW do
         for Z = 1, self.SizeZ do
          for Y = 1, self.SizeY do
            for X = 1, self.SizeX do
                if TileIDs[J] ~= 0 then

                    self:SetTile(W, Z, Y, X, TileIDs[J], "tile_id")
                    self:SetTile(W, Z, Y, X, false, "animated")
                    J = Inc(J)
                else
                    self:SetTile(W, Z, Y, X, AnimationIDs[J], "animation_id")
                    self:SetTile(W, Z, Y, X, true, "animated")
                    J = Inc(J)
            end end end end end

      -- Load SpriteList data.
      local file = io.open((SpriteListFile or ""), "r")
      if not file then
        print("Error: cant open SpriteListFile")
      return end

      local i = 0
      local data = {}
      local AnimationTable = {}
      self.SpriteList = {}

      repeat
        i = i + 1
        data[i] = assert(loadstring("return " .. tostring(file:read())))()
      until data[i] == nil

      file:close()

      for i=1, #data do
        AnimationTable.Indicies = {}
        AnimationTable.LoopStatus = data[i][7]
        AnimationTable.Speed = data[i][8]

        local j = 9
        while data[i][j] ~= nil do
          AnimationTable.Indicies[#AnimationTable.Indicies+1] = data[i][j]
          j = j + 1
        end

        local TmpSprite = Sprite.New(data[i][1], data[i][2], data[i][3], data[i][4], data[i][5], data[i][6], AnimationTable)
        self.SpriteList[#self.SpriteList+1] = TmpSprite
      end
 end

    function self:Save(Worldfile, SpriteListFile)

        -- Save tilemap data.
        local File = io.open(Worldfile, "w+")
        if not File then
          print("Error cant open " .. SpriteListFile)
        return end
        for W = 1, self.SizeW do
          for Z = 1, self.SizeZ do
            for Y = 1, self.SizeY do
              for X = 1, self.SizeX do
                if not self:GetTile(W, Z, Y, X, "animated") then
                    File:write('{' .. math.floor(self:GetTile(W, Z, Y, X, "tile_id")) .. ':' .. '0' .. ':' .. tostring(self:GetTile(W, Z, Y, X, "impassable")) .. '}')
                    else
                    File:write('{' .. '0' .. ':' .. math.floor(self:GetTile(W, Z, Y, X, "animation_id")) .. ':' .. tostring(self:GetTile(W, Z, Y, X, "impassable")) .. '}')
                end
               end
             File:write('\n')
          end
         end
        end
        io.close(File)

     -- Save SpriteList data.
     local file = io.open((SpriteListFile or ""), "w+")
     if not file then
       print("Error: cant open SpriteListFile")
     return end

     for i = 1, #self.SpriteList do
       file:write("{" .. self.SpriteList[i].x .. "," .. self.SpriteList[i].y)
       file:write("," .. self.SpriteList[i].w .. "," .. self.SpriteList[i].h)
       file:write("," .. self.SpriteList[i].TextureID)
       file:write("," .. tostring(self.SpriteList[i].Visible))
       file:write("," .. tostring(self.SpriteList[i].AnimationTable.LoopStatus))
       file:write("," .. self.SpriteList[i].AnimationTable.Speed)

       for j = 1, #self.SpriteList[i].AnimationTable.Indicies do
         file:write(",")
         file:write(tostring(self.SpriteList[i].AnimationTable.Indicies[j]))
       end
     file:write("}\n")
    end

  file:close()
    end

    function self:Render(W,Z)
        local Rect, X, Y = {}, 1, 1

        Rect.x = 0
        Rect.y = 0
        Rect.w = TileSize
        Rect.h = TileSize

        local View = {
        x = math.floor(self.Camera.PosX / TileSize),
        y = math.floor(self.Camera.PosY / TileSize),
        w = math.floor(self.Camera.Width / TileSize),
        h = math.floor(self.Camera.Height / TileSize)
        }

        if View.w + View.x > self.SizeX then View.x = self.SizeX - View.w end
        if View.h + View.y > self.SizeY then View.y = self.SizeY - View.h end
        if View.x < 1 then View.x = 1 end
        if View.y < 1 then View.y = 1 end

        if self.SizeX < (self.Camera.Width / TileSize) then View.w = self.SizeX end
        if self.SizeY < (self.Camera.Height / TileSize) then View.h = self.SizeY end

        local StartTileX, EndTileX = View.x, (View.x + View.w + 2)
        local StartTileY, EndTileY = View.y, (View.y + View.h + 2)

        if EndTileX > self.SizeX and EndTileX > 1 then EndTileX = self.SizeX end
        if EndTileY > self.SizeY and EndTileY > 1 then EndTileY = self.SizeY end

        for X = StartTileX, EndTileX do
            Rect.y = ((Y * self.SizeOfTile) - self.SizeOfTile) - self.Camera.PosY
            Rect.x = ((X * self.SizeOfTile) - self.SizeOfTile) - self.Camera.PosX

            for Y = StartTileY, EndTileY do
                if self:GetTile(W, Z, Y, X, "animated") then

                    -- Render Animation
                    self.Animationset[self:GetTile(W, Z, Y, X, "animation_id")].Animation.PlayTile
                    (Renderer, X, Y, self.Animationset[self:GetTile(W, Z, Y, X, "animation_id")].Speed
                    ,self.Animationset[self:GetTile(W, Z, Y, X, "animation_id")].LoopStatus, self.Camera)
                else
                    -- Render Tile
                    Renderer:copy(self.Tileset[self:GetTile(W, Z, Y, X, "tile_id")], nil, Rect)
                end

                Rect.y = (Y * self.SizeOfTile) - self.Camera.PosY
            end
        end

    end

    return self
end

World.Tileset = {}

function World.Tileset.New(Renderer, TilesetFile)

    local self = {}

    self.Image, err = Image.load(TilesetFile) if not self.Image then error(err) end
    self.ImageBuffer, err = SDL.createRGBSurface(World.SizeOfTile, World.SizeOfTile, 32)
    self.Texture, err = Renderer:createTextureFromSurface(self.Image) if not self.Texture then error(err) end

    local f, a, w, h = self.Texture:query()
    NumberOfTiles = w / World.SizeOfTile

    local TileID
    local SrcRect = {}
    local DestRect = {}

    for TileID = 0, NumberOfTiles do

        DestRect.x = 0
        DestRect.y = 0
        DestRect.w = World.SizeOfTile
        DestRect.h = World.SizeOfTile

        SrcRect.x = (TileID * World.SizeOfTile) - World.SizeOfTile
        SrcRect.y = 0
        SrcRect.w = World.SizeOfTile
        SrcRect.h = World.SizeOfTile

        self.Image:setClipRect(SrcRect)
        self.Image:blit(self.ImageBuffer)
        self[TileID], err = Renderer:createTextureFromSurface(self.ImageBuffer) if not self.Texture then error(err) end
    end

    return self
end

World.TileAnimationset = {}

function World.TileAnimationset.New(Renderer, TileAnimationsetFile, TileAnimationsetCfgFile)

    local self = {}

    local J = 0
    local NumberOfTilesIndex, AnimationSpeedIndex = 0, 0
    local AnimationSpeed, NumberOfTiles, LoopStatus, Buffer = {}, {}, {}, ""
    local CfgFile = io.open(TileAnimationsetCfgFile, "r")
    local FileContents = CfgFile:read("*a") io.close(CfgFile)

    for I = 1, #FileContents do -- Read the Configuration file for the Animationset

        if FileContents:sub(I,I) == '#' then --Skip comments
            if FileContents:sub(I,I) == '#' then
                goto continue
            end
            I = Inc(I)
        end

        if FileContents:sub(I,I) == '{' then --Read data between commas
            I = Inc(I)
            while FileContents:sub(I,I) ~= ',' do
                Buffer = Buffer .. FileContents:sub(I,I)
                I = Inc(I)
            end
            I = Inc(I)
            NumberOfTiles[J] = tonumber(Buffer)
            Buffer = ""
            while FileContents:sub(I,I) ~= ',' do
                Buffer = Buffer .. FileContents:sub(I,I)
                I = Inc(I)
            end
            I = Inc(I)
            AnimationSpeed[J] = tonumber(Buffer)
            Buffer = ""
            while FileContents:sub(I,I) ~= '}' do
                Buffer = Buffer .. FileContents:sub(I,I)
                I = Inc(I)
            end
            if Buffer == "true" then LoopStatus[J] = true end
            if Buffer == "false" then LoopStatus[J] = false end
            Buffer = ""

            J = Inc(J)
        end

        ::continue::
    end

    self.Image, err = Image.load(TileAnimationsetFile) if not self.Image then error(err) end
    self.Texture, err = Renderer:createTextureFromSurface(self.Image) if not self.Texture then error(err) end

    local f, a, w, h = self.Texture:query()
    local NumberOfAnimations = h / World.SizeOfTile

    local AnimationID
    local SrcRect = {}
    local ImageBuffer

    I = 1
    for AnimationID = 1, NumberOfAnimations do

        SrcRect.x = 0
        SrcRect.y = (AnimationID * World.SizeOfTile) - World.SizeOfTile
        SrcRect.w = (World.SizeOfTile * NumberOfTiles[I])
        SrcRect.h = World.SizeOfTile

        ImageBuffer, err = SDL.createRGBSurface(NumberOfTiles[I] * World.SizeOfTile, h, 32)

        self.Image:setClipRect(SrcRect)
        self.Image:blit(ImageBuffer)
        self[AnimationID] = {}
        self[AnimationID].AnimationTexture, err = Renderer:createTextureFromSurface(ImageBuffer) if not self.Texture then error(err) end
        self[AnimationID].Animation = TileAnimation.New(Renderer, self[AnimationID].AnimationTexture)
        self[AnimationID].Speed = AnimationSpeed[I]
        self[AnimationID].LoopStatus = LoopStatus[I]
        I = Inc(I)
    end

    return self
end

return World
