#!/usr/bin/env python
# coding: utf-8

#   config.py - generator for config file parsers
#
#   Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
#
#   This file is part of The Mana World (Athena server)
#
#   This program is free software: you can redistribute it and/or modify
#   it under the terms of the GNU Affero General Public License as published by
#   the Free Software Foundation, either version 3 of the License, or
#   (at your option) any later version.
#
#   This program 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 Affero General Public License for more details.
#
#   You should have received a copy of the GNU Affero General Public License
#   along with this program.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import print_function

import glob
import os

from protocol import OpenWrite


generated = '// This is a generated file, edit %s instead\n' % __file__

copyright = '''//    {filename} - {description}
//
//    Copyright © 2014 Ben Longbons <b.r.longbons@gmail.com>
//
//    This file is part of The Mana World (Athena server)
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program 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 Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
'''


class AnyHeader(object):
    __slots__ = ('name')

    def __init__(self, name):
        self.name = name

class SystemHeader(AnyHeader):
    __slots__ = ()
    meta = 0

    def relative_to(self, path):
        return '<%s>' % self.name

class Header(AnyHeader):
    __slots__ = ()
    meta = 1

    def relative_to(self, path):
        return '"%s"' % os.path.relpath(self.name, path)


class ConfigType(object):
    __slots__ = ()

class SimpleType(ConfigType):
    __slots__ = ('name', 'headers')

    def __init__(self, name, headers):
        self.name = name
        self.headers = frozenset(headers)

    def __repr__(self):
        return 'SimpleType(%r, %r)' % (self.name, self.headers)

    def type_name(self):
        return self.name

    def dump_extract(self, cpp, var):
        cpp.write(
'''
            if (!extract(value.data, &{var}))
            {{
                value.span.error("Failed to extract value"_s);
                return false;
            }}
'''.lstrip('\n').format(var=var))

class PhonyType(ConfigType):
    __slots__ = ('type', 'name', 'call', 'headers')

    def __init__(self, type, name, call, extra_headers):
        self.type = type
        self.name = name
        self.call = call
        self.headers = type.headers | extra_headers

    def __repr__(self):
        return 'PhonyType(%r, %r, %r, %r)' % (self.type, self.name, self.call, self.headers)

    def type_name(self):
        return '// special %s' % self.type.type_name()

    def dump_extract(self, cpp, var):
        cpp.write('            %s %s;\n' % (self.type.type_name(),  self.name))
        self.type.dump_extract(cpp, self.name)
        cpp.write('            %s\n' % self.call)

class TransformedType(ConfigType):
    __slots__ = ('type', 'transform', 'headers')

    def __init__(self, type, transform, extra_headers=set()):
        self.type = type
        self.transform = transform
        self.headers = type.headers | extra_headers

    def __repr__(self):
        return 'TransformedType(%r, %r)' % (self.type, self.transform)

    def type_name(self):
        return self.type.type_name()

    def dump_extract(self, cpp, var):
        self.type.dump_extract(cpp, var)
        cpp.write('            %s;\n' % self.transform)

class BoundedType(ConfigType):
    __slots__ = ('type', 'low', 'high', 'headers')

    def __init__(self, type, low, high, extra_headers=set()):
        assert isinstance(type, ConfigType)
        self.type = type
        self.low = low
        self.high = high
        self.headers = type.headers | extra_headers

    def __repr__(self):
        return 'BoundedType(%r, %r, %r, %r)' % (self.type, self.low, self.high, self.headers)

    def type_name(self):
        return self.type.type_name()

    def dump_extract(self, cpp, var):
        self.type.dump_extract(cpp, var)
        cpp.write(
'''
            if (!({low} <= {var} && {var} <= {high}))
            {{
                value.span.error("Value of {name} not in range [{low}, {high}]"_s);
                return false;
            }}
'''.format(low=self.low, high=self.high, var=var, name=var.split('.')[-1]))

class MinBoundedType(ConfigType):
    __slots__ = ('type', 'low', 'headers')

    def __init__(self, type, low, extra_headers=set()):
        assert isinstance(type, ConfigType)
        self.type = type
        self.low = low
        self.headers = type.headers | extra_headers

    def __repr__(self):
        return 'MinBoundedType(%r, %r, %r, %r)' % (self.type, self.low, self.headers)

    def type_name(self):
        return self.type.type_name()

    def dump_extract(self, cpp, var):
        self.type.dump_extract(cpp, var)
        cpp.write(
'''
            if (!({low} <= {var}))
            {{
                value.span.error("Value of {name} not at least {low}"_s);
                return false;
            }}
'''.format(low=self.low, var=var, name=var.split('.')[-1]))

class Option(object):
    __slots__ = ('name', 'type', 'default', 'headers')

    def __init__(self, name, type, default, extra_headers=set()):
        self.name = name
        self.type = type
        self.default = default
        self.headers = type.headers | extra_headers

    def dump1(self, hpp):
        hpp.write('    %s %s = %s;\n' % (self.type.type_name(), self.name, self.default))
    def dump2(self, cpp, x):
        # NOTE about hashing
        # dead simple hash: pack 6 bits of first 5 letters into an int
        y = self.name[:5]
        if x != y:
            if x is not None:
                cpp.write('        break;\n')
            c0 = y[0] if len(y) > 0 else '\\0'
            c1 = y[1] if len(y) > 1 else '\\0'
            c2 = y[2] if len(y) > 2 else '\\0'
            c3 = y[3] if len(y) > 3 else '\\0'
            c4 = y[4] if len(y) > 4 else '\\0'
            assert len(y) >= 3, '<-- change this number in the source file for: %r' % self.name
            cpp.write("    case (('%s' << 24) | ('%s' << 18) | ('%s' << 12) | ('%s' << 6) | ('%s' << 0)):\n" % (c0, c1, c2, c3, c4))
        cpp.write('        if (key.data == "{name}"_s)\n'.format(name=self.name))
        cpp.write('        {\n')
        self.type.dump_extract(cpp, 'conf.%s' % self.name)
        cpp.write('            return true;\n')
        cpp.write('        }\n')
        return y

class Group(object):
    __slots__ = ('name', 'options', 'extra_headers')

    def __init__(self, name):
        self.name = name
        self.options = {}
        self.extra_headers = []

    def extra(self, h):
        self.extra_headers.append(h)

    def opt(self, name, type, default, extra_headers=set(), pre=None, post=None, min=None, max=None):
        assert name not in self.options, 'Duplicate option name: %s' % name
        assert isinstance(default, str)
        if pre is not None:
            type = TransformedType(type, pre)
        if min is not None:
            if max is not None:
                type = BoundedType(type, min, max)
            else:
                type = MinBoundedType(type, min)
        else:
            assert max is None
        if post is not None:
            type = TransformedType(type, post)
        self.options[name] = rv = Option(name, type, default, extra_headers)
        return rv

    def dump_in(self, path, namespace_name):
        if namespace_name == 'char':
            namespace_name += '_'
        var_name = '%s_conf' % self.name
        class_name = var_name.replace('_', ' ').title().replace(' ', '')
        short_hpp_name = '%s.hpp' % var_name
        hpp_name = os.path.join(path, short_hpp_name)
        short_cpp_name = '%s.cpp' % var_name
        cpp_name = os.path.join(path, short_cpp_name)

        values = sorted(self.options.values(), key=lambda o: o.name)

        desc = 'Config for %s::%s' % (namespace_name, self.name)
        with OpenWrite(hpp_name) as hpp, \
                OpenWrite(cpp_name) as cpp:
            hpp.write('#pragma once\n')
            hpp.write(copyright.format(filename=short_hpp_name, description=desc))
            hpp.write('\n')
            hpp.write(generated)
            cpp.write('#include "%s"\n' % short_hpp_name)
            cpp.write(copyright.format(filename=short_cpp_name, description=desc))
            cpp.write('\n')
            cpp.write(generated)
            headers = {
                    Header('src/io/fwd.hpp'),
                    Header('src/strings/fwd.hpp')
            }
            for o in values:
                headers |= o.headers

            hpp.write('\n')
            hpp.write('#include "fwd.hpp"\n')
            for h in sorted(headers, key=lambda h: (h.meta, h.name)):
                hpp.write('#include %s\n' % h.relative_to(path))
            hpp.write('\n')
            cpp.write('\n')
            for h in [
                    SystemHeader('bitset'),
                    Header('src/io/extract.hpp'),
                    Header('src/io/span.hpp'),
                    Header('src/mmo/extract_enums.hpp'),
                    Header('src/high/extract_mmo.hpp'),
            ] + self.extra_headers:
                cpp.write('#include %s\n' % h.relative_to(path))
            cpp.write('\n')
            cpp.write('#include "../poison.hpp"\n')
            cpp.write('\n')

            hpp.write('namespace tmwa\n{\n')
            cpp.write('namespace tmwa\n{\n')
            cpp.write('''
static __attribute__((unused))
bool extract(XString str, bool *v)
{
    if (str == "true"_s || str == "on"_s || str == "yes"_s
        || str == "oui"_s || str == "ja"_s
        || str == "si"_s || str == "1"_s)
    {
        *v = 1;
        return true;
    }
    if (str == "false"_s || str == "off"_s || str == "no"_s
        || str == "non"_s || str == "nein"_s || str == "0"_s)
    {
        *v = 0;
        return true;
    }
    return false;
}

static __attribute__((unused))
bool extract(XString str, std::bitset<256> *v)
{
    if (!str)
    {
        v->reset();
        return true;
    }
    for (uint8_t c : str)
    {
        (*v)[c] = true;
    }
    return true;
}
''')
            hpp.write('namespace %s\n{\n' % namespace_name)
            cpp.write('namespace %s\n{\n' % namespace_name)
            hpp.write('struct %s\n{\n' % class_name)
            for o in values:
                o.dump1(hpp)
            hpp.write('}; // struct %s\n' % class_name)
            hpp.write('bool parse_%s(%s& conf, io::Spanned<XString> key, io::Spanned<ZString> value);\n' % (var_name, class_name))
            hpp.write('} // namespace %s\n' % namespace_name)
            hpp.write('} // namespace tmwa\n')
            cpp.write('bool parse_%s(%s& conf, io::Spanned<XString> key, io::Spanned<ZString> value)\n{\n' % (var_name, class_name))
            # see NOTE about hashing in Option.dump2
            cpp.write('    int key_hash = 0;\n')
            cpp.write('    if (key.data.size() > 0)\n')
            cpp.write('        key_hash |= key.data[0] << 24;\n')
            cpp.write('    if (key.data.size() > 1)\n')
            cpp.write('        key_hash |= key.data[1] << 18;\n')
            cpp.write('    if (key.data.size() > 2)\n')
            cpp.write('        key_hash |= key.data[2] << 12;\n')
            cpp.write('    if (key.data.size() > 3)\n')
            cpp.write('        key_hash |= key.data[3] << 6;\n')
            cpp.write('    if (key.data.size() > 4)\n')
            cpp.write('        key_hash |= key.data[4] << 0;\n')
            cpp.write('    switch (key_hash)\n')
            cpp.write('    {\n')
            x = None
            for o in values:
                x = o.dump2(cpp, x)
            cpp.write('        break;\n')
            cpp.write('    } // switch\n')
            cpp.write('    key.span.error("Unknown config key"_s);\n')
            cpp.write('    return false;\n')
            cpp.write('} // fn parse_%s_conf()\n' % var_name)
            cpp.write('} // namespace %s\n' % namespace_name)
            cpp.write('} // namespace tmwa\n')

class Realm(object):
    __slots__ = ('path', 'groups')

    def __init__(self, path):
        self.path = path
        self.groups = {}

    def conf(self, name=None):
        if not name:
            name = self.path.split('/')[-1]
        assert name not in self.groups, 'Duplicate group name: %s' % name
        self.groups[name] = rv = Group(name)
        return rv

    def dump(self):
        for g in self.groups.values():
            g.dump_in(self.path, self.path.split('/')[-1])

class Everything(object):
    __slots__ = ('realms')

    def __init__(self):
        self.realms = {}

    def realm(self, path):
        assert path not in self.realms, 'Duplicate realm path: %s' % path
        self.realms[path] = rv = Realm(path)
        return rv

    def dump(self):
        for g in glob.glob('src/*/*_conf.[ch]pp'):
            os.rename(g, g + '.old')
        for v in self.realms.values():
            v.dump()
        for g in glob.glob('src/*/*_conf.[ch]pp.old'):
            print('Obsolete: %s' % g)
            os.remove(g)


def lit(s):
    return '"%s"_s' % s.replace('\\', '\\\\').replace('"', '\\"')

def build_config():
    rv = Everything()

    # realms
    login_realm = rv.realm('src/login')
    admin_realm = rv.realm('src/admin')
    char_realm = rv.realm('src/char')
    map_realm = rv.realm('src/map')

    # confs
    login_conf = login_realm.conf()
    login_lan_conf = login_realm.conf('login_lan')

    admin_conf = admin_realm.conf()

    char_conf = char_realm.conf()
    char_lan_conf = char_realm.conf('char_lan')
    inter_conf = char_realm.conf('inter')

    map_conf = map_realm.conf()
    battle_conf = map_realm.conf('battle')

    # headers
    cstdint_sys = SystemHeader('cstdint')
    vector_sys = SystemHeader('vector')
    bitset_sys = SystemHeader('bitset')

    ip_h = Header('src/net/ip.hpp')
    rstring_h = Header('src/strings/rstring.hpp')
    literal_h = Header('src/strings/literal.hpp')
    ids_h = Header('src/mmo/ids.hpp')
    strs_h = Header('src/mmo/strs.hpp')
    timer_th = Header('src/net/timer.t.hpp')
    login_th = Header('src/login/login.t.hpp')
    udl_h = Header('src/ints/udl.hpp')
    net_point_h = Header('src/proto2/net-Point.hpp')
    char_h = Header('src/char/char.hpp')
    map_h = Header('src/map/map.hpp')
    map_th = Header('src/map/map.t.hpp')
    npc_h = Header('src/map/npc.hpp')
    npc_parse_h = Header('src/map/npc-parse.hpp')

    map_conf.extra(npc_parse_h)

    # types
    bool = SimpleType('bool', set())
    #double = SimpleType('double', set())
    u8 = SimpleType('uint8_t', {cstdint_sys})
    u16 = SimpleType('uint16_t', {cstdint_sys})
    u32 = SimpleType('uint32_t', {cstdint_sys})
    u64 = SimpleType('uint64_t', {cstdint_sys})
    i8 = SimpleType('int8_t', {cstdint_sys})
    i16 = SimpleType('int16_t', {cstdint_sys})
    i32 = SimpleType('int32_t', {cstdint_sys})
    i64 = SimpleType('int64_t', {cstdint_sys})

    percent = i32
    perk = i32
    per10k = i32
    #per10kd = double
    per10kd = i32

    IP4Address = SimpleType('IP4Address', {ip_h})
    IP4Mask = SimpleType('IP4Mask', {ip_h})
    IpSet = SimpleType('std::vector<IP4Mask>', {vector_sys, ip_h})
    RString = SimpleType('RString', {rstring_h, literal_h})
    GmLevel = SimpleType('GmLevel', {ids_h})
    hours = SimpleType('std::chrono::hours', {timer_th})
    seconds = SimpleType('std::chrono::seconds', {timer_th})
    milliseconds = SimpleType('std::chrono::milliseconds', {timer_th})
    ACO = SimpleType('ACO', {login_th})
    ServerName = SimpleType('ServerName', {strs_h})
    AccountName = SimpleType('AccountName', {strs_h})
    AccountPass = SimpleType('AccountPass', {strs_h})
    Point = SimpleType('Point', {net_point_h})
    CharName = SimpleType('CharName', {strs_h})
    CharBitset = SimpleType('std::bitset<256>', {bitset_sys})
    MapName = SimpleType('MapName', {strs_h})
    ATK = SimpleType('ATK', {map_th})

    addmap = PhonyType(MapName, 'name', 'map_addmap(name);', {map_h})
    delmap = PhonyType(MapName, 'name', 'map_delmap(name);', {map_h})
    addnpc = PhonyType(RString, 'npc', 'npc_addsrcfile(npc);', {npc_h})
    delnpc = PhonyType(RString, 'npc', 'npc_delsrcfile(npc);', {npc_h})


    # options
    login_lan_conf.opt('lan_char_ip', IP4Address, 'IP4_LOCALHOST')
    login_lan_conf.opt('lan_subnet', IP4Mask, 'IP4Mask(IP4_LOCALHOST, IP4_BROADCAST)')

    login_conf.opt('admin_state', bool, 'false')
    login_conf.opt('admin_pass', AccountPass, '{}')
    login_conf.opt('ladminallowip', IpSet, '{}')
    login_conf.opt('new_account', bool, 'false')
    login_conf.opt('login_port', u16, '6901', min='1024')
    login_conf.opt('account_filename', RString, lit('save/account.txt'))
    login_conf.opt('gm_account_filename', RString, lit('save/gm_account.txt'))
    login_conf.opt('gm_account_filename_check_timer', seconds, '15_s')
    login_conf.opt('login_log_filename', RString, lit('log/login.log'))
    login_conf.opt('display_parse_login', bool, 'false')
    login_conf.opt('display_parse_admin', bool, 'false')
    login_conf.opt('display_parse_fromchar', i32, '0', min='0', max='2')
    login_conf.opt('min_level_to_connect', GmLevel, 'GmLevel::from(0_u32)', {udl_h})
    login_conf.opt('order', ACO, 'ACO::DENY_ALLOW')
    login_conf.opt('allow', IpSet, '{}')
    login_conf.opt('deny', IpSet, '{}')
    login_conf.opt('anti_freeze_enable', bool, 'false')
    login_conf.opt('anti_freeze_interval', seconds, '15_s')
    login_conf.opt('update_host', RString, '{}')
    login_conf.opt('main_server', ServerName, '{}')
    login_conf.opt('userid', AccountName, '{}')
    login_conf.opt('passwd', AccountPass, '{}')
    login_conf.opt('conn_limit_enable', bool, 'true')
    login_conf.opt('conn_limit_interval', seconds, '5_s')


    admin_conf.opt('login_ip', IP4Address, 'IP4_LOCALHOST')
    admin_conf.opt('login_port', u16, '6901', min='1024')
    admin_conf.opt('admin_pass', AccountPass, 'stringish<AccountPass>("admin"_s)')
    admin_conf.opt('ladmin_log_filename', RString, lit('log/ladmin.log'))


    char_lan_conf.opt('lan_map_ip', IP4Address, 'IP4_LOCALHOST')
    char_lan_conf.opt('lan_subnet', IP4Mask, 'IP4Mask(IP4_LOCALHOST, IP4_BROADCAST)')

    char_conf.opt('userid', AccountName, '{}')
    char_conf.opt('passwd', AccountPass, '{}')
    char_conf.opt('server_name', ServerName, '{}')
    char_conf.opt('login_ip', IP4Address, '{}')
    char_conf.opt('login_port', u16, '6901', min='1024')
    char_conf.opt('char_ip', IP4Address, '{}')
    char_conf.opt('char_port', u16, '6121', min='1024')
    char_conf.opt('char_txt', RString, '{}')
    char_conf.opt('max_connect_user', u32, '0')
    char_conf.opt('autosave_time', seconds, 'DEFAULT_AUTOSAVE_INTERVAL', {char_h}, min='1_s')
    char_conf.opt('start_point', Point, '{ {"001-1.gat"_s}, 273, 354 }')
    char_conf.opt('unknown_char_name', CharName, 'stringish<CharName>("Unknown"_s)')
    char_conf.opt('char_log_filename', RString, lit('log/char.log'))
    char_conf.opt('char_name_letters', CharBitset, '{}')
    char_conf.opt('online_txt_filename', RString, lit('online.txt'))
    char_conf.opt('online_html_filename', RString, lit('online.html'))
    char_conf.opt('online_gm_display_min_level', GmLevel, 'GmLevel::from(20_u32)', {udl_h})
    char_conf.opt('online_refresh_html', u32, '20', min=1)
    char_conf.opt('max_hair_style', u16, '20', min=1)
    char_conf.opt('max_hair_color', u16, '11', min=1)
    char_conf.opt('min_stat_value', u16, '1')
    char_conf.opt('max_stat_value', u16, '9', min=1)
    char_conf.opt('total_stat_sum', u16, '30', min=6)
    char_conf.opt('min_name_length', u16, '4', min=4)
    char_conf.opt('char_slots', u16, '9', min=1)
    char_conf.opt('anti_freeze_enable', bool, 'false')
    char_conf.opt('anti_freeze_interval', seconds, '6_s', min='5_s')

    inter_conf.opt('storage_txt', RString, lit('save/storage.txt'))
    inter_conf.opt('party_txt', RString, lit('save/party.txt'))
    inter_conf.opt('accreg_txt', RString, lit('save/accreg.txt'))
    inter_conf.opt('party_share_level', u32, '10')

    map_conf.opt('userid', AccountName, '{}')
    map_conf.opt('passwd', AccountPass, '{}')
    map_conf.opt('char_ip', IP4Address, '{}')
    map_conf.opt('char_port', u16, '6121', min='1024')
    map_conf.opt('map_ip', IP4Address, '{}')
    map_conf.opt('map_port', u16, '5121', min='1024')
    map_conf.opt('map', addmap, '{}')
    map_conf.opt('delmap', delmap, '{}')
    map_conf.opt('npc', addnpc, '{}')
    map_conf.opt('delnpc', delnpc, '{}')
    map_conf.opt('autosave_time', seconds, 'DEFAULT_AUTOSAVE_INTERVAL', {map_h})
    map_conf.opt('mapreg_txt', RString, lit('save/mapreg.txt'))
    map_conf.opt('gm_log', RString, '{}')
    map_conf.opt('log_file', RString, '{}')

    battle_conf.opt('enemy_critical', bool, 'false')
    battle_conf.opt('enemy_critical_rate', percent, '100')
    battle_conf.opt('enemy_str', bool, 'true')
    battle_conf.opt('enemy_perfect_flee', bool, 'false')
    battle_conf.opt('casting_rate', percent, '100')
    battle_conf.opt('delay_rate', percent, '100')
    battle_conf.opt('delay_dependon_dex', bool, 'false')
    battle_conf.opt('skill_delay_attack_enable', bool, 'false')
    battle_conf.opt('monster_skill_add_range', i32, '0')
    battle_conf.opt('player_damage_delay', bool, '1')
    battle_conf.opt('flooritem_lifetime', milliseconds, 'LIFETIME_FLOORITEM', min='1_s')
    battle_conf.opt('item_auto_get', bool, 'false')
    battle_conf.opt('item_first_get_time', milliseconds, '3_s')
    battle_conf.opt('item_second_get_time', milliseconds, '1_s')
    battle_conf.opt('item_third_get_time', milliseconds, '1_s')
    battle_conf.opt('base_exp_rate', percent, '100')
    battle_conf.opt('job_exp_rate', percent, '100')
    battle_conf.opt('death_penalty_type', i32, '0', min='0', max='2')
    battle_conf.opt('death_penalty_base', per10kd, '0')
    battle_conf.opt('death_penalty_job', per10kd, '0')
    battle_conf.opt('restart_hp_rate', percent, '0', min='0', max='100')
    battle_conf.opt('restart_sp_rate', percent, '0', min='0', max='100')
    battle_conf.opt('monster_hp_rate', percent, '0')
    battle_conf.opt('monster_max_aspd', milliseconds, '199_ms', pre='conf.monster_max_aspd = 2000_ms - conf.monster_max_aspd * 10;', min='10_ms', max='1000_ms')
    battle_conf.opt('atcommand_gm_only', bool, '0')
    battle_conf.opt('atcommand_spawn_quantity_limit', i32, '{}')
    battle_conf.opt('gm_all_equipment', GmLevel, 'GmLevel::from(0_u32)', {udl_h})
    battle_conf.opt('monster_active_enable', bool, 'true')
    battle_conf.opt('mob_skill_use', bool, 'true')
    battle_conf.opt('mob_count_rate', percent, '100')
    battle_conf.opt('basic_skill_check', bool, 'true')
    battle_conf.opt('player_invincible_time', milliseconds, '5_s')
    battle_conf.opt('player_pvp_time', milliseconds, '5_s')
    battle_conf.opt('skill_min_damage', bool, 'false')
    battle_conf.opt('natural_healhp_interval', milliseconds, '6_s', {map_h}, min='NATURAL_HEAL_INTERVAL')
    battle_conf.opt('natural_healsp_interval', milliseconds, '8_s', {map_h}, min='NATURAL_HEAL_INTERVAL')
    battle_conf.opt('natural_heal_weight_rate', percent, '50', min='50', max='101')
    battle_conf.opt('arrow_decrement', bool, 'true')
    battle_conf.opt('max_aspd', milliseconds, '199_ms', pre='conf.max_aspd = 2000_ms - conf.max_aspd * 10;', min='10_ms', max='1000_ms')
    battle_conf.opt('max_hp', i32, '32500', min='100', max='1000000')
    battle_conf.opt('max_sp', i32, '32500', min='100', max='1000000')
    battle_conf.opt('max_lv', i32, '99')
    battle_conf.opt('max_parameter', i32, '99', min='10', max='10000')
    battle_conf.opt('monster_skill_log', bool, 'false')
    battle_conf.opt('battle_log', bool, 'false')
    battle_conf.opt('save_log', bool, 'false')
    battle_conf.opt('error_log', bool, 'true')
    battle_conf.opt('etc_log', bool, 'true')
    battle_conf.opt('save_clothcolor', bool, 'false')
    battle_conf.opt('undead_detect_type', i32, '0', min='0', max='2')
    battle_conf.opt('agi_penaly_type', i32, '0', min='0', max='2')
    battle_conf.opt('agi_penaly_count', i32, '3', min='2')
    battle_conf.opt('agi_penaly_num', i32, '0')
    battle_conf.opt('vit_penaly_type', i32, '0', min='0', max='2')
    battle_conf.opt('vit_penaly_count', i32, '3', min='2')
    battle_conf.opt('vit_penaly_num', i32, '0')
    battle_conf.opt('mob_changetarget_byskill', bool, 'false')
    battle_conf.opt('player_attack_direction_change', bool, 'true')
    battle_conf.opt('monster_attack_direction_change', bool, 'true')
    battle_conf.opt('display_delay_skill_fail', bool, 'true')
    battle_conf.opt('prevent_logout', bool, 'true')
    battle_conf.opt('alchemist_summon_reward', bool, '{}')
    battle_conf.opt('maximum_level', i32, '255')
    battle_conf.opt('drops_by_luk', percent, '0')
    battle_conf.opt('monsters_ignore_gm', bool, '{}')
    battle_conf.opt('multi_level_up', bool, 'false')
    battle_conf.opt('pk_mode', bool, 'false')
    battle_conf.opt('agi_penaly_count_lv', ATK, 'ATK::FLEE')
    battle_conf.opt('vit_penaly_count_lv', ATK, 'ATK::DEF')
    battle_conf.opt('hide_GM_session', bool, 'false')
    battle_conf.opt('invite_request_check', bool, 'true')
    battle_conf.opt('disp_experience', bool, '0')
    battle_conf.opt('hack_info_GM_level', GmLevel, 'GmLevel::from(60_u32)', {udl_h})
    battle_conf.opt('any_warp_GM_min_level', GmLevel, 'GmLevel::from(20_u32)', {udl_h})
    battle_conf.opt('min_hair_style', i32, '0')
    battle_conf.opt('max_hair_style', i32, '20')
    battle_conf.opt('min_hair_color', i32, '0')
    battle_conf.opt('max_hair_color', i32, '9')
    battle_conf.opt('min_cloth_color', i32, '0')
    battle_conf.opt('max_cloth_color', i32, '4')
    battle_conf.opt('castrate_dex_scale', i32, '150')
    battle_conf.opt('area_size', i32, '14')
    battle_conf.opt('chat_lame_penalty', i32, '2')
    battle_conf.opt('chat_spam_threshold', seconds, '10_s', min='0_s', max='32767_s')
    battle_conf.opt('chat_spam_flood', i32, '10', min='0', max='32767')
    battle_conf.opt('chat_spam_ban', hours, '1_h', min='0_h', max='32767_h')
    battle_conf.opt('chat_spam_warn', i32, '8', min='0', max='32767')
    battle_conf.opt('chat_maxline', i32, '255', min='1', max='512')
    battle_conf.opt('packet_spam_threshold', seconds, '2_s', min='0_s', max='32767_s')
    battle_conf.opt('packet_spam_flood', i32, '30', min='0', max='32767')
    battle_conf.opt('packet_spam_kick', bool, 'true')
    battle_conf.opt('mask_ip_gms', bool, 'true')
    battle_conf.opt('drop_pickup_safety_zone', i32, '20')
    battle_conf.opt('itemheal_regeneration_factor', i32, '1')
    battle_conf.opt('mob_splash_radius', i32, '-1', min='-1')

    battle_conf.opt('gm_log_delay', milliseconds, '60_min')

    return rv

def main():
    cfg = build_config()
    cfg.dump()

if __name__ == '__main__':
    main()