--local loop  = require "loop.init"
local oo = require "loop.base"
require "LuaOrm.Mapper"
require "LuaOrm.Entity"
require "LuaOrm.Model"
require "LuaOrm.DBMS.DataSource"
require "LuaOrm.Associations.Associations"
require "LuaOrm.Associations.AssociationArray"
local jsonEncoder = require "LuaOrm.Serializers.Json"
local json = require "json"

require "LuaOrm.Date"
require "lunit"
local console = require "lunit-console"
--redefine iterator function for legibility
local rpairs = LuaOrm.Associations.AssociationArray.Iterator

local mapper = {}
local datasource 

-------------------------
--Connects to data source
function Connect() 
    print("********************************************")
	datasource = oo.new(LuaOrm.DBMS.DataSource)
    local database = {
      driver = "inconcertPool",
      conn_string = "Driver={SQL Server};Server=192.168.0.229;Database=MMProDat;Uid=UsrAccMw;Pwd=inc2001;",
      user = "",
      pass = ""
    }
    print("Connecting to: driver "..database.driver..", Connection string: "..database.conn_string)

    --the connection belongs to the abstract layer module from now on, so it should be accesible
    --as long as the module is there
    datasource:InitRDMBSLayer(database.driver, database.conn_string, database.user, database.pass)

end

function DefineModels()
    print("********************************************")
    print("Defining models...")

    --In this case the relationship defined says that Grant belongs to Usuario, so Save() and Delete() operations will operate on
    --the relationship too
    local Usuario = LuaOrm.Model {
                                                tableName = "Usuarios0092",
												dataSource = datasource,
                                                name = "Usuario",
                                                Associations = {
                                                                hasMany("Grant", nil, true),
                                                                },
                                              Attributes = {
                                                                Country = function (entity) 
                                                                            return "Uruguay"
                                                                          end,
                                                                Age = function(entity)
                                                                            return entity.TmStmp0092:toString()
                                                                       end,
                                                                Size = 34 --hardcoded is not useful, but valid...
                                                             }
                                            }

    --In this case the relationships have no foreign keys defined on the database so it must
    --be specified on the model definition
    local UserAddressesActive = LuaOrm.Model {
                                                tableName = "UserAddressesActive",
												dataSource = datasource,
                                                Associations = {
                                                                    hasOne("Usuario", {IdUsua0092 = "IdUser"}), --the order key/value DOES matter
                                                                }	
                                          }



    --The Grant model is defined in order to be accesible from Usuario (as in Usuario.Grants)
    --in this case the relationship is not defined "belonging to", so if you Grant:Save() it won't
    --save the related user on the operation
    local Grant = LuaOrm.Model {
                                                tableName = "LogGrant0219",
												dataSource = datasource,
                                                name = "Grant",
                                                Associations = {
                                                                belongsToOne("Usuario"),
                                                                }
                                            }

--[[
    local filterHeader = LuaOrm.Model { 
					tableName = "filterHeader", 
					name = "filterHeader",  
					Associations = { 
							hasMany("filterDetail", nil, true), 
							} 
					}



	local filterComboData = LuaOrm.Model {
						tableName = "filterComboData", 
						name = "filterComboData", 
						Associations = {}
					}					
	local filterCombo  = LuaOrm.Model {
						tableName = "filterCombo", 
						name = "filterCombo", 
						Associations = {
										belongsToOne("filterDetail"),
										belongsToOne("filterComboData"),
									}
                            }

    local filterDetail = LuaOrm.Model { 
		tableName = "filterDetail",  
		name = "filterDetail",  
		Associations = { 
			hasMany("filterCombo" , nil, true),
			belongsToOne("filterHeader"), 
		} 
      }
]]--

    --model used in order to test and demonstrate paged request
    local Interactions = LuaOrm.Model {
            tableName = "InstInte0245",
			dataSource = datasource,
            name = "Interaction",
            --primaryKey = {"IdInst0245" , "F1Camp02930245"},
            Associations = {
                belongsToOne("Campaign", { IdCamp0293 = "F1Camp02930245"})
            }	
    }
    local Campaign = LuaOrm.Model {
            tableName = "CbCampan0293",
			dataSource = datasource,
            name = "Campaign",
            Associations = {}   
        }


    --this is just orbit stuff simulation in order to mantain the API triggering the intializacion on the add_models entry point
    mapper = orbit.model.new()

    function add_models(ms)
        local modelos = {}
      for k, v in pairs(ms) do
        modelos[v.name] = mapper:new(k, v)
      end
      mapper.models = modelos
    end
	datasource:ValidateDataSource()
    add_models( {UserAddressesActive, Grant, Usuario, Interactions, Campaign}) --filterHeader, filterDetail, filterComboData, filterCombo})

end

module( "unitTesting", package.seeall, lunit.testcase )

function testFind()
    print("********************************************")
    print("= Testing Find Operation")

    local usermodel = mapper.models["Usuario"]    
    print("==  Searching using string key")
    local user = usermodel:Find("Administrator")
    assert_false(user == nil, "Find failed")
    if (user) then
        print("===   Item found")
        for k, v in pairs(user) do
            print("             Key:"..tostring(k).." - Value:"..tostring(v))
        end 
    end

    local addressmodel = mapper.models["UserAddressesActive"]
    print("==  Searching using table key")
    local address = addressmodel:Find({IdUser = "Agent1", IdChannel = "TAPI"})
    if (address) then
        print("===   Item found")
        for k, v in pairs(address) do
            print("             Key:"..tostring(k).." - Value:"..tostring(v))
        end 
    end

end

function testFindAll()
    print("********************************************")
    print("= Testing Find All Operation")
    
    local usermodel = mapper.models["Usuario"]
    print("==  Search using only model values")
    local userList = usermodel:FindAll("Catego0092 = ?", {5})
    assert_false(userList == nil, "FindAll failed")
    if (userList) then
        for k, user in pairs(userList) do
            print("===   User found")
            print("             Name:"..user.IdUsua0092.." - Password:"..user.Passwo0092)
            assert_true(user.Age ~= nil)
        end
    end
    
    print("==  Search using related entity")
    --Users with category 5, having grant on Queue CampTemplate
    local userList = usermodel:FindAll("Catego0092 = ? and Grant.F1Cola02900219 = ?", {5, "CampTemplate"})
    assert_false(userList == nil, "FindAll failed")
    if (userList) then
        for k, user in pairs(userList) do
            print("===   User found")
            print("             Name:"..user.IdUsua0092.." - Grants:"..user.Grants:Count())
        end
    end
    
    print("==  Search on manual defined relationship")
    local addressmodel = mapper.models["UserAddressesActive"]
    local addresslist = addressmodel:FindAll("IdChannel = ? and Usuario.F1VsNg01230092 = ?", {"TAPI", "MMProDat"})
    assert_false(addresslist == nil, "FindAll failed")
    if (addresslist) then
        for k, address in pairs(addresslist) do
            print("===   Address found")
            --here we're using the relationship in order to access the user description related to the 
            print("             Address:"..address.Address.." - User:"..address.Usuario.DesUsu0092)
        end
    end
end

function testRelationShips()
    print("********************************************")
    print("= Testing Relationships")
    
    --hasMany
    local usermodel = mapper.models["Usuario"]
    print("=== HasMany relationship...")
    local user = usermodel:Find("Administrator")
    if (user) then
        print("----------------------------------------------- ")
        print("     User has:"..user.Grants:Count().." grants")
        for k, grant in rpairs(user.Grants) do
            print("     -> Grant Queue:["..grant.F1Cola02900219.."] , timestamp:"..grant.TmStmp0219:toString()..", backward user:"..grant.Usuario.DesUsu0092)
        end
    end

    --hasOne
    print("==  HasOne relationship...")
    local addressmodel = mapper.models["UserAddressesActive"]
    local address = addressmodel:Find({IdChannel = "TAPI",
                                       IdUser = "Agent1"})
    if (address) then
        print("===   Address found")
        --here we're using the relationship in order to access the user description related to the 
        print("             Address:"..address.Address.." - User:"..address.Usuario.DesUsu0092)
    end

    --belongsToOne
    local grantmodel = mapper.models["Grant"]    
    print("=== belongsToOne relationship...")
    local grant = grantmodel:Find({
                                    F1Agen00060219 = '',
                                    F1AppR02310219 = '', 
                                    F1Cola02900219 = 'CampTemplate',
                                    F1Equi00070219 = '', 
                                    F1Logg02180219 = '', 
                                    F1Role00080219 = '',
                                    F1Secc00090219 = '',
                                    F1Usua00920219 = 'Agent1',
                                    F1VrAp02310219 = ''
                                 })
    if (grant) then
        print("     -> Grant user is:"..grant.Usuario.DesUsu0092)
    end    
    
    --belongsToMany
    --TODO
    --hasAndBelongsToMany
    --TODO
end

function testNew()
    print("********************************************")
    print("= Testing new")

    local filtermodel = mapper.models["filterHeader"]
    local filterdetailmodel = mapper.models["filterDetail"]
    local filter = filtermodel:New()
    local filter = filtermodel:New()
    if (filter) then
        filter.applicationId = "BarAgent"
        filter.userId = "Agent2"
        filter.pageName ="pagina"
        filter.filterId = "myGuilloteFilter"
        filter.ordinal = 50
        filter.filterType = ""

        local detail = filterdetailmodel:New()
        detail.fieldId = "campofiltro"
        detail.caption = "caption"
        detail.value = "value"
        detail.tagId = "select"
        detail.tagType = "char"
        filter.userId = "Agent2"
        detail.formatDate = ""
        detail.isDynamic = 0
        detail.ordinal = 0

        filter.filterDetails:Add(detail)
        filter:Save()
    end    
    lista = filtermodel:FindAll("applicationId =? and userId =? and pageName =?",  {"MULTIMEDIA LIVE PLAYER","USER1", "MLPPLAYER"})

    --new entity
    print("=== Creating new user")
    local usermodel = mapper.models["Usuario"]
    local user = usermodel:New()
    if (user) then
        user.IdUsua0092 = "guille"
        user.DesUsu0092 = "Este es el usuario guille"
        user.TmStmp0092:fromString("11/01/1977")
        user.F1VsNg01230092 = "MMProDat"
        user.F1VrVs01240092 = "1.0"
        user.Catego0092 = 2
        assert_true(user:Save(), "Saving new entity")
        user = usermodel:Find("guille")
        if (user) then
            assert_equal(user.Catego0092, 2)
            assert_true(user:Delete(), "Deleting user failed")
        else
            assert_true(false, "Save record not found")
        end
    end

    --new entity with related data
    print("=== Creating new user with related data")
    local grantmodel = mapper.models["Grant"]
    local user = usermodel:New()
    if (user) then
        user.IdUsua0092 = "guille"
        user.DesUsu0092 = "Este es el usuario guille"
        user.F1VsNg01230092 = "MMProDat"
        user.F1VrVs01240092 = "1.0"
        user.TmStmp0092:fromString("11/01/1977")
        user.Catego0092 = 2

        local newgrant = grantmodel:New()
        if (newgrant) then
            newgrant.F1Agen00060219 = ''
            newgrant.F1AppR02310219 = 'BarAgent'
            newgrant.F1Cola02900219 = 'CampTemplate'
            newgrant.F1Equi00070219 = ''
            newgrant.F1Logg02180219 = '' 
            newgrant.F1Role00080219 = ''
            newgrant.F1Secc00090219 = ''
            --newgrant.F1Usua00920219 = 'Agent1' , this should be added by the relationship itself
            newgrant.F1VrAp02310219 = '1.0'
            user.Grants:Add(newgrant)
        end
        assert_true(user:Save(), "Saving hasmany relationship") --now the grant should be saved to
        user = usermodel:Find("guille")
        if (user) then
            newgrant = user.Grants:GetItem(1)
            assert_equal(newgrant.F1AppR02310219, "BarAgent")
            assert_true(user:Delete(), "Cascading delete on hasmany relationship")
        end
    end

    --new entity that already exists in the database (duplicate primary key)
    local user = usermodel:New()
    if (user) then
        user.IdUsua0092 = "Administrator"
        assert_false(user:Save(), "Saving entity with duplicate primary key")
    end
    --new entitiy with missing primary key
    local user = usermodel:New()
    if (user) then
        user.DesUsua0092 = "Usuario sin primary key"
        assert_false(user:Save(), "Saving entity without primary key")
    end
end

function testSave()
    --update

    --update relationship

    --update non belonging relationship
    
end

function testDelete()
    local usermodel = mapper.models["Usuario"]
    local user = usermodel:Find("guille")
    if (user == nil) then
        --
    end
end

function testDynamicAttributes()
    local usermodel = mapper.models["Usuario"]        
    local user = usermodel:Find("Administrator")
    assert_false(user == nil, "Find failed")
    if (user) then
        assert_true(user.Country ~= nil)
        assert_true(user.Age ~= nil)
        assert_true(user.Size ~= nil)
    end

end

function testEncoding()
    local usermodel = mapper.models["Usuario"]        
    local user = usermodel:Find("Administrator")
    assert_false(user == nil, "Find failed")
    if (user) then
        local encoder = oo.new(jsonEncoder)
        --jsonString = encoder:Serialize(user)        
        jsonString = json.encode(user)
        file,err = io.open( "json_user.js", "w" )
        if err then return _,err end
        file:write(jsonString)
        file:close()
        local decodedUser = encoder:Deserialize(jsonString, usermodel)
        assert_true(decodedUser ~=nil and decodedUser.IdUsua0092 == "Administrator")
        assert_true(decodedUser.Grants:Count() == user.Grants:Count())
        assert_true(decodedUser.Grants:GetItem(1).F1Usua00920219 == user.Grants:GetItem(1).F1Usua00920219)
    end
    
    local filtermodel = mapper.models["filterHeader"]
    local listaFiltros = filtermodel:FindAll("userId = ?", {"USER1"})
    if (listaFiltros) then
        local encoder = oo.new(jsonEncoder)
        jsonString = encoder:SerializeList(listaFiltros, true)        
        file,err = io.open( "json_filterHeader.js", "w" )
        if err then return _,err end
        file:write(jsonString)
        file:close()
    end
end

function testDateStuff()
    local fecha = oo.new(LuaOrm.Date)
    fecha:fromString("11.01.1977")
    print(tostring(fecha))
end

function testPagedRequests()
    local interactionModel = mapper.models["Interaction"]
    assert_true(interactionModel ~= nil)
    --find all the campaigns paged by 10 campaigns per page
    local pagedInteractionsByCampaign = interactionModel:FindAllPaged("F1Camp02930245 = ?", {"CampTemplate"}, 10)
    if (pagedInteractionsByCampaign) then
        assert_true(pagedInteractionsByCampaign:GetNumberOfPages() > 0)
        local pagedRows = pagedInteractionsByCampaign:GetPage()
        while (pagedRows) do
            print("---------------------------------------------")
            print("=== Rows in this page:"..tostring(#pagedRows))
            for k, v in ipairs(pagedRows) do
                print("Interaction:"..v.IdInst0245.." on Campaign:"..v.Campaign.IdCamp0293)
                --print("        "..json.encode(v))
            end
            pagedRows = pagedInteractionsByCampaign:NextPage()
        end
    end

    --Idem previous case, but paging also relationship data
    local pagedInteractionsUsingAlgorithm = interactionModel:FindAllPaged("F1Camp02930245 = ? and InsCan0245 = ? and Campaign.AlgEnc0293 = ?", {"CampTemplate", "SIP", "CIRCULAR", order = "IdInst0245"}, 3)
    if (pagedInteractionsUsingAlgorithm) then
        assert_true(pagedInteractionsUsingAlgorithm:GetNumberOfPages() > 0)
        local pagedRows = pagedInteractionsUsingAlgorithm:GetPage()
        while (pagedRows) do
            print("---------------------------------------------")
            print("=== Rows in this page:"..tostring(#pagedRows))
            for k, v in ipairs(pagedRows) do
                print("Interaction:"..v.IdInst0245.." on Campaign:"..v.Campaign.IdCamp0293)
                --print("        "..json.encode(v))
            end
            pagedRows = pagedInteractionsUsingAlgorithm:NextPage()
        end
    end

end

function toto()
   -- user = new entidadusuario
    --jsonString = json.encode(user)
    --json.append(jsonString, translate(user.pais), "paistraducido")

end

Connect()
DefineModels()
console.begin()
lunit.run()
console.done()

