#!/usr/bin/python
########################################################################################################################
#
# Copyright (c) 2020, Nifty Chips Laboratory, Hanyang University
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
# following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
#   disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
#    following disclaimer in the documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
########################################################################################################################

"""
This module implements interfaces with gds files via gdspy.
Hard copy of gds.py. This file will be deprecated soon.
"""

import logging
import pprint
from math import log10
from decimal import *
import laygo2.util.transform as tf

import numpy as np

import laygo2.object

def _load_layermap(layermapfile):
    """
    Load layermap information from layermapfile (Foundry techfile can be used)

    Parameters
    ----------
    layermapfile : str
        layermap filename.

        The example file can be found in default.layermap or see below:
        #technology layer information
        #layername  layerpurpose stream# datatype
        text        drawing 100 0
        prBoundary  drawing 101 0
        M1      drawing 50  0
        M1      pin     50  10
        M2      drawing 51  0
        M2      pin     51  10

    Returns
    -------
    dict
        constructed layermap information.

    """
    layermap = dict()
    f = open(layermapfile, 'r')
    for line in f:
        tokens = line.split()
        if not len(tokens) == 0:
            if not tokens[0].startswith('#'):
                name = tokens[0]
                #if not layermap.has_key(name):
                if name not in layermap:
                    layermap[name] = dict()
                layermap[name][tokens[1]] = {"layer":int(tokens[2]), "datatype":int(tokens[3])}
    return layermap


def _translate_obj(objname, obj, layermap, scale=0.001, master=None, offset=np.array([0, 0]), 
                   pin_label_height=0.1):
    """
    Convert an object to corresponding skill commands.
    offset : np.array([int, int])
        Offsets to obj.xy
    """
    # import gdspy here to avoid unnecessary C++ compliations for non-gds options.
    import gdspy

    if master is None:  
        mxy = np.array([0, 0])
        mtf = 'R0'
    else: # if the translated object has a master (e.g. VirtualInstance)
        mxy = master.xy
        mtf = master.transform

    if obj.__class__ == laygo2.object.Rect:
        ## TODO: add color handling.
        #color = obj.color # coloring function example for skill.
        
        _xy = np.sort(obj.xy, axis=0)  # make sure obj.xy is sorted
        _xy = mxy + np.dot(_xy + np.array([[-obj.hextension, -obj.vextension], 
                           [obj.hextension, obj.vextension]]), tf.Mt(mtf).T)
         
        l = layermap[obj.layer[0]][obj.layer[1]]
        rect = gdspy.Rectangle((_xy[0, 0], _xy[0, 1]), (_xy[1, 0], _xy[1, 1]), **l)
        return rect
    elif obj.__class__ == laygo2.object.Path:
        # TODO: implement path export function.
        pass
    elif obj.__class__ == laygo2.object.Pin:
        if obj.elements is None:
            _objelem = [obj]
        else:
            _objelem = obj.elements
        item = []
        for idx, _obj in np.ndenumerate(_objelem):
            _xy = mxy + np.dot(_obj.xy, tf.Mt(mtf).T)
            l = layermap[_obj.layer[0]][_obj.layer[1]]
            rect = gdspy.Rectangle((_xy[0, 0], _xy[0, 1]), (_xy[1, 0], _xy[1, 1]), **l)
            _xy_c = 0.5*(_xy[0, :] + _xy[1, :])
            text = gdspy.Label(_obj.netname, _xy_c, "nw", magnification=pin_label_height*100) 
            item += [rect, text]
        return item
    elif obj.__class__ == laygo2.object.Text:
        # TODO: implement text export function.
        pass
    elif obj.__class__ == laygo2.object.Instance:
        print("[Warning] laygo2.interface.gdspy: Instance transform is not implemented yet.")
        _xy = mxy + np.dot(obj.xy, tf.Mt(mtf).T)
        if master is None:  
            transform = obj.transform
        else: # if the translated object has a master (e.g. VirtualInstance)
            transform = tf.combine(obj.transform, master.transform)
        if obj.shape is None:
            num_rows = 1
            num_cols = 1
            sp_rows = 0
            sp_cols = 0
        else:
            num_rows = obj.shape[1]
            num_cols = obj.shape[0]
            sp_rows = obj.pitch[1]
            sp_cols = obj.pitch[0]
        #if obj.params is None:  # gds cannot handle pcell parameters.
        #    inst_params = "nil"
        #else:
        #    inst_params = _py2skill_inst_params(obj.params['pcell_params'])
        inst = gdspy.CellReference(obj.cellname, _xy) #, transform)
        return inst
    elif obj.__class__ == laygo2.object.VirtualInstance:
        item = []
        if obj.shape is None:
            for elem_name, elem in obj.native_elements.items():
                if not elem.__class__ == laygo2.object.Pin:
                    if obj.name == None:
                        obj.name='NoName'
                    else:
                        pass
                    item += [_translate_obj(obj.name + '_' + elem_name, elem, layermap=layermap, 
                                           master=obj, scale=scale, pin_label_height=pin_label_height)]
        else:  # arrayed VirtualInstance
            for i, j in np.ndindex(tuple(obj.shape.tolist())):  # iterate over obj.shape
                for elem_name, elem in obj.native_elements.items():
                    if not elem.__class__ == laygo2.object.Pin:
                        item += [_translate_obj(obj.name + '_' + elem_name + str(i) + '_' + str(j), 
                                               elem, layermap=layermap, master=obj[i, j],
                                               scale=scale, pin_label_height=pin_label_height)]
        return item
    return None
    #raise Exception("No corresponding GDS structure for:"+obj.summarize())


def export(db, filename, cellname=None, scale = 1e-9, layermapfile="default.layermap",
           physical_unit=1e-9, logical_unit=0.001, pin_label_height=0.1,
           svg_filename=None,png_filename=None):
    """
    Export design(s) to skill code.

    Parameters
    ----------
    db: laygo2.database.Library
        The library database to exported.
    filename: str, optional
        If specified, the generated skill script is stored in filename.
    cellname: str or List[str]
        The name(s) of cell(s) to be exported.
    scale: float
        The scaling factor between laygo2's integer coordinats actual physical coordinates.
    layermapfile : str
        the name of layermap file.
    physical_unit : float, optional
        GDS physical unit.
    logical_unit : float, optional
        GDS logical unit.
    pin_label_height : float, optional
        the height of pin label.
    svg_filename: str, optional
        If specified, it exports a svg file with the specified filename. 
    svg_filename: str, optional
        If specified, it exports a png file with the specified filename 
        (svg_filename needs to be specified as well). 
    """
    #raise Exception("GDS support is currently disabled. Will be reinvented by using gds-tk.")
    
    # Compute scale parameter.
    _scale = round(1/scale*physical_unit/logical_unit)
    # 1um in phy
    # 1um/1nm = 1000 in laygo2 if scale = 1e-9 (1nm)
    # 1000/1nm*1nm/0.001 = 1000000 in gds if physical_unit = 1e-9 (1nm) and logical_unit = 0.001

    # Load layermap file.
    layermap = _load_layermap(layermapfile)  # load layermap information

    # Construct cellname.    
    cellname = db.keys() if cellname is None else cellname  # export all cells if cellname is not given.
    cellname = [cellname] if isinstance(cellname, str) else cellname  # convert to a list for iteration.

    # import gdspy here to avoid unnecessary C++ compliations for non-gds options.
    import gdspy

    # Create library.
    lib = gdspy.GdsLibrary()
    for cn in cellname:
        # Create cell.
        cell = lib.new_cell(cn)
        # Translate objects.
        for objname, obj in db[cn].items():
            tobj = _translate_obj(objname, obj, layermap=layermap, scale=_scale,
                                  pin_label_height=pin_label_height,
                                 )
            if tobj is not None:
                cell.add(tobj)
    lib.write_gds(filename)
    if svg_filename is not None: 
        cell.write_svg(svg_filename)
        if svg_filename is not None: 
            # import cairosvg here to avoid unnecessary lib installation for non-gds options.
            import cairosvg
            cairosvg.svg2png(url=svg_filename, write_to=png_filename, scale=1.0)
    #gdspy.LayoutViewer()