#include <iostream>
#include <sstream>


#include "ClassValues.hh"
#include "FieldDescription.hh"

#ifdef __APPLE__
#include "llvm/Support/CommandLine.h"
extern llvm::cl::opt< bool > global_compat15 ;
#endif

ClassValues::ClassValues() :
  has_init_attr_friend(false) ,
  is_pod(false) ,
  is_abstract(false) ,
  has_default_constructor(false) ,
  has_public_destructor(false),
  compat15(false)
{}

ClassValues::~ClassValues() {
    std::vector<FieldDescription *>::iterator fdit ;
    for ( fdit = field_descripts.begin() ; fdit != field_descripts.end() ; fdit++ ) {
        delete (*fdit) ;
    }
}

void ClassValues::addFieldDescription(FieldDescription * in_fdes) {
    field_descripts.push_back(in_fdes) ;

#ifdef __APPLE__
    if ( !global_compat15 and !compat15 ) {
#else
    {
#endif
        // Test to see if the new field overloads a field of the same name.  If it does
        // then fully qualify the name of the inherited field (the one already in field_name_to_info).
        std::map< std::string , FieldDescription * >::iterator mit = field_name_to_info_map.find(in_fdes->getName()) ;
        if ( mit != field_name_to_info_map.end() ) {
            // If the matched variable is inherited, qualify it with its container class name.
            if ( (*mit).second->isInherited() ) {
                (*mit).second->setName( (*mit).second->getContainerClass() + "::" + (*mit).second->getName() ) ;
                field_name_to_info_map[(*mit).second->getName()] = (*mit).second ;
                field_name_to_info_map.erase(mit) ;
            }
        }
    }
    field_name_to_info_map[in_fdes->getName()] = in_fdes ;
}

void ClassValues::addInheritedFieldDescriptions(std::vector<FieldDescription *> in_fdes,
  unsigned int class_offset, bool virtual_inherited ) {
    // Make a copy of all of the FieldDescription variables.
    field_descripts.insert(field_descripts.end(), in_fdes.begin() , in_fdes.end()) ;

    std::vector<FieldDescription *>::iterator fdit ;
    // Loop through the incoming inherited variable names
    for ( fdit = in_fdes.begin() ; fdit != in_fdes.end() ; fdit++ ) {

        (*fdit)->setBaseClassOffset( class_offset ) ;
        (*fdit)->setInherited( true ) ;
        (*fdit)->setVirtualInherited( virtual_inherited ) ;
        // Adds the class offset to the field offset giving the total offset to the inherited variable
        // The offset is stored in bits so multiply class_offset by 8.
        (*fdit)->addOffset( class_offset * 8 ) ;
    }

#ifdef __APPLE__
    if ( !global_compat15 and !compat15 ) {
#else
    {
#endif
        for ( fdit = in_fdes.begin() ; fdit != in_fdes.end() ; fdit++ ) {

            std::string in_name = (*fdit)->getName() ;
            // search existing names for incoming inherited variable name.
            std::map< std::string , FieldDescription * >::iterator mit = field_name_to_info_map.find(in_name) ;
            // if the variable name already exists we have overloaded variable names.
            if ( mit != field_name_to_info_map.end() ) {
                // the incoming variable is known to be inherited.  Qualify it with its container class name.
                (*fdit)->setName( (*fdit)->getContainerClass() + "::" + (*fdit)->getName() ) ;
                field_name_to_info_map[(*fdit)->getName()] = *fdit ;

                // If the matched variable is also inherited, qualify it with its container class name.
                if ( (*mit).second->isInherited() ) {
                    (*mit).second->setName( (*mit).second->getContainerClass() + "::" + (*mit).second->getName() ) ;
                    field_name_to_info_map[(*mit).second->getName()] = (*mit).second ;
                    field_name_to_info_map.erase(mit) ;
                    // Mark this name is one that we always have to qualify.
                    field_names_to_qualify.insert(in_name) ;
                }
            } else {
                // incoming name did not match an existing variable name.
                // Test to see if there variables of this name need to be qualified.
                if ( field_names_to_qualify.find(in_name) == field_names_to_qualify.end() ) {
                    // The name is not overloaded (yet), add the unqualified inherited name straight into map.
                    field_name_to_info_map[in_name] = *fdit ;
                    // Upgrade the inherited variable's container class to the current class.
                    //(*fdit)->setContainerClass( name ) ;
                } else {
                    // The name is overloaded by other inherited variables... qualify the name.
                    (*fdit)->setName( (*fdit)->getContainerClass() + "::" + (*fdit)->getName() ) ;
                    field_name_to_info_map[(*fdit)->getName()] = *fdit ;
                }
            }
        }
    }

}

void ClassValues::saveInheritAncestry( ClassValues * in_cv  ) {

    std::map< std::string , unsigned int >::iterator mit ;
    for ( mit = in_cv->all_inherited_class_names_map.begin() ; mit != in_cv->all_inherited_class_names_map.end() ; mit++ ) {
        all_inherited_class_names_map[(*mit).first] +=  (*mit).second ;
    }

    all_inherited_class_names_map[in_cv->getName()]++ ;
}

void ClassValues::setContainerClassForFields() {
    std::vector<FieldDescription *>::iterator fdit ;
    // Loop through all fields
    for ( fdit = field_descripts.begin() ; fdit != field_descripts.end() ; fdit++ ) {
        // Prepend the field container class with the current class name.
        (*fdit)->setContainerClass( name + "::" + (*fdit)->getContainerClass() ) ;
    }
}

void ClassValues::clearAmbiguousVariables() {

    std::map< std::string , unsigned int >::iterator mit ;
    std::vector<FieldDescription *>::iterator fdit ;

    for ( mit = all_inherited_class_names_map.begin() ; mit != all_inherited_class_names_map.end() ; mit++ ) {
        // If a class in inherited more than once we have a diamond.  We'll need to modify variables that were
        // inherited from that class.
        if ( (*mit).second > 1 ) {
            std::string str = (*mit).first + "::" ;
            // Loop through all fields testing for the diamond inherited class in the name.
            for ( fdit = field_descripts.begin() ; fdit != field_descripts.end() ; fdit++ ) {
                std::string fdit_name = (*fdit)->getName() ;
                size_t f = fdit_name.find(str) ;
                // If the variable contains the diamond class string, remove the diamond class qualification.
                if ( f != std::string::npos ) {
                    fdit_name.replace(f , str.length() , "") ;
                    (*fdit)->setName(fdit_name) ;
                }
            }
        }
    }

    // Removing diamond class qualifications possibly leaves fields with duplicate names.
    // We want to remove the duplicated names.
    std::set< std::string > field_names ;
    fdit = field_descripts.begin() ;
    while ( fdit != field_descripts.end() ) {
        // test if the current field is in the list of field names processed.
        if ( field_names.find( (*fdit)->getName() ) == field_names.end() ) {
            // This name is new.
            // Add the field to the test set
            field_names.insert( (*fdit)->getName() ) ;
            fdit++ ;
        } else {
            // This name was already present
            // Free the field's memory.
            delete (*fdit) ;
            // Erase the field from the list. Move iterator to next field.
            fdit = field_descripts.erase(fdit) ;

        }
    }

}

void ClassValues::clearFieldDescription() {
    field_descripts.clear() ;
}

void ClassValues::addInheritedClass(std::string  class_name) {
    inherited_classes.push_back(class_name) ;
}

void ClassValues::clearInheritedClass() {
    inherited_classes.clear() ;
}

void ClassValues::setHasInitAttrFriend(bool in_val) {
    has_init_attr_friend = in_val ;
}

bool ClassValues::getHasInitAttrFriend() {
    return has_init_attr_friend ;
}

void ClassValues::setPOD(bool in_val) {
    is_pod = in_val ;
}

bool ClassValues::isPOD() {
    return is_pod ;
}

void ClassValues::setSize(unsigned int in_size) {
    size = in_size ;
}

unsigned int ClassValues::getSize() {
    return size ;
}

void ClassValues::setAbstract(bool in_val) {
    is_abstract = in_val ;
}

bool ClassValues::isAbstract() {
    return is_abstract ;
}

void ClassValues::setHasDefaultConstructor(bool in_val) {
    has_default_constructor = in_val ;
}

bool ClassValues::getHasDefaultConstructor() {
    return has_default_constructor ;
}

void ClassValues::setHasPublicDestructor(bool in_val) {
    has_public_destructor = in_val ;
}

bool ClassValues::getHasPublicDestructor() {
    return has_public_destructor ;
}

void ClassValues::setMangledTypeName( std::string in_val ) {
    mangled_type_name = in_val ;
}

std::string ClassValues::getMangledTypeName() {
    if ( mangled_type_name.empty() ) {
        return name ;
    }
    return mangled_type_name ;
}

std::string ClassValues::getFullyQualifiedMangledTypeName(const std::string& delimiter) {
    std::ostringstream oss ;
    printNamespacesAndContainerClasses(oss, delimiter) ;
    oss << getMangledTypeName() ;
    return oss.str() ;
}

std::string ClassValues::getFullyQualifiedNameIfEqual() {
    std::string result;
    if (getName() == getMangledTypeName()) {
        result += getNamespacesAndContainerClasses();
    }
    result += getName();
    return result;
}


void ClassValues::setCompat15(bool in_val) {
    compat15 = in_val ;
}

bool ClassValues::isCompat15() {
    return compat15 ;
}

bool ClassValues::isInStandardNamespace() {
    return namespaces.size() && !namespaces[0].compare("std");
}

std::ostream & operator << (std::ostream & ostream, ClassValues & cv) {
    ostream << "  name = " << cv.name << std::endl ;
    ostream << "  mangled_name = " << cv.mangled_type_name << std::endl ;
    ostream << "  file_name = " << cv.file_name << std::endl ;
    ostream << "  namespaces =" ;
    cv.printNamespaces(ostream) ;
    ostream << std::endl ;
    ostream << "  parent classes =" ;
    cv.printContainerClasses(ostream) ;
    ostream << std::endl ;
    ostream << "  has_init_attr_friend = " << cv.has_init_attr_friend << std::endl ;
    ostream << "  is_pod = " << cv.is_pod << std::endl ;
    ostream << "  is_abstract = " << cv.is_abstract << std::endl ;
    ostream << "  has_default_constructor = " << cv.has_default_constructor << std::endl ;
    ostream << "  has_public_destructor = " << cv.has_public_destructor << std::endl ;

    for (auto& field : cv.getFieldDescriptions()) {
        ostream << field << std::endl ;
    }

    return ostream ;
}