###############################################################################
#                                                                             #
# This file is part of IfcOpenShell.                                          #
#                                                                             #
# IfcOpenShell is free software: you can redistribute it and/or modify        #
# it under the terms of the Lesser GNU General Public License as published by #
# the Free Software Foundation, either version 3.0 of the License, or         #
# (at your option) any later version.                                         #
#                                                                             #
# IfcOpenShell 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                #
# Lesser GNU General Public License for more details.                         #
#                                                                             #
# You should have received a copy of the Lesser GNU General Public License    #
# along with this program. If not, see <http://www.gnu.org/licenses/>.        #
#                                                                             #
###############################################################################

header = """
#ifndef %(schema_name_upper)s_H
#define %(schema_name_upper)s_H

#include <string>
#include <vector>

#include <boost/optional.hpp>

#include "../ifcparse/IfcUtil.h"
#include "../ifcparse/IfcSchema.h"
#include "../ifcparse/IfcException.h"
#include "../ifcparse/%(schema_name)senum.h"

const IfcParse::schema_definition& get_schema();

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4100)
#endif

#define IfcSchema %(schema_name)s

namespace %(schema_name)s {

const char* const Identifier = "%(schema_name_upper)s";

// Forward definitions
%(forward_definitions)s

%(declarations)s

%(class_definitions)s
void InitStringMap();
IfcUtil::IfcBaseClass* SchemaEntity(IfcAbstractEntity* e = 0);
}

#ifdef _MSC_VER
#pragma warning(pop)
#endif

#endif
"""

enum_header = """
#ifndef %(schema_name_upper)sENUM_H
#define %(schema_name_upper)sENUM_H

#define IfcSchema %(schema_name)s

namespace %(schema_name)s {

namespace Type {
    typedef enum {
        %(types)s, UNDEFINED
    } Enum;
    Enum Parent(Enum v);
    Enum FromString(const std::string& s);
    std::string ToString(Enum v);
    bool IsSimple(Enum v);
}

}

#endif
"""

lb_header = """
#ifndef %(schema_name_upper)sRT_H
#define %(schema_name_upper)sRT_H

#define IfcSchema %(schema_name)s

#include "../ifcparse/IfcUtil.h"
#include "../ifcparse/IfcEntityDescriptor.h"
#include "../ifcparse/IfcWritableEntity.h"

namespace %(schema_name)s {
namespace Type {
    int GetAttributeCount(Enum t);
    int GetAttributeIndex(Enum t, const std::string& a);
    IfcUtil::ArgumentType GetAttributeType(Enum t, unsigned char a);
    Enum GetAttributeEntity(Enum t, unsigned char a);
    const std::string& GetAttributeName(Enum t, unsigned char a);
    bool GetAttributeOptional(Enum t, unsigned char a);
    bool GetAttributeDerived(Enum t, unsigned char a);
    std::pair<const char*, int> GetEnumerationIndex(Enum t, const std::string& a);
    std::pair<Enum, unsigned> GetInverseAttribute(Enum t, const std::string& a);
    std::set<std::string> GetInverseAttributeNames(Enum t);
    void PopulateDerivedFields(IfcWrite::IfcWritableEntity* e);
}}

#endif
"""

implementation= """
#include "../ifcparse/%(schema_name)s.h"
#include "../ifcparse/IfcSchema.h"
#include "../ifcparse/IfcException.h"
#include "../ifcparse/IfcWrite.h"
#include "../ifcparse/IfcWritableEntity.h"

#include <map>

using namespace %(schema_name)s;
using namespace IfcParse;
using namespace IfcWrite;

// External definitions
%(external_definitions)s

IfcUtil::IfcBaseClass* %(schema_name)s::SchemaEntity(IfcAbstractEntity* e) {
    switch(e->type()) {
%(schema_entity_statements)s
        default: throw IfcException("Unable to find find keyword in schema"); break;
    }
}

std::string Type::ToString(Enum v) {
    if (v < 0 || v >= %(max_id)d) throw IfcException("Unable to find find keyword in schema");
    const char* names[] = { %(type_name_strings)s };
    return names[v];
}

static std::map<std::string,Type::Enum> string_map;
void %(schema_name)s::InitStringMap() {
%(string_map_statements)s
}

Type::Enum Type::FromString(const std::string& s) {
    if (string_map.empty()) InitStringMap();
    std::map<std::string,Type::Enum>::const_iterator it = string_map.find(s);
    if ( it == string_map.end() ) throw IfcException("Unable to find find keyword in schema");
    else return it->second;
}

Type::Enum Type::Parent(Enum v){
    if (v < 0 || v >= %(max_id)d) return (Enum)-1;
%(parent_type_statements)s
    return (Enum)-1;
}

bool Type::IsSimple(Enum v) {
    return %(simple_type_statement)s;
}

%(enumeration_functions)s

%(simple_type_impl)s

%(entity_implementations)s
"""

lb_implementation = """
#include <set>

#include "../ifcparse/%(schema_name)s.h"
#include "../ifcparse/%(schema_name)s-latebound.h"
#include "../ifcparse/IfcException.h"
#include "../ifcparse/IfcWrite.h"
#include "../ifcparse/IfcWritableEntity.h"
#include "../ifcparse/IfcUtil.h"
#include "../ifcparse/IfcEntityDescriptor.h"

using namespace %(schema_name)s;
using namespace IfcParse;
using namespace IfcWrite;
using namespace IfcUtil;

typedef std::map<Type::Enum,IfcEntityDescriptor*> entity_descriptor_map_t;
typedef std::map<Type::Enum,IfcEnumerationDescriptor*> enumeration_descriptor_map_t;
typedef std::map<Type::Enum, std::map<std::string, std::pair<Type::Enum, int> > > inverse_map_t;
typedef std::map<Type::Enum,std::set<int> > derived_map_t;

entity_descriptor_map_t entity_descriptor_map;
enumeration_descriptor_map_t enumeration_descriptor_map;
inverse_map_t inverse_map;
derived_map_t derived_map;

#if defined(__clang__)
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC push_options
#pragma GCC optimize ("O0")
#elif defined(_MSC_VER)
#pragma optimize("", off)
#endif

void InitDescriptorMap() {
    IfcEntityDescriptor* current;
%(entity_descriptors)s
    // Enumerations
    IfcEnumerationDescriptor* current_enum;
    std::vector<std::string> values;
%(enumeration_descriptors)s
}

void InitInverseMap() {
%(inverse_implementations)s
}

void InitDerivedMap() {
%(derived_field_statements)s
}

#if defined(__clang__)
#elif defined(__GNUC__) || defined(__GNUG__)
#pragma GCC pop_options
#elif defined(_MSC_VER)
#pragma optimize("", on)
#endif

int Type::GetAttributeIndex(Enum t, const std::string& a) {
    if (entity_descriptor_map.empty()) ::InitDescriptorMap();
    std::map<Type::Enum,IfcEntityDescriptor*>::const_iterator i = entity_descriptor_map.find(t);
    if ( i == entity_descriptor_map.end() ) throw IfcException("Type not found");
    else return i->second->getArgumentIndex(a);
}

int Type::GetAttributeCount(Enum t) {
    if (entity_descriptor_map.empty()) ::InitDescriptorMap();
    std::map<Type::Enum,IfcEntityDescriptor*>::const_iterator i = entity_descriptor_map.find(t);
    if ( i == entity_descriptor_map.end() ) throw IfcException("Type not found");
    else return i->second->getArgumentCount();
}

ArgumentType Type::GetAttributeType(Enum t, unsigned char a) {
    if (entity_descriptor_map.empty()) ::InitDescriptorMap();
    std::map<Type::Enum,IfcEntityDescriptor*>::const_iterator i = entity_descriptor_map.find(t);
    if ( i == entity_descriptor_map.end() ) throw IfcException("Type not found");
    else return i->second->getArgumentType(a);
}

Type::Enum Type::GetAttributeEntity(Enum t, unsigned char a) {
    if (entity_descriptor_map.empty()) ::InitDescriptorMap();
    std::map<Type::Enum,IfcEntityDescriptor*>::const_iterator i = entity_descriptor_map.find(t);
    if ( i == entity_descriptor_map.end() ) throw IfcException("Type not found");
    else return i->second->getArgumentEntity(a);
}

const std::string& Type::GetAttributeName(Enum t, unsigned char a) {
    if (entity_descriptor_map.empty()) ::InitDescriptorMap();
    std::map<Type::Enum,IfcEntityDescriptor*>::const_iterator i = entity_descriptor_map.find(t);
    if ( i == entity_descriptor_map.end() ) throw IfcException("Type not found");
    else return i->second->getArgumentName(a);
}

bool Type::GetAttributeOptional(Enum t, unsigned char a) {
    if (entity_descriptor_map.empty()) ::InitDescriptorMap();
    std::map<Type::Enum,IfcEntityDescriptor*>::const_iterator i = entity_descriptor_map.find(t);
    if ( i == entity_descriptor_map.end() ) throw IfcException("Type not found");
    else return i->second->getArgumentOptional(a);
}

bool Type::GetAttributeDerived(Enum t, unsigned char a) {
    if (derived_map.empty()) ::InitDerivedMap();
    std::map<Type::Enum,std::set<int> >::const_iterator i = derived_map.find(t);
    return i != derived_map.end() && i->second.find(a) != i->second.end();
}

std::pair<const char*, int> Type::GetEnumerationIndex(Enum t, const std::string& a) {
    if (enumeration_descriptor_map.empty()) ::InitDescriptorMap();
    std::map<Type::Enum,IfcEnumerationDescriptor*>::const_iterator i = enumeration_descriptor_map.find(t);
    if ( i == enumeration_descriptor_map.end() ) throw IfcException("Value not found");
    else return i->second->getIndex(a);
}

std::pair<Type::Enum, unsigned> Type::GetInverseAttribute(Enum t, const std::string& a) {
    if (inverse_map.empty()) ::InitInverseMap();
    inverse_map_t::const_iterator it;
    inverse_map_t::mapped_type::const_iterator jt;
    for(;;) {
        it = inverse_map.find(t);
        if (it != inverse_map.end()) {
            jt = it->second.find(a);
            if (jt != it->second.end()) {
                return jt->second;
            }
        }
        if ((t = Parent(t)) == -1) break;
    }
    throw IfcException("Attribute not found");
}

std::set<std::string> Type::GetInverseAttributeNames(Enum t) {
    if (inverse_map.empty()) ::InitInverseMap();
    inverse_map_t::const_iterator it;
    inverse_map_t::mapped_type::const_iterator jt;

    std::set<std::string> return_value;

    for (;;) {
        it = inverse_map.find(t);
        if (it != inverse_map.end()) {
            for (jt = it->second.begin(); jt != it->second.end(); ++jt) {
                return_value.insert(jt->first);
            }
        }
        if ((t = Parent(t)) == -1) break;
    }

    return return_value;
}

void Type::PopulateDerivedFields(IfcWrite::IfcWritableEntity* e) {
    std::map<Type::Enum, std::set<int> >::const_iterator i = derived_map.find(e->type());
    if (i != derived_map.end()) {
        for (std::set<int>::const_iterator it = i->second.begin(); it != i->second.end(); ++it) {
            e->setArgumentDerived(*it);
        }
    }
}
"""

entity_descriptor = """    current = entity_descriptor_map[Type::%(type)s] = new IfcEntityDescriptor(Type::%(type)s,%(parent_statement)s);
%(entity_descriptor_attributes)s"""

entity_descriptor_parent = "entity_descriptor_map.find(Type::%(type)s)->second"
entity_descriptor_attribute_without_entity = '    current->add("%(name)s",%(optional)s,%(type)s);'
entity_descriptor_attribute_with_entity = '    current->add("%(name)s",%(optional)s,%(type)s,Type::%(entity_name)s);'

enumeration_descriptor = """    values.clear(); values.reserve(128);
%(enumeration_descriptor_values)s
    current_enum = enumeration_descriptor_map[Type::%(type)s] = new IfcEnumerationDescriptor(Type::%(type)s, values);"""

enumeration_descriptor_value = '    values.push_back("%(name)s");'

derived_field_statement = '    {std::set<int> idxs; %(statements)sderived_map[Type::%(type)s] = idxs;}';
derived_field_statement_attrs = 'idxs.insert(%d); '

simpletype = """%(documentation)s
class %(name)s : public %(superclass)s {
public:
    virtual const IfcParse::type_declaration& declaration() const;
    static Type::Enum Class();
    explicit %(name)s (IfcAbstractEntity* e);
    %(name)s (%(type)s v);
    operator %(type)s() const;
};
"""

simpletype_impl_comment = "// Function implementations for %(name)s"
simpletype_impl_argument_type = "if (i == 0) { return %(attr_type)s; } else { throw IfcParse::IfcAttributeOutOfRangeException(\"Argument index out of range\"); }"
simpletype_impl_argument = "return data_->getArgument(i);"
simpletype_impl_is_with_supertype = "return v == Type::%(class_name)s || %(superclass)s::is(v);"
simpletype_impl_is_without_supertype = "return v == %(class_name)s::Class();"
simpletype_impl_type = "return Type::%(class_name)s;"
simpletype_impl_class = "return Type::%(class_name)s;"
simpletype_impl_explicit_constructor = "data_ = e;"
simpletype_impl_constructor = "IfcWritableEntity* e = new IfcWritableEntity(Type::%(class_name)s); e->setArgument(0, v); data_ = e;"
simpletype_impl_constructor_templated = "IfcWritableEntity* e = new IfcWritableEntity(Type::%(class_name)s); e->setArgument(0, v->generalize()); data_ = e;"
simpletype_impl_cast = "return *data_->getArgument(0);"
simpletype_impl_cast_templated = "IfcEntityList::ptr es = *data_->getArgument(0); return es->as<%(underlying_type)s>();"
simpletype_impl_declaration = "return *%(class_name)s_type;"
    
select = """%(documentation)s
typedef IfcUtil::IfcBaseClass %(name)s;
"""

enumeration = """namespace %(name)s {
%(documentation)s
typedef enum {%(values)s} %(name)s;
const char* ToString(%(name)s v);
%(name)s FromString(const std::string& s);
}
"""

entity = """%(documentation)s
class %(name)s %(superclass)s{
public:
%(attributes)s    %(inverse)s    virtual const IfcParse::entity& declaration() const;
    static Type::Enum Class();
    %(name)s (IfcAbstractEntity* e);
    %(name)s (%(constructor_arguments)s);
    typedef IfcTemplatedEntityList< %(name)s > list;
};
"""

enumeration_function="""
const char* %(name)s::ToString(%(name)s v) {
    if ( v < 0 || v >= %(max_id)d ) throw IfcException("Unable to find find keyword in schema");
    const char* names[] = { %(values)s };
    return names[v];
}

%(name)s::%(name)s %(name)s::FromString(const std::string& s) {
%(from_string_statements)s
    throw IfcException("Unable to find find keyword in schema");
}
"""

entity_implementation = """// Function implementations for %(name)s
%(attributes)s
%(inverse)s
const IfcParse::entity& %(name)s::declaration() const { return *%(name)s_type; }
Type::Enum %(name)s::Class() { return Type::%(name)s; }
%(name)s::%(name)s(IfcAbstractEntity* e) : %(superclass)s { if (!e) return; if (!e->is(Type::%(name)s)) throw IfcException("Unable to find find keyword in schema"); data_ = e; }
%(name)s::%(name)s(%(constructor_arguments)s) : %(superclass)s { IfcWritableEntity* e = new IfcWritableEntity(Class());%(constructor_implementation)s data_ = e; EntityBuffer::Add(this); }
"""

optional_attribute_description = "/// Whether the optional attribute %s is defined for this %s"

function = "%(return_type)s %(class_name)s::%(name)s(%(arguments)s) { %(body)s }"
const_function = "%(return_type)s %(class_name)s::%(name)s(%(arguments)s) const { %(body)s }"
constructor = "%(class_name)s::%(class_name)s(%(arguments)s) { %(body)s }"
constructor_single_initlist = "%(class_name)s::%(class_name)s(%(arguments)s) : %(superclass)s(%(superclass_init)s) { %(body)s }"
cast_function = "%(class_name)s::operator %(return_type)s() const { %(body)s }"

array_type = "std::vector< %(instance_type)s > /*[%(lower)s:%(upper)s]*/"
nested_array_type = "std::vector< std::vector< %(instance_type)s > >"
list_type = "IfcTemplatedEntityList< %(instance_type)s >::ptr"
list_list_type = "IfcTemplatedEntityListList< %(instance_type)s >::ptr"
untyped_list = "IfcEntityList::ptr"
inverse_attr = "IfcTemplatedEntityList< %(entity)s >::ptr %(name)s() const; // INVERSE %(entity)s::%(attribute)s"

enum_from_string_stmt = '    if (s == "%(value)s") return ::%(schema_name)s::%(name)s::%(short_name)s_%(value)s;'

schema_entity_stmt = '        case Type::%(name)s: return new %(name)s(e); break;'
string_map_statement = '    string_map["%(uppercase_name)s"%(padding)s] = Type::%(name)s;'
parent_type_stmt = '    if(v==%(name)s%(padding)s) { return %(parent)s; }'

parent_type_test = " || %s::is(v)"

optional_attr_stmt = "return !data_->getArgument(%(index)d)->isNull();"

get_attr_stmt = "return *data_->getArgument(%(index)d);"
get_attr_stmt_enum = "return %(type)s::FromString(*data_->getArgument(%(index)d));"
get_attr_stmt_entity = "return (%(type)s)((IfcUtil::IfcBaseClass*)(*data_->getArgument(%(index)d)));"
get_attr_stmt_array = "IfcEntityList::ptr es = *data_->getArgument(%(index)d); return es->as<%(list_instance_type)s>();"
get_attr_stmt_nested_array = "IfcEntityListList::ptr es = *data_->getArgument(%(index)d); return es->as<%(list_instance_type)s>();"

get_inverse = "return data_->getInverse(Type::%(type)s, %(index)d)->as<%(type)s>();"

set_attr_stmt = "if ( ! data_->isWritable() ) { data_ = new IfcWritableEntity(data_); } ((IfcWritableEntity*)data_)->setArgument(%(index)d,v);"
set_attr_stmt_enum = "if ( ! data_->isWritable() ) { data_ = new IfcWritableEntity(data_); } ((IfcWritableEntity*)data_)->setArgument(%(index)d,v,%(type)s::ToString(v));"
set_attr_stmt_array = "if ( ! data_->isWritable() ) { data_ = new IfcWritableEntity(data_); } ((IfcWritableEntity*)data_)->setArgument(%(index)d,v->generalize());"

constructor_stmt = " e->setArgument(%(index)d,(%(name)s));"
constructor_stmt_enum = " e->setArgument(%(index)d,%(name)s,%(type)s::ToString(%(name)s));"
constructor_stmt_array = " e->setArgument(%(index)d,(%(name)s)->generalize());"
constructor_stmt_optional = " if (%(name)s) {%(stmt)s } else { e->setArgument(%(index)d); }"
constructor_stmt_derived = " e->setArgumentDerived(%(index)d);"

inverse_implementation = "    inverse_map[Type::%(type)s].insert(std::make_pair(\"%(name)s\", std::make_pair(Type::%(related_type)s, %(index)d)));"

def multi_line_comment(li):
    return ("/// %s"%("\n/// ".join(li))) if len(li) else ""

