--[[
Некоторые пояснения к стуруктуре кода:
- Есть переменная currentObj, представляющая собой контейнер, который 
обрабатывается игровым циклом. Все что не в нем - спит.
- Смена currentObj осуществляется командой setScene, куда можно передавать
контейнер или метку контейнера
- setMap предназначен для смены локации, она срабатывает внутри сцены mainScreen
основного экрана игры.
- На карте в сетке координат расположен герой pl и активные области.
- У активных областей есть menu - таблица компонент. Если герой входит в 
активную область, ее menu загружается в соответствующее место mainScreen и
в результате пользователь видит кнопочки и надписи и может с ними взаимодействовать.
При перекрытии активных областей пользователь видит только последнюю
- ChLabel, Ch1Button, Ch1ButtonOneClick - просто синтаксический сахар для
быстрого создания текст+кнопки выбора в menu. Теоретически, там может быть что угодно.
- message - это специальная компонента для вывода сообщений. showmessage() ее показывает
hidemessage() - прячет. Она работает только внутри основного экрана игры.

--]]


-- Константы размера приложения
WIDTH = 16*15
HEIGTH = 16*30
MAP_HEIGTH = 16*10
MARGIN = 8
BUTTON_HEIGTH = 20
FRAMEBORDERCOLOR = 1


-- Ключевые метки статуса
oldSceneName = ''
activeSceneName = 'Главный экран'
activeObjectName = ''
activeMapName = ''

gfx.win(WIDTH, HEIGTH)




-- Обрабатываемый объект-сцена
--local currentObj
function setScene(sc)
    oldSceneName = currentObj.nam
    if type(sc)=='string' then currentObj=ALL[sc]
                               activeSceneName = sc
                          else currentObj = sc
                               activeSceneName = sc.nam
                          end
    if debugmode then print(activeSceneName) end
end

-- Карта, в которой находится игрок
function setMap(sc)
    if type(sc)=='string' then highscreen.obj={ALL[sc]} 
                               activeMapName = sc
                          else highscreen.obj={ sc }
                               activeMapName = sc.nam
                          end
    savetofile() -- Сохранение при смене локации.
    if debugmode then print(activeMapName) end
end



----------- Кнопки меню на главном экране ----------------------
MenuButton = {}
setmetatable(MenuButton ,{__index = TextButton})
function MenuButton:new(nam, text, key, position)
    local buttonHeigth = 20
    local o = TextButton:new(nam, MARGIN*3, position*(MARGIN+buttonHeigth), WIDTH-6*MARGIN, buttonHeigth, key..': '..text, key)
    setmetatable(o, self)
    return o
end --func

------- Нижняя кнопка меню для всяких вспомогательных экранов ----------
MenuBottomButton = {}
setmetatable(MenuBottomButton ,{__index = MenuButton})
function MenuBottomButton:new(nam, text, key)
    local o = MenuButton:new(nam, text, key, 16)
    setmetatable(o, self)
    return o
end --func

------- Сообщение на все рабочее поле для всяких вспомогательных экранов ----
LabelFull = {}
setmetatable(LabelFull ,{__index = Label})
function LabelFull:new(text)
    local o = Label:new(nil, 10, 10, text)
    setmetatable(o, self)
    return o
end --func

------------------- Область сообщения с выборами --------------------------

-- Вывод текста в области сообщений
ChLabel = {}
function ChLabel:new(nam, text)
    local o = {}
    o.x = x
    o.y = y
    o.text = text
    o.draw = function (s)
             screen:rect(MARGIN, MAP_HEIGTH+0.5*MARGIN, WIDTH-MARGIN, HEIGTH-MARGIN-3*BUTTON_HEIGTH-3*5, FRAMEBORDERCOLOR)
             gfx.print(s.text, 2*MARGIN, MAP_HEIGTH+1.5*MARGIN)
             end;
    o.act = function(s)
            end;
    o.update = function(s)
                end;
    setmetatable(o, self)
    self.__index = self
    if nam then ALL[nam] = o end
    return o
end --func


-- Кнопки выбора в текстовом меню с фиксированной позицией и заданными ключами 1-3
Ch1Button = {}
setmetatable(Ch1Button ,{__index = TextButton})
function Ch1Button:new(nam, text)
    local o = TextButton:new(nam, MARGIN, HEIGTH-MARGIN-3*BUTTON_HEIGTH-2*5, WIDTH-2*MARGIN, BUTTON_HEIGTH, '1: '..text, '1')
    setmetatable(o, self)
    return o
end --func

Ch2Button = {}
setmetatable(Ch2Button ,{__index = TextButton})
function Ch2Button:new(nam, text)
    local o = TextButton:new(nam, MARGIN, HEIGTH-MARGIN-2*BUTTON_HEIGTH-1*5, WIDTH-2*MARGIN, BUTTON_HEIGTH, '2: '..text, '2')
    setmetatable(o, self)
    return o
end --func

Ch3Button = {}
setmetatable(Ch3Button ,{__index = TextButton})
function Ch3Button:new(nam, text)
    local o = TextButton:new(nam, MARGIN, HEIGTH-MARGIN-1*BUTTON_HEIGTH-0*5, WIDTH-2*MARGIN, BUTTON_HEIGTH, '3: '..text, '3')
    setmetatable(o, self)
    return o
end --func

-- Аналогичные кнопки, которые отключатся после первого нажатия

Ch1ButtonOneClick = {}
setmetatable(Ch1ButtonOneClick ,{__index = TextButtonOneClick})
function Ch1ButtonOneClick:new(nam, text, mess)
    local o = TextButton:new(nam, MARGIN, HEIGTH-MARGIN-3*BUTTON_HEIGTH-2*5, WIDTH-2*MARGIN, BUTTON_HEIGTH, '1: '..text, '1')
    o._active = function (s) return not(CHECK(s.nam..'_была_нажата')) end;--func
    o.onclick = mess
    setmetatable(o, self)
    return o
end --func

Ch2ButtonOneClick = {}
setmetatable(Ch2ButtonOneClick ,{__index = TextButton})
function Ch2ButtonOneClick:new(nam, text, mess)
    local o = TextButton:new(nam, MARGIN, HEIGTH-MARGIN-2*BUTTON_HEIGTH-1*5, WIDTH-2*MARGIN, BUTTON_HEIGTH, '2: '..text, '2')
    o._active = function (s) return not(CHECK(s.nam..'_была_нажата')) end;--func
    o.onclick = mess
    setmetatable(o, self)
    return o
end --func

Ch3ButtonOneClick = {}
setmetatable(Ch3ButtonOneClick ,{__index = TextButton})
function Ch3ButtonOneClick:new(nam, text, mess)
    local o = TextButton:new(nam, MARGIN, HEIGTH-MARGIN-1*BUTTON_HEIGTH-0*5, WIDTH-2*MARGIN, BUTTON_HEIGTH, '3: '..text, '3')
    o._active = function (s) return not(CHECK(s.nam..'_была_нажата')) end;--func
    o.onclick = mess
    setmetatable(o, self)
    return o
end --func

--------------------- Сообщение в поле текста, пропадающее по любой клавише ----------------

message = {}
message.text = ''
message.draw = function (s)
             screen:rect(MARGIN, MAP_HEIGTH+0.5*MARGIN, WIDTH-MARGIN, HEIGTH-MARGIN, FRAMEBORDERCOLOR) --HEIGTH-MARGIN-3*BUTTON_HEIGTH-3*5
             gfx.print(s.text, 2*MARGIN, MAP_HEIGTH+1.5*MARGIN)
             gfx.print('Нажмите пробел', MARGIN + WIDTH/2, HEIGTH-MARGIN-2*5, 4)
             end;
message.act = function(s)
            local x, y, mb = input.mouse()
            if mb.left and posinrect(x,y, MARGIN, MAP_HEIGTH+0.5*MARGIN, WIDTH-MARGIN, HEIGTH-MARGIN) or
                input.keypress('space') or input.keypress('left') or input.keypress('right') or input.keypress('up') or input.keypress('down')
                then
                s:sound()
                FREEZE = 10
                hidemessage()
                end --if
            end;
message.update = function(s)
                end;
message.sound = function(s)
                buttonsound()
                end;

-- Функция выводит сообщение в замещение окна выбора
showmessage = function(text)
                      message.text = text
                      lowscreen.obj = {message}
                      pl.active = false
                      inventory.active = false
                      _'Меню'.active = false
                    end
hidemessage = function()
                    lowscreen.obj = {chmess}
                    pl.active = true
                    inventory.active = true
                    _'Меню'.active = true
                    end --func


--------------------- Здесь объекты для работы с картой -------------------------------------
function i2x(i)
    return i*16+6+16
end --func

function j2y(j)
    return j*16-5
end --func

------------------------------------ Персонаж ----------------------------------------

pl = {}
pl.active = true
pl.visible = true
pl.i = 1
pl.j = 1
pl.pic = {spr.hare, spr.hare1}
pl.draw = function (s)
          if not s.visible then return end
          s.pic[1+math.floor(s.clock/50*#(s.pic))]:blend(screen, i2x(s.i), j2y(s.j))
          end
pl.act= function(s)
        if not s.active then return end
        local i_old = s.i
        local j_old = s.j
        if input.keypress('right') then s.i = s.i + 1 end
        if input.keypress('left') then s.i = s.i - 1 end
        if input.keypress('up') then s.j = s.j - 1 end
        if input.keypress('down') then s.j = s.j + 1 end
        --(MARGIN+11+16, MARGIN, WIDTH-MARGIN, MAP_HEIGTH-0.5*MARGIN+1, FRAMEBORDERCOLOR)
        -- Управление движением мышью
        local x, y, mb = input.mouse()
        if mb.left then FREEZE = 10 end
        if false then -- Старая схема управления - через области графического окна крестом
            local tapzoneX = MARGIN+11+16
            local tapzoneY = MARGIN
            local tapzoneW = WIDTH-MARGIN - (MARGIN+11+16)
            local tapzoneH = MAP_HEIGTH-0.5*MARGIN+1 - MARGIN
            if mb.left and posinrect(x,y, tapzoneX+tapzoneW*2/3, tapzoneY+tapzoneH/3,    tapzoneX+tapzoneW,     tapzoneY+tapzoneH*2/3) then s.i = s.i + 1 end
            if mb.left and posinrect(x,y, tapzoneX,              tapzoneY+tapzoneH/3,    tapzoneX+tapzoneW/3,   tapzoneY+tapzoneH*2/3) then s.i = s.i - 1 end
            if mb.left and posinrect(x,y, tapzoneX+tapzoneW*1/3, tapzoneY+tapzoneH*2/3,  tapzoneX+tapzoneW*2/3, tapzoneY+tapzoneH) then s.j = s.j + 1 end
            if mb.left and posinrect(x,y, tapzoneX+tapzoneW*1/3, tapzoneY,               tapzoneX+tapzoneW*2/3, tapzoneY+tapzoneH/3) then s.j = s.j - 1 end
        else -- Новая схема управления - клик в сторону, куда ходить, относительно зайца
            local d = 5
            if mb.left and posinrect(x,y, i2x(s.i+1), j2y(s.j), i2x(s.i+1+d), j2y(s.j+1)) then s.i = s.i + 1 end
            if mb.left and posinrect(x,y, i2x(s.i-d), j2y(s.j), i2x(s.i), j2y(s.j+1)) then s.i = s.i - 1 end
            if mb.left and posinrect(x,y, i2x(s.i), j2y(s.j+1), i2x(s.i+1), j2y(s.j+1+d)) then s.j = s.j + 1 end
            if mb.left and posinrect(x,y, i2x(s.i), j2y(s.j-d), i2x(s.i+1), j2y(s.j)) then s.j = s.j - 1 end
            
        end --if
        -- Запрет на движение наружу экрана и внутрь препятсвий
        for i,v in pairs(highscreen.obj[1].obj) do
                    if v:obstacle(s.i, s.j) then
                                    s.i=i_old
                                    s.j=j_old
                                    cantwalksound()
                                    return true
                                    end--if
                    end--for
        if s.i<1 or s.i>12 or s.j<1 or s.j>9 then
                                    s.i=i_old
                                    s.j=j_old
                                    cantwalksound()
                                    return true
                                    end--if
        if s.i ~= i_old or s.j ~= j_old then walksound() end
    end --func
pl.clock = 1
pl.update = function(s)
            --if not s.active then return end
            s.clock = s.clock + 1
            if s.clock>=50 then s.clock = 1 end
            end -- func



------ Объект, чувствительный к приближеню героя, при касании загружает свой menu в контейнер сообщений -------------
SensPic = {}
function SensPic:new(nam, i, j, obstacle, pic)
    local o = {}
    o.nam = nam
    o.i = i
    o.j = j
    o.visible = true
    o._visible = function(s)
                if type(s.visible)=='function' then return s.visible() else return s.visible end
                end --func
    o.active = true
    o._active = function(s)
                if type(s.active)=='function' then return s.active() else return s.active end
                end --func
    o._obstacle = obstacle
    o.obstacle = function(s, i, j) -- Функция возвращает тру, если в объект нельзя входить
                 if not s:_active() then return false end
                 if s._obstacle and s.i<=i and i <= s.i+s.pw-1 and s.j <=j and j<=s.j+s.ph-1 then return true else return false end
                 end --func
    o.pic = pic
    o.pw, o.ph = pic:size()   -- Размер рисунка в тайтлах
    o.pw = math.ceil(o.pw /16)
    o.ph = math.ceil(o.ph /16)
    o.width, o.heigth = pic:size()
    o.menu = {}
    o.draw = function (s)
            if not s:_visible() then return end
            s.pic:blend(screen, i2x(s.i), j2y(s.j))
            end;
    o.act = function(s)
            if not s:_active() then return end
            end;
    o.update = function(s)
                if next(s.menu)==nil  then return end
                if not s:_active() then return end
                local zone
                if s._obstacle then zone = 2 else zone = 1 end
                if (s.i<=pl.i and pl.i<=s.i+s.pw-1 and s.j-zone<pl.j and pl.j<s.j+zone+s.ph-1) or 
                   (s.j<=pl.j and pl.j<=s.j+s.ph-1 and s.i-zone<pl.i and pl.i<s.i+zone+s.pw-1) then
                            chmess.obj = s.menu
                            activeObjectName = s.nam
                            end --if
                end;
    setmetatable(o, self)
    self.__index = self
    if nam then ALL[nam] = o end
    return o
end --func

----------------- Чувствительная зона -------------------------------

SensZone = {}
function SensZone:new(nam, imin, jmin, imax, jmax, obstacle)
    local o = {}
    o.nam = nam
    o.imin = math.min(imin, imax)
    o.jmin = math.min(jmin, jmax)
    o.imax = math.max(imin, imax)
    o.jmax = math.max(jmin, jmax)
    o._obstacle = obstacle
    o.active = true
    o._active = function(s)
                if type(s.active)=='function' then return s.active() else return s.active end
                end --func
    o.obstacle = function(s, i, j) 
                 if not s:_active() then return false end
                 if s._obstacle and s.imin<=pl.i and pl.i<=s.imax and s.jmin<=pl.j and pl.j<=s.jmax then return true else return false end
                 end
    o.menu = {}
    o.draw = function (s)
            end;
    o.act = function(s)
            if not s:_active() then return end
            end;
    o.update = function(s)
                if next(s.menu)==nil  then return end
                if not s:_active() then return end
                local brdr -- Если зона непроходима, ее зона чувствительности шире на brdr
                if s._obstacle then brdr = 1 else brdr = 0 end
                if s.imin-brdr<=pl.i and pl.i<=s.imax+brdr and s.jmin-brdr<=pl.j and pl.j<=s.jmax+brdr then
                            chmess.obj = s.menu
                            activeObjectName = s.nam
                            end --if
                end;
    setmetatable(o, self)
    self.__index = self
    if nam then ALL[nam] = o end
    return o
end --func

--------------- Замащивание области спрайтом ---------------------
SensZoneSprite = {}
function SensZoneSprite:new(nam, sprite, imin, jmin, imax, jmax, obstacle)
    local o = SensZone:new(nam, imin, jmin, imax, jmax, obstacle)
    o.pic = sprite
    o.visible = true
    o._visible = function(s)
                if type(s.visible)=='function' then return s.visible() else return s.visible end
                end --func
    o.draw = function (s)
            if not s:_visible() then return end
            for i = s.imin, s.imax do
            for j = s.jmin, s.jmax do
            s.pic:blend(screen, i2x(i), j2y(j))
            end
            end
            end;--func
    setmetatable(o, self)
    self.__index = self
    return o
end --func

----------------------- Инвентарь ---------------------------------

inventory = Conteiner:new('Инвентарь')
inventory.add = function(s, o)
                if type(o)=='string' then table.insert(s.obj, _(o)) else table.insert(s.obj, o) end
                end
inventory.remove = function(s, n)
                for i = 1, #s.obj do
                    if s.obj[i].nam == n then
                            table.remove(s.obj, i)
                            return true
                            end --if
                end--for
                end--func
inventory.check = function(s, n)
                for i = 1, #s.obj do
                    if s.obj[i].nam == n then return true end --if
                end--for
                return false
                end--func
inventory.draw = function (s)
             if not s.visible then return end
             for i,v in pairs(s.obj) do
                 v.x = MARGIN
                 v.y = MAP_HEIGTH-0.5*MARGIN+1 - 24 - 26*(i-1)
                 v:draw()
                 end --for
             end;--func
-- Псевдонимы в стиле инстед (Передавать только строки)
function have(name) return inventory:check(name) end
function take(name) return inventory:add(name) end
function drop(name) return inventory:remove(name) end

------------------ Структура игрового экрана ------------------------

-- Экран состоит из верхней и нижней половинок
-- В верхнюю половину надо класть карту активной зоны
-- В нижней половине динамически переключаются экран сообщений с кнопками и просто пассивный вывод сообщений
mainScreen = Conteiner:new('Экран игры')
highscreen = Conteiner:new('Верхний экран')
highscreen.draw = function (s)
                if not s.visible then return end
                screen:rect(MARGIN+11+16, MARGIN, WIDTH-MARGIN, MAP_HEIGTH-0.5*MARGIN+1, FRAMEBORDERCOLOR)
                screen:rect(MARGIN+11+16+2, MARGIN+2, WIDTH-MARGIN-2, MAP_HEIGTH-0.5*MARGIN-1, FRAMEBORDERCOLOR)
                for i,v in pairs(s.obj) do v:draw() end
             end;
lowscreen = Conteiner:new('Нижний экран')

chmess = Conteiner:new('Экран сообщений')
lowscreen.obj = {chmess}

-- Для отладки выводит текущую координату персонажа
-- TODO Убрать в релизе
--debugText = Label:new(nam, WIDTH/2, 0, '')
--debugText.update = function(s) s.text = '@ = ('..pl.i..', '..pl.j..')' end

mainScreen.obj = {  PicButton:new('Меню', MARGIN, MARGIN, spr.gear, 'escape'),
                    inventory,
                    highscreen,
                    pl, -- Плайер должен быть после экрана, чтобы отрисовываться поверх проходимых тайтлов
                    debugText,
                    lowscreen
}
mainScreen.update = function(s)
                chmess.obj={}
                for i,v in pairs(s.obj) do v:update() end
                end;

_'Меню'.onclick = function(s) setScene('Главное меню') end;


--------------------- Объект для затенения -----------------------------

Shading = {}
Shading.state = 0
Shading.type = ''
Shading.make = function(s, command) -- Устанавливаем тип эффекта и его длительность
    s.type = command
    if command == 'Из темноты' then s.state = 50
    elseif command == 'Затенение' then s.state = 100
    end --if
end --func
Shading.act = function(s) -- Отрисовываем эффект
    if s.state > 0  then
            s.state = s.state - 1
            local herz = 50
            if s.type == 'Из темноты' then screen:fill_rect(1, 1, WIDTH, HEIGTH, { 0, 0, 0, 255*(s.state/herz) })
            elseif s.type == 'Затенение' then screen:fill_rect(1, 1, WIDTH, HEIGTH, { 0, 0, 0, 255*(s.state/25 - s.state*s.state/2500) })
            end -- if
            end --if
end --func
