#
# Copyright (c) 2013 Juniper Networks, Inc. All rights reserved.
#

import os
import re
import sys

from ifmap_global import CamelCase, getGoLangType


class GoLangApiGenerator(object):
    def __init__(self, parser, type_map, identifiers, metadata):
        self._parser = parser
        self._type_map = type_map
        self._identifier_map = identifiers
        self._metadata_map = metadata
        self._top_level_map = {}
        self._type_count = {}

    def _GenerateTypeMap(self, dirname):
        file = self._parser.makeFile(os.path.join(dirname, 'types.go'))
        decl = """
package types

import (
        "reflect"

        "github.com/Juniper/contrail-go-api"
)

var (
        TypeMap = map[string]reflect.Type {
"""
        file.write(decl)
        for ident in self._identifier_map.values():
            decl = '\t\t"%s": reflect.TypeOf(%s{}),\n' % \
                   (ident.getName(), ident.getCppName())
            file.write(decl)

        decl = """
        }
)

func init() {
        contrail.RegisterTypeMap(TypeMap)
}
"""
        file.write(decl)

    def _GenerateObject(self, ident, filename):
        """ Generate the class corresponding to an IF-MAP Identifier
        defined in the schema.
        """
        file = self._parser.makeFile(filename)

        header = """//
// Automatically generated. DO NOT EDIT.
//

package types

import (
        "encoding/json"

        "github.com/Juniper/contrail-go-api"
)
"""
        file.write(header)
        self._GenerateConstFlags(ident, file)
        self._GenerateObjectStruct(ident, file)
        self._GenerateGenericMethods(ident, file)
        self._GeneratePropertyMethods(ident, file)
        self._GenerateChildrenMethods(ident, file)
        self._GenerateRefsMethods(ident, file)
        self._GenerateBackRefsMethods(ident, file)
        self._GenerateMarshalJSON(ident, file)
        self._GenerateUnmarshalJSON(ident, file)
        self._GenerateUpdate(ident, file)
        self._GenerateUpdateReferences(ident, file)
        self._GenerateClientAuxMethods(ident, file)

    # end _GenerateObject

    def _GenerateStructType(self, ctype, filename):
        file = self._parser.makeFile(filename)
        header = """//
// Automatically generated. DO NOT EDIT.
//

package types
"""
        file.write(header)

        self._GenerateCType(ctype, file)

    # end _GenerateStructType

    def _GenerateCType(self, ctype, file):
        for deptype in ctype.getDependentTypes():
            if deptype.getName() in self._top_level_map:
                continue
            self._GenerateCType(deptype, file)

        decl = """
type %(camel)s struct {
""" % {'camel': ctype.getName()}
        file.write(decl)
        for member in ctype.getDataMembers():
            camel = CamelCase(member.membername)
            ptrType = False
            if member.isComplex:
                mtype = member.xsd_object.getType()
                if not member.isSequence:
                    ptrType = True
            else:
                mtype = getGoLangType(member.xsd_object.getType())
            if member.isSequence:
                mtype = '[]' + mtype
            decl = '\t%s %s%s `json:"%s,omitempty"`\n' % \
                   (camel, '*' if ptrType else '', mtype, member.membername)
            file.write(decl)
        file.write('}\n')

        # Generate methods (add/delete/clear/set) for sequence fields
        for member in ctype.getDataMembers():
            if not member.isSequence:
                continue
            membertype = member.xsd_object.getType()
            if not member.isComplex:
                membertype = getGoLangType(membertype)
            decl = """
func (obj *%(typecamel)s) Add%(fieldcamel)s(value %(ptr)s%(fieldtype)s) {
        obj.%(member)s = append(obj.%(member)s, %(ptr)svalue)
}
""" \
            % {'typecamel': ctype.getName(),
               'fieldcamel': CamelCase(member.membername),
               'fieldtype': membertype,
               'ptr': '*' if member.isComplex else '',
               'member': CamelCase(member.membername),
               }
            file.write(decl)

    # end _GenerateCType

    def _ExamineInnerTypes(self, inner_type_map, top_level, ctype):
        """ Examine all the dependent types of a given top_level type
        (recursivly) in order to determine which types are referred to
        more than once.
        The ones that are get promoted to top-level.
        """
        for deptype in ctype.getDependentTypes():
            mtype = deptype.getName()
            if mtype in inner_type_map:
                xset = inner_type_map[mtype]
                if top_level not in xset:
                    xset.append(top_level)
            else:
                inner_type_map[mtype] = [top_level]

            self._ExamineInnerTypes(inner_type_map, top_level, deptype)

    def _PromoteInnerTypes(self):
        inner_type_map = {}

        for ctype in self._top_level_map.values():
            self._ExamineInnerTypes(inner_type_map, ctype, ctype)

        while True:
            promoted = []
            for itype, typeset in inner_type_map.iteritems():
                if len(typeset) == 1:
                    continue

                # print "promote %s" % itype
                # for typ in typeset:
                #     print "    %s" % typ.getName()
                self._top_level_map[itype] = self._type_map[itype]
                promoted.append(itype)

            if len(promoted) == 0:
                break

            for itype in promoted:
                del inner_type_map[itype]
                ctype = self._type_map[itype]
                self._ExamineInnerTypes(inner_type_map, ctype, ctype)

    def _IdentifierLinks(self, ident):
        """ Returns the list of all the links (children, refs, back_refs)
        of a specific identifier.
        """
        fields = []
        for link_info in ident.getLinksInfo():
            if ident.isLinkRef(link_info):
                suffix = '_refs'
            elif ident.isLinkHas(link_info):
                suffix = 's'
            else:
                suffix = '_refs'
            link_to = ident.getLinkTo(link_info)
            fields.append(link_to.getCIdentifierName() + suffix)
        for back_link in ident.getBackLinksInfo():
            link_from = ident.getBackLinkFrom(back_link)
            fields.append(link_from.getCIdentifierName() + '_back_refs')

        return fields

    def _GenerateConstFlags(self, ident, file):
        """ Emit a const declaration with a flag per struct field used to
            record which fields have been modified.
        """
        file.write("\nconst (")

        first = True
        fields = [prop.getCIdentifierName() for prop in ident.getProperties()]
        fields.extend(self._IdentifierLinks(ident))

        for field in fields:
            file.write("\n\t%s_%s" % (ident.getCIdentifierName(), field))
            if first:
                file.write(" uint64 = 1 << iota")
                first = False
        file.write("\n)\n")
    # end _GenerateConstFlags

    def _GenerateObjectStruct(self, ident, file):
        """ Generate the golang struct type definition for an Identifier.
        """
        decl = """
type %(camel)s struct {
        contrail.ObjectBase
""" % {"camel": ident.getCppName()}
        file.write(decl)

        for prop in ident.getProperties():
            decl = '\t%s %s\n' % \
                   (prop.getCIdentifierName(), prop.getGoLangTypename())
            file.write(decl)
            ctype = prop.getCType()
            if ctype:
                ctypename = ctype.getName()
                self._top_level_map[ctypename] = self._type_map[ctypename]

        for link_info in ident.getLinksInfo():
            if ident.isLinkHas(link_info):
                child = ident.getLinkTo(link_info)
                decl = '\t%ss contrail.ReferenceList\n' % \
                       child.getCIdentifierName()
                file.write(decl)
            else:
                link_to = ident.getLinkTo(link_info)
                decl = '\t%s_refs contrail.ReferenceList\n' % \
                       link_to.getCIdentifierName()
                file.write(decl)
                datatype = self._getAttrType(ident, link_info)
                if datatype:
                    self._top_level_map[datatype] = self._type_map[datatype]

        for back_link in ident.getBackLinksInfo():
            link_from = ident.getBackLinkFrom(back_link)
            decl = '\t%s_back_refs contrail.ReferenceList\n' % \
                   link_from.getCIdentifierName()
            file.write(decl)

        decl = """        valid uint64
        modified uint64
        originalMap map[string]contrail.ReferenceList
}
"""
        file.write(decl)
    # end _GenerateObjectStruct

    def _GenerateGenericMethods(self, ident, file):
        """ Methods that do not iterate through the Identifier's fields.
        """
        parent_fqn = ""
        parent_type = ""
        parents = ident.getParents()
        if parents:
            (parent, meta) = parents[0]
            quoted_list = map(lambda x: '"%s"' % x, parent.getDefaultFQName())
            parent_fqn = ', '.join(quoted_list)
            parent_type = parent.getName()

        decl = """
func (obj *%(camel)s) GetType() string {
        return "%(typename)s"
}

func (obj *%(camel)s) GetDefaultParent() []string {
        name := []string{%(parent_fqn)s}
        return name
}

func (obj *%(camel)s) GetDefaultParentType() string {
        return "%(parent_type)s"
}

func (obj *%(camel)s) SetName(name string) {
        obj.VSetName(obj, name)
}

func (obj *%(camel)s) SetParent(parent contrail.IObject) {
        obj.VSetParent(obj, parent)
}

func (obj *%(camel)s) addChange(
        name string, refList contrail.ReferenceList) {
        if obj.originalMap == nil {
                obj.originalMap = make(map[string]contrail.ReferenceList)
        }
        var refCopy contrail.ReferenceList
        copy(refCopy, refList)
        obj.originalMap[name] = refCopy
}

func (obj *%(camel)s) UpdateDone() {
        obj.modified = 0