local person = [[{
    "name": "person", "type": "record", "fields": [
        { "name": "FirstName",   "type": "string" },
        { "name": "LastName",    "type": "string" },
        { "name": "Age",         "type": "int"    },
        { "name": "Sex",         "type": "int"    },
        { "name": "PhoneNumber", "type": "string" },
        { "name": "HomeAddress", "type": "string" },
        { "name": "Occupation",  "type": "string" }
    ]
}]]

local person_default = [[{
    "name": "person", "type": "record", "fields": [
        { "name": "FirstName",   "type": "string" },
        { "name": "LastName",    "type": "string", "default": "" },
        { "name": "Age",         "type": "int"    },
        { "name": "Sex",         "type": "int"    },
        { "name": "PhoneNumber", "type": "string", "default": "" },
        { "name": "HomeAddress", "type": "string", "default": "" },
        { "name": "Occupation",  "type": "string", "default": "" }
    ]
}]]

local record_nullable = [[ {
    "name": "outer", "type": "record", "fields": [
        { "name": "r1", "type": {
                          "name": "tr1",
                          "type": "record", "fields": [
                            {"name": "v1", "type": "int"} ,
                            {"name": "v2", "type": "string"}
                          ] } },
        { "name": "r2", "type": "tr1*"},
        { "name": "dummy", "type": {
                             "name": "td", "type": "array", "items": "int" }
                           },
        { "name": "r3", "type": {
                          "name": "tr2",
                          "type": "record*", "fields": [
                            {"name": "v1", "type": "string"} ,
                            {"name": "v2", "type": "int"}
                          ] } },
       { "name": "r4", "type": "tr2" }
    ] } ]]

local schemas = { person, person_default }

-----------------------------------------------------------------------

t {
    schema = person,
    func = 'flatten',
    input = [[{
        "FirstName": "John", "LastName": "Doe", "Age":33, "Sex":1,
        "PhoneNumber": "+7 999 1234567",
        "HomeAddress": "Long Street, 1", "Occupation": "Engineer"
    }]],
    output = '["John", "Doe", 33, 1, "+7 999 1234567", "Long Street, 1", "Engineer"]'
}

-- check different key orders
t {
    schema = person,
    func = 'flatten',
    input = [[{
        "HomeAddress": "Long Street, 1", "Occupation": "Engineer",
        "FirstName": "John", "LastName": "Doe", "Age":33, "Sex":1,
        "PhoneNumber": "+7 999 1234567"
    }]],
    output = '["John", "Doe", 33, 1, "+7 999 1234567", "Long Street, 1", "Engineer"]'
}

t {
    schema = person,
    func = 'flatten',
    input = [[{
        "PhoneNumber": "+7 999 1234567",
        "FirstName": "John", "LastName": "Doe",
        "Age":33,
        "HomeAddress": "Long Street, 1",
        "Sex":1,
        "Occupation": "Engineer"
    }]],
    output = '["John", "Doe", 33, 1, "+7 999 1234567", "Long Street, 1", "Engineer"]'
}

-- unflatten
t {
    schema = person,
    func = 'unflatten',
    input = '["Jane", "Doe", 21, 0, "+7 999 1234567", "Long Street, 1", "Engineer"]',
    output = [[{
        "FirstName": "Jane", "LastName": "Doe", "Age":21, "Sex":0,
        "PhoneNumber": "+7 999 1234567",
        "HomeAddress": "Long Street, 1", "Occupation": "Engineer"
    }]]
}

-- flatten, defaults
t {
    schema = person_default,
    func = 'flatten',
    input = [[{
        "FirstName": "John", "Age":33, "Sex":1,
        "PhoneNumber": "+7 999 1234567",
        "HomeAddress": "Long Street, 1"
    }]],
    output = '["John", "", 33, 1, "+7 999 1234567", "Long Street, 1", ""]'
}

t {
    schema = person_default,
    func = 'flatten',
    input = [[{
        "FirstName": "John", "LastName": "Doe", "Age":33, "Sex":1,
        "Occupation": "Engineer"
    }]],
    output = '["John", "Doe", 33, 1, "", "", "Engineer"]'
}

-- validation errors
t {
    error = 'Expecting MAP, encountered LONG',
    schema = person,
    func = 'flatten',
    input = '42'
}

t {
    error = 'Unknown key: "InvalidKey"',
    schema = person,
    func = 'flatten',
    input = '{"InvalidKey": 42}'
}

--
local funcs = { 'flatten', 'xflatten'}
for schema = 1,#schemas do

    _G['schema'] = schema

    for func = 1,#funcs do

        _G['func'] = func

        t {
            error = 'FirstName: Expecting STR, encountered LONG',
            schema = schemas[ schema ],
            func   = funcs  [ func   ],
            input = '{"FirstName": 42}'
        }

        t {
            error = 'LastName: Expecting STR, encountered LONG',
            schema = schemas[ schema ],
            func   = funcs  [ func   ],
            input = '{"LastName": 42}'
        }

        t {
            error = 'Age: Expecting INT, encountered DOUBLE',
            schema = schemas[ schema ],
            func   = funcs  [ func   ],
            input = '{"Age": 42.0}'
        }

        t {
            error = 'PhoneNumber: Expecting STR, encountered LONG',
            schema = schemas[ schema ],
            func   = funcs  [ func   ],
            input = '{"PhoneNumber": 42}'
        }

        t {
            error = 'HomeAddress: Expecting STR, encountered LONG',
            schema = schemas[ schema ],
            func   = funcs  [ func   ],
            input = '{"HomeAddress": 42}'
        }

        t {
            error = 'Occupation: Expecting STR, encountered LONG',
            schema = schemas[ schema ],
            func   = funcs  [ func   ],
            input = '{"Occupation": 42}'
        }
    end
end

_G['schema'] = nil
_G['func'] = nil

--

t {
    error = 'Key missing: "FirstName"',
    schema = person,
    func = 'flatten',
    input = '{}'
}

t {
    error = 'Key missing: "FirstName"',
    schema = person_default,
    func = 'flatten',
    input = '{}'
}

t {
    error = 'Key missing: "LastName"',
    schema = person,
    func = 'flatten',
    input = '{"FirstName": "Jane"}'
}

t {
    error = 'Key missing: "Age"',
    schema = person,
    func = 'flatten',
    input = '{"FirstName": "Jane", "LastName": "Doe"}'
}

t {
    error = 'Key missing: "Age"',
    schema = person_default,
    func = 'flatten',
    input = '{"FirstName": "Jane"}'
}

t {
    error = 'Key missing: "Sex"',
    schema = person,
    func = 'flatten',
    input = '{"FirstName": "Jane", "LastName": "Doe", "Age": 21}'
}

t {
    error = 'Key missing: "Sex"',
    schema = person_default,
    func = 'flatten',
    input = '{"FirstName": "Jane", "Age": 21}'
}

t {
    error = 'Key missing: "PhoneNumber"',
    schema = person,
    func = 'flatten',
    input = '{"FirstName": "Jane", "LastName": "Doe", "Age": 21, "Sex": 0}'
}

t {
    error = 'Key missing: "HomeAddress"',
    schema = person,
    func = 'flatten',
    input = [[{
        "FirstName": "Jane", "LastName": "Doe", "Age": 21, "Sex": 0,
        "PhoneNumber": "+7 ???"
    }]]
}

t {
    error = 'Key missing: "Occupation"',
    schema = person,
    func = 'flatten',
    input = [[{
        "FirstName": "Jane", "LastName": "Doe", "Age": 21, "Sex": 0,
        "PhoneNumber": "+7 ???", "HomeAddress": "Long St., 1"
    }]]
}

--

for schema = 1,#schemas do

    _G['schema'] = schema

    t {
        error = 'Expecting ARRAY, encountered LONG',
        schema = schemas[schema],
        func = 'unflatten', input = '42'
    }

    t {
        error = 'Expecting ARRAY of length 7. Encountered ARRAY of length 1.',
        schema = schemas[schema],
        func = 'unflatten', input = '[42]'
    }

    t {
        error = 'Expecting ARRAY of length 7. Encountered ARRAY of length 8.',
        schema = schemas[schema],
        func = 'unflatten',
        input = '["John", "", 33, 1, "+7 999 1234567", "Long Street, 1", "", 42]'
    }

    t {
        error = '1: Expecting STR, encountered NIL',
        schema = schemas[schema],
        func = 'unflatten',
        input = '[null, "", 33, 1, "+7 999 1234567", "Long Street, 1", ""]'
    }

    t {
        error = '2: Expecting STR, encountered NIL',
        schema = schemas[schema],
        func = 'unflatten',
        input = '["John", null, 33, 1, "+7 999 1234567", "Long Street, 1", ""]'
    }

    t {
        error = '3: Expecting INT, encountered NIL',
        schema = schemas[schema],
        func = 'unflatten',
        input = '["John", "", null, 1, "+7 999 1234567", "Long Street, 1", ""]'
    }

    t {
        error = '4: Expecting INT, encountered NIL',
        schema = schemas[schema],
        func = 'unflatten',
        input = '["John", "", 33, null, "+7 999 1234567", "Long Street, 1", ""]'
    }

    t {
        error = '5: Expecting STR, encountered NIL',
        schema = schemas[schema],
        func = 'unflatten',
        input = '["John", "", 33, 1, null, "Long Street, 1", ""]'
    }

    t {
        error = '6: Expecting STR, encountered NIL',
        schema = schemas[schema],
        func = 'unflatten',
        input = '["John", "", 33, 1, "+7 999 1234567", null, ""]'
    }

    t {
        error = '7: Expecting STR, encountered NIL',
        schema = schemas[schema],
        func = 'unflatten',
        input = '["John", "", 33, 1, "+7 999 1234567", "Long Street, 1", null]'
    }

end

_G['schema'] = nil

-- xflatten

for schema = 1,#schemas do 

    _G['schema'] = schema

    t {
        schema = schemas[schema], func = 'xflatten',
        input = '{"FirstName": "John"}',
        output = '[["=", 1, "John"]]'
    }

    t {
        schema = schemas[schema], func = 'xflatten',
        input = '{"LastName": "Doe"}',
        output = '[["=", 2, "Doe"]]'
    }

    t {
        schema = schemas[schema], func = 'xflatten',
        input = '{"Age": 33}',
        output = '[["=", 3, 33]]'
    }

    t {
        schema = schemas[schema], func = 'xflatten',
        input = '{"Sex": 1}',
        output = '[["=", 4, 1]]'
    }

    t {
        schema = schemas[schema], func = 'xflatten',
        input = '{"PhoneNumber": "+7 999 1234567"}',
        output = '[["=", 5, "+7 999 1234567"]]'
    }
    t {
        schema = schemas[schema], func = 'xflatten',
        input = '{"HomeAddress": "Long St., 1"}',
        output = '[["=", 6, "Long St., 1"]]'
    }
    t {
        schema = schemas[schema], func = 'xflatten',
        input = '{"Occupation": "Engineer"}',
        output = '[["=", 7, "Engineer"]]'
    }

    --

    t {
        schema = schemas[schema], func = 'xflatten',
        input = '{"FirstName": "John", "LastName": "Doe", "Age": 33}',
        output = '[["=", 1, "John"], ["=", 2, "Doe"], ["=", 3, 33]]'
    }

end

_G['schema'] = nil

t {
    schema = [[{
        "name": "foo", "type": "record", "fields": [
            {"name": "_", "type": {"type":"array", "items":"int"}, "default":[]}
        ]
    }]],
    func = 'flatten', input = '{}', output = '[[]]'
}

t {
    schema = [[{
        "name": "foo", "type": "record", "fields": [
            {"name": "_", "type": {"type":"map", "values":"int"}, "default":[]}
        ]
    }]],
    func = 'flatten', input = '{}', output = '[{}]'
}

t {
    schema = record_nullable,
    func = 'flatten',  input = [[{
                                    "r1": {"v1": 1, "v2": "hello" },
                                    "r2": {"v1": 2, "v2": "hello2" },
                                    "dummy": [1, 2, 3],
                                    "r3": {"v1": "world", "v2": 2},
                                    "r4": {"v1": "WAT", "v2": 3}
                               }]],

    output = '[1, "hello", [2, "hello2"], [1, 2, 3], ["world", 2], "WAT", 3]'
}

t {
    schema = record_nullable,
    func = 'flatten',  input = [[{
                                    "r1": {"v1": 1, "v2": "hello" },
                                    "r2": null,
                                    "dummy": [1, 2, 3],
                                    "r3": null,
                                    "r4": {"v1": "WAT", "v2": 3}
                               }]],

    output = '[1, "hello", null, [1, 2, 3], null, "WAT", 3]'
}

t {
    error = 'r1: Expecting MAP, encountered NIL',
    schema = record_nullable,
    func = 'flatten',  input = [[{
                                    "r1": null,
                                    "r2": {"v1": 1, "v2": "hello" },
                                    "dummy": [1, 2, 3],
                                    "r3": null,
                                    "r4": {"v1": "WAT", "v2": 3}
                               }]],
}

t {
    error = 'r4: Expecting MAP, encountered NIL',
    schema = record_nullable,
    func = 'flatten',  input = [[{
                                    "r1": {"v1": 1, "v2": "hello" },
                                    "r2": {"v1": 2, "v2": "hello2" },
                                    "dummy": [1, 2, 3],
                                    "r3": {"v1": "WAT", "v2": 3},
                                    "r4": null
                               }]],
}

t {
    schema = record_nullable,
    func = 'unflatten',
    input = '[1, "hello", [2, "hello2"], [1, 2, 3], ["world", 2], "WAT", 3]',
    output= [[{"r1": {"v1": 1, "v2": "hello" },
              "r2": {"v1": 2, "v2": "hello2" },
              "dummy": [1, 2, 3],
              "r3": {"v1": "world", "v2": 2},
              "r4": {"v1": "WAT", "v2": 3}}]]
}

t {
    schema = record_nullable,
    func = 'unflatten',
    input = '[1, "hello", null, [1, 2, 3], null, "WAT", 3]',
    output= [[{"r1": {"v1": 1, "v2": "hello" },
              "r2": null,
              "dummy": [1, 2, 3],
              "r3": null,
              "r4": {"v1": "WAT", "v2": 3}}]]
}

t {
    schema = [[{
      "name": "foo", "type": "record", "fields": [
        {"name": "X", "type": "int*"},
        {"name": "Y", "type": "string"} ]
      }]],
    func = 'flatten',
    input = [[ { "X": 42, "Y": "kek" } ]],
    output = ' [42, "kek"]'
}

t {
    schema = [[{
      "name": "foo", "type": "record", "fields": [
        {"name": "X", "type": "int*"},
        {"name": "Y", "type": "string"} ]
      }]],
    func = 'unflatten',
    input = ' [42, "kek"]',
    output = [[ { "X": 42, "Y": "kek" } ]]
}

t {
    schema = [[{
      "name": "foo", "type": "record", "fields": [
        {"name": "X", "type": "int*"},
        {"name": "Y", "type": "string"} ]
      }]],
    func = 'flatten',
    input = [[ { "X": null, "Y": "kek" } ]],
    output = ' [null, "kek"]'
}

t {
    schema = [[{
      "name": "foo", "type": "record", "fields": [
        {"name": "X", "type": "int*"},
        {"name": "Y", "type": "string"} ]
      }]],
    func = 'unflatten',
    input = ' [null, "kek"]',
    output = [[ { "X": null, "Y": "kek" } ]]
}

-- gh-35: a regression test. Make sure, that if field
-- marked nullable, then this means to flattener that
-- absense of such a field in data means it is NULL.
t {
    schema = [[{
      "name": "foo", "type": "record", "fields": [
        {"name": "X", "type": "int*"},
        {"name": "Y", "type": "string"} ]
      }]],
    func = 'flatten',
    input = [[ { "Y": "kek" } ]],
    output = ' [null, "kek"]'
}

-- Check that fields which are defined after the fields with undefined size are
-- are flattened and unflattened correctly.
local complex_nullable_schema = [[
    {"type":"record*","name":"X","fields":[
        {"name":"f1","type":"int"},
        {"name":"f2","type":
            {"type":"array*","items":"int*"}},
        {"name":"f3","type":
            {"type":"record*","name":"Y","fields":[
                {"name":"yf1","type":"string*"}]}},
        {"name":"f4","type":
            {"type":"map*","values":"X"}},
        {"name":"f5","type":"double"}]}]]

t {
    schema = complex_nullable_schema,
    func = 'flatten',
    input = [[
        {"f4":
            {"map_element_1":
                {"f5":15,"f1":13}},
        "f2":
            [5,7,9,null],
        "f5":17,
        "f3":
            {"yf1":"yf1_11"},
        "f1":3}]],
    output = [=[
    [[
        3,
        [5,7,9,null],
        ["yf1_11"],
        {"map_element_1":[13,null,null,null,15.0]},
        17.0]]
    ]=]
}

t {
    schema = complex_nullable_schema,
    func = 'unflatten',
    input = [=[
    [[
        3,
        [5,7,9,null],
        ["yf1_11"],
        {"map_element_1":[13,null,null,null,15.0]},
        17.0]]
    ]=],
    output = [[{
        "f1": 3, "f2": [5, 7, 9, null],
        "f3": {"yf1": "yf1_11"},
        "f4": {"map_element_1":
            {"f1": 13, "f2": null, "f3": null, "f4": null, "f5": 15.0}},
        "f5": 17.0 } ]]
}

-- nullable xflatten
local nullable_xflatten = [[{
    "type": "record", "name": "X", "fields": [
        {"name": "f1", "type": "int*"},
        {"name": "f2", "type": {
            "type": "array*", "items": "string*"}},
        {"name": "f3", "type":[
            "int", "X*"]},
        {"name": "f4", "type": {
            "type": "map*", "values": "int*"}}]}
    ]]

t {
    schema = nullable_xflatten,
    func = "xflatten",
    input = '{"f1":null, "f2":null, "f3":{"X":null}, "f4":null}',
    output = [=[
        [["=", 1, null], ["=", 2, null], ["=", 3, 1],
        ["=", 4, null], ["=", 5, null]]
    ]=]
}

t {
    schema = nullable_xflatten,
    func = "xflatten",
    input = '{"f3":{"X":{"f1":1}}}',
    error = [[f3/X: Key missing: "f3"]]
}

t {
    schema = nullable_xflatten,
    func = "xflatten",
    input = '{"f3":{"X":{"f1":1, "f3":{"int":1}}}}',
    output = [=[
        [["=", 3, 1], ["=", 4, [1, null, 0, 1, null]]]
    ]=]
}

local nullable_root_record_xflatten = [[{
        "type": "record*", "name": "X", "fields": [
            {"name": "f1", "type": "int"},
            {"name": "f2", "type": "int"}]}
    ]]

t {
    schema = nullable_root_record_xflatten,
    func = "xflatten",
    input = [[ {"f1":1} ]],
    error = [[Key missing: "f2"]]
}

t {
    schema = nullable_root_record_xflatten,
    func = "xflatten",
    input = [[ {"f1":1, "f2":2} ]],
    output = [=[
        [["=", 1, [1,2]]]
    ]=]
}

local nullable_record_xflatten = [[{
        "type": "record", "name": "X", "fields": [
            {"name": "f1", "type": "int"},
            {"name": "f2", "type": {
                "type": "record*", "name": "Y", "fields": [
                    {"name": "f3", "type": "int"},
                    {"name": "f4", "type": "int"} ] }} ]}
    ]]

t {
    schema = nullable_record_xflatten,
    func = "xflatten",
    input = [[ {"f2":{"f3":3}} ]],
    error = [[f2: Key missing: "f4"]]
}

t {
    schema = nullable_record_xflatten,
    func = "xflatten",
    input = [[ {"f2":{"f3":3, "f4":4}} ]],
    output = [[ [ ["=", 2, [3,4] ] ] ]]
}

-- ensure that element position is calculated correctly
local complex_xflatten = [[
    {"type":"record","name":"X","fields":[
        {"name":"f1","type":"int"},
        {"name":"f2","type":
            {"type":"array*","items":"int*"}},
        {"name":"f3","type":
            {"type":"record*","name":"Y","fields":[
                {"name":"yf1","type":"string*"}]}},
        {"name":"f4","type":
            {"type":"map*","values":"X"}},
        {"name":"f5","type":"double"}]}]]

t {
    schema = complex_xflatten,
    func = "xflatten",
    input = [[
        {"f2": [2, null], "f3": {"yf1": "yf1str"},
        "f4": {"map_key_1" : { "f1":1, "f5":5.0 } },
        "f5": 5.5 }
    ]],
    output = [=[
        [["=", 2, [2, null]], ["=", 3, ["yf1str"]],
        ["=", 4, {"map_key_1": [1, null, null, null, 5.0]}],
        ["=", 5, 5.5]]
    ]=]
}
