# Copyright (c) 2012 Santosh Philip
# Copyright (c) 2021  Dimitris Mantas
# =======================================================================
#  Distributed under the MIT License.
#  (See accompanying file LICENSE or copy at
#  http://opensource.org/licenses/MIT)
# =======================================================================

"""use epbunch"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from itertools import chain

from eppy.EPlusInterfaceFunctions import readidf
import eppy.bunchhelpers as bunchhelpers
from eppy.EPlusInterfaceFunctions.structures import CaseInsensitiveDict
from eppy.bunch_subclass import EpBunch

# from eppy.bunch_subclass import fieldnames, fieldvalues
import eppy.iddgaps as iddgaps
import eppy.function_helpers as fh
from eppy.idf_msequence import Idf_MSequence


class NoIDDFieldsError(Exception):
    pass


def iddversiontuple(afile):
    """given the idd file or filehandle, return the version handle"""

    def versiontuple(vers):
        """version tuple"""
        return tuple([int(num) for num in vers.split(".")])

    try:
        fhandle = open(afile, "rb")
    except TypeError:
        fhandle = afile
    line1 = fhandle.readline()
    try:
        line1 = line1.decode("ISO-8859-2")
    except AttributeError:
        pass
    line = line1.strip()
    if line1 == "":
        return (0,)
    vers = line.split()[-1]
    return versiontuple(vers)


def makeabunch(commdct, obj, obj_i, debugidd=True, block=None):
    """make a bunch from the object"""
    objidd = commdct[obj_i]
    objfields = [comm.get("field") for comm in commdct[obj_i]]
    objfields[0] = ["key"]
    objfields = [field[0] for field in objfields]
    obj_fields = [bunchhelpers.makefieldname(field) for field in objfields]
    bobj = EpBunch(obj, obj_fields, objidd)
    # TODO : test for len(obj) > len(obj_fields)
    # that will be missing fields in idd file
    # do we throw an exception here ????? YES !!!!!
    if debugidd:
        if len(obj) > len(obj_fields):
            n = len(obj) - len(obj_fields)
            extlst = extension_of_extensible(commdct[obj_i], block[obj_i], n)
            errortext = "idfobject with key '{}' & first field '{}' has {} fields while the idd for '{}' has only {} fields. Add the following fields to the object '{}' in file Energy+.idd '{};'".format(
                obj[0].upper(),
                obj[1],
                len(obj) - 1,
                obj[0].upper(),
                len(obj_fields) - 1,
                obj[0].upper(),
                ", ".join(extlst),
            )
            # "idfobject with key 'TIMESTEP' & first field '44' has 2 fields while the idd for 'TIMESTEP' has only 1 fields. Add the following fields to object 'TIMESTEP' in file Energy+.idd A5, A6;'"
            # print(block[obj_i])
            # print(errortext)
            print(extlst)
            raise NoIDDFieldsError(errortext)
    return bobj


def makebunches(data, commdct):
    """make bunches with data"""
    bunchdt = CaseInsensitiveDict()
    ddtt, dtls = data.dt, data.dtls
    for obj_i, key in enumerate(dtls):
        key = key.upper()
        bunchdt[key] = []
        objs = ddtt[key]
        for obj in objs:
            bobj = makeabunch(commdct, obj, obj_i)
            bunchdt[key].append(bobj)
    return bunchdt


def makebunches_alter(data, commdct, theidf, block=None):
    """make bunches with data"""
    bunchdt = CaseInsensitiveDict()
    dt, dtls = data.dt, data.dtls
    for obj_i, key in enumerate(dtls):
        key = key.upper()
        objs = dt[key]
        list1 = []
        for obj in objs:
            bobj = makeabunch(commdct, obj, obj_i, block=block)
            list1.append(bobj)
        bunchdt[key] = Idf_MSequence(list1, objs, theidf)
    return bunchdt


class ConvInIDD(object):
    """hold the conversion function to integer, real and no_type"""

    def no_type(self, x, avar):
        if avar.startswith("N"):  # is a number if it starts with N
            try:
                return float(x)  # in case x=autosize
            except ValueError as e:
                return x
        else:
            return x  # starts with A, is not a number

    def integer(self, x, y):
        try:
            return int(x)
        except ValueError as e:
            return x

    def real(self, x, y):
        try:
            return float(x)
        except ValueError as e:
            return x

    def conv_dict(self):
        """dictionary of conversion"""
        return dict(integer=self.integer, real=self.real, no_type=self.no_type)


# remove this one
def convertfields_old(key_comm, obj, inblock=None):
    """convert the float and interger fields"""
    convinidd = ConvInIDD()
    typefunc = dict(integer=convinidd.integer, real=convinidd.real)
    types = []
    for comm in key_comm:
        types.append(comm.get("type", [None])[0])
    convs = [typefunc.get(typ, convinidd.no_type) for typ in types]
    try:
        inblock = list(inblock)
    except TypeError as e:
        inblock = ["does not start with N"] * len(obj)
    for i, (val, conv, avar) in enumerate(zip(obj, convs, inblock)):
        if i == 0:
            # inblock[0] is the key
            pass
        else:
            val = conv(val, inblock[i])
        obj[i] = val
    return obj


def convertafield(field_comm, field_val, field_iddname):
    """convert field based on field info in IDD"""
    convinidd = ConvInIDD()
    field_typ = field_comm.get("type", [None])[0]
    conv = convinidd.conv_dict().get(field_typ, convinidd.no_type)
    return conv(field_val, field_iddname)


def convertfields(key_comm, obj, inblock=None):
    """convert based on float, integer, and A1, N1"""
    # f_ stands for field_
    convinidd = ConvInIDD()
    if not inblock:
        inblock = ["does not start with N"] * len(obj)
    for i, (f_comm, f_val, f_iddname) in enumerate(zip(key_comm, obj, inblock)):
        if i == 0:
            # inblock[0] is the iddobject key. No conversion here
            pass
        else:
            obj[i] = convertafield(f_comm, f_val, f_iddname)
    return obj


def convertallfields(data, commdct, block=None):
    """docstring for convertallfields"""
    # import pdbdb; pdb.set_trace()
    for key in list(data.dt.keys()):
        objs = data.dt[key]
        for i, obj in enumerate(objs):
            key_i = data.dtls.index(key)
            key_comm = commdct[key_i]
            try:
                inblock = block[key_i]
            except TypeError as e:
                inblock = None
            obj = convertfields(key_comm, obj, inblock)
            objs[i] = obj


def addfunctions(dtls, bunchdt):
    """add functions to the objects"""
    snames = [
        "BuildingSurface:Detailed",
        "Wall:Detailed",
        "RoofCeiling:Detailed",
        "Floor:Detailed",
        "FenestrationSurface:Detailed",
        "Shading:Site:Detailed",
        "Shading:Building:Detailed",
        "Shading:Zone:Detailed",
    ]
    for sname in snames:
        if sname.upper() in bunchdt:
            surfaces = bunchdt[sname.upper()]
            for surface in surfaces:
                func_dict = {
                    "area": fh.area,
                    "height": fh.height,  # not working correctly
                    "width": fh.width,  # not working correctly
                    "azimuth": fh.azimuth,
                    "tilt": fh.tilt,
                    "coords": fh.getcoords,  # needed for debugging
                }
                try:
                    surface.__functions.update(func_dict)
                except KeyError as e:
                    surface.__functions = func_dict
    # add common functions
    # for name in dtls:
    #     for idfobject in bunchdt[name]:
    # idfobject.__functions
    #         idfobject['__functions']['fieldnames'] = fieldnames
    #         idfobject['__functions']['fieldvalues'] = fieldvalues
    #         idfobject['__functions']['getrange'] = GetRange(idfobject)
    #         idfobject['__functions']['checkrange'] = CheckRange(idfobject)


def addfunctions2new(abunch, key):
    """add functions to a new bunch/munch object"""
    snames = [
        "BuildingSurface:Detailed",
        "Wall:Detailed",
        "RoofCeiling:Detailed",
        "Floor:Detailed",
        "FenestrationSurface:Detailed",
        "Shading:Site:Detailed",
        "Shading:Building:Detailed",
        "Shading:Zone:Detailed",
    ]
    snames = [sname.upper() for sname in snames]
    if key in snames:
        func_dict = {
            "area": fh.area,
            "height": fh.height,  # not working correctly
            "width": fh.width,  # not working correctly
            "azimuth": fh.azimuth,
            "tilt": fh.tilt,
            "coords": fh.getcoords,  # needed for debugging
        }
        try:
            abunch.__functions.update(func_dict)
        except KeyError as e:
            abunch.__functions = func_dict
    return abunch


def idfreader(fname, iddfile, conv=True):
    """read idf file and return bunches"""
    data, commdct, idd_index = readidf.readdatacommdct(fname, iddfile=iddfile)
    if conv:
        convertallfields(data, commdct)
    # fill gaps in idd
    ddtt, dtls = data.dt, data.dtls
    # skiplist = ["TABLE:MULTIVARIABLELOOKUP"]
    nofirstfields = iddgaps.missingkeys_standard(
        commdct, dtls, skiplist=["TABLE:MULTIVARIABLELOOKUP"]
    )
    iddgaps.missingkeys_nonstandard(None, commdct, dtls, nofirstfields)
    bunchdt = makebunches(data, commdct)
    return bunchdt, data, commdct, idd_index


def idfreader1(fname, iddfile, theidf, conv=True, commdct=None, block=None):
    """read idf file and return bunches"""
    versiontuple = iddversiontuple(iddfile)
    # import pdb; pdb.set_trace()
    block, data, commdct, idd_index = readidf.readdatacommdct1(
        fname, iddfile=iddfile, commdct=commdct, block=block
    )
    if conv:
        convertallfields(data, commdct, block)
    # fill gaps in idd
    ddtt, dtls = data.dt, data.dtls
    if versiontuple < (8,):
        skiplist = ["TABLE:MULTIVARIABLELOOKUP"]
    else:
        skiplist = None
    nofirstfields = iddgaps.missingkeys_standard(commdct, dtls, skiplist=skiplist)
    iddgaps.missingkeys_nonstandard(block, commdct, dtls, nofirstfields)
    # bunchdt = makebunches(data, commdct)
    bunchdt = makebunches_alter(data, commdct, theidf, block)
    return bunchdt, block, data, commdct, idd_index, versiontuple


def getextensible(objidd):
    """return the extensible from the idd"""
    keys = objidd[0].keys()
    extkey = [key for key in keys if key.startswith("extensible")]
    if extkey:
        extens = extkey[0].split(":")[-1]
        return int(extens)
    else:
        return None


def endof_extensible(extensible, thisblock):
    """get the vars from where extension happens"""
    return thisblock[-extensible:]


def extension_of_extensible(objidd, objblock, n):
    """generate the list of new vars needed to extend by n"""
    ext = getextensible(objidd)
    print(n, type(n), ext, type(ext))
    # n = int(n / ext)
    n = n // ext
    print(n, type(n))
    lastvars = endof_extensible(ext, objblock)
    print("ext, lastvars", ext, lastvars)
    alpha_lastvars = [i[0] for i in lastvars]
    int_lastvars = [int(i[1:]) for i in lastvars]
    lst = []
    for alpha, start in zip(alpha_lastvars, int_lastvars):
        step = alpha_lastvars.count(alpha)
        rng = range(start + step, start + 1 + n * step, step)
        lst.append(["{}{}".format(alpha, item) for item in rng])

    from itertools import chain

    return list(chain(*zip(*lst)))


# complete -- remove this junk below
# working code - working on it now.
# N3, A4, M8, A5
#
# N3, A4, M8, A5
# N4, A6, M9, A7
# N5, A8, M10, A9
# N6, A10, M11, A11


# ref = idf1.newidfobject("Refrigeration:WalkIn".upper())
# lastvars = ["N3", "A4", "M8", "A5"]
# lastvars = [u'A18',
#  u'N29',
#  u'N30',
#  u'N31',
#  u'N32',
#  u'N33',
#  u'A19',
#  u'N34',
#  u'N35',
#  u'N36',
#  u'A20',
#  u'A21']
# alpha_lastvars = [i[0] for i in lastvars]
# int_lastvars = [int(i[1:]) for i in lastvars]
#
#
#
# n = 2
#
# lst = []
# for alpha, start in zip(alpha_lastvars, int_lastvars):
#     step = alpha_lastvars.count(alpha)
#     rng = range(start +1, start + 1 + n * step, step)
#     lst.append(["{}{}".format(alpha, item) for item in rng])
#
# from itertools import chain
# c = list(chain(*zip(*lst)))
#
#
