import os
import shutil
import datetime
from compiler.compiler import CQT_Dtype
from cocytus_net.C.function_generator import FunctionGenerator

neon_padding_list = ('NEON_HPADDING_0', 'NEON_HPADDING_3', 'NEON_HPADDING_2', 'NEON_HPADDING_1',)


class CGenerator:
    def __init__(self, compiler):
        self.compiler = compiler
        self.config = self.compiler.config

    def generate(self):
        """
        Cソースファイルを作成する
        """
        # ディレクトリの作成
        target_dir = self.config['Cocyuts']['output_dir']
        create_c_dir(target_dir)

        # ヘッダーファイルの作成、コピー
        template_dir = self.config['Cocyuts']['c_lib_dir']

        self.generate_hedarfiles(target_dir, template_dir)

        # Cソースの作成
        target_dir = self.config['Cocyuts']['output_dir']
        self.generate_cqt_gen(target_dir, template_dir)

        target_dir = self.config['Cocyuts']['output_dir']
        self.generate_cqt_debug(target_dir, template_dir)

        # ライブラリの作成
        self.generate_cqt_lib(target_dir, template_dir)



    def generate_hedarfiles(self, target_dir, template_dir):
        """
        template_dirから、target_dirへヘッダファイルをコピーする。
        :param target_dir: str
        :param template_dir: str
        :return:
        """
        headers = ['cqt.h', 'cqt_type.h', 'cqt_keras.h', 'numpy.h', 'cqt_net.h']

        if self.compiler.is_neon_enable():
            headers.append('SSE2NEON.h')

        for h in headers:
            shutil.copy(os.path.join(template_dir, h),
                        os.path.join(target_dir, 'inc'))

    def generate_cqt_gen(self, target_dir, template_dir):
        """
        target_dirで指定されたディレクトリ/cqt_gen以下にcqt_gen.hとcqt_gen.cのファイルを作成する。
        :param target_dir: str
        :return:
        """
        cqt_gen_h_path = os.path.join(target_dir, 'cqt_gen', 'cqt_gen.h')
        print("making %s" % cqt_gen_h_path)
        cqt_gen_h = CqtGenH(cqt_gen_h_path, self.compiler)
        cqt_gen_h.generate()

        cqt_gen_c_path = os.path.join(target_dir, 'cqt_gen', 'cqt_gen.c')
        print("making %s" % cqt_gen_c_path)
        cqt_gen_c = CqtGenC(cqt_gen_c_path, self.compiler)
        cqt_gen_c.generate()

        files = ['numpy.c', 'cqt_lib.c']
        for f in files:
            shutil.copy(os.path.join(template_dir, f),
                        os.path.join(target_dir, 'cqt_lib'))

    def generate_cqt_debug(self, target_dir, template_dir):
        """
        target_dirで指定されたディレクトリ/cqt_gen以下にcqt_debug.hとcqt_debug.cのファイルを作成する。
        :param target_dir: str
        :return:
        """
        cqt_gen_h_path = os.path.join(target_dir, 'cqt_gen', 'cqt_debug.h')
        print("making %s" % cqt_gen_h_path)
        cqt_gen_h = CqtDebugH(cqt_gen_h_path, self.compiler)
        cqt_gen_h.generate()

        cqt_gen_c_path = os.path.join(target_dir, 'cqt_gen', 'cqt_debug.c')
        print("making %s" % cqt_gen_c_path)
        cqt_gen_c = CqtDebugC(cqt_gen_c_path, self.compiler)
        cqt_gen_c.generate()

    def generate_cqt_lib(self, target_dir, template_dir):
        """
        ## Ｃライブラリ(cqt_lib)
        ### cqt_lib.h
        ### cqt_lib.c
        ### numpy.c
        """
        cqt_lib_h_path = os.path.join(target_dir, 'inc', 'cqt_lib.h')
        print("making %s" % cqt_lib_h_path)
        cqt_lib_h = CqtLibH(cqt_lib_h_path, self.compiler)
        cqt_lib_h.generate()

        fg = FunctionGenerator(self.compiler, self.config, target_dir, template_dir)
        fg.generate()


class CFile:
    def __init__(self, file, compier):
        self.file = file
        self.fp = open(file, 'w')
        self.compiler = compier
        self.pd_dic = {'valid': 'PD_VALID', 'same': 'PD_SAME'}
        self.df_dic = {'channels_last' : 'DF_CHANNELS_LAST', 'channels_first': 'DF_CHANNELS_FIRST'}
        self.act_dic = {'linear': 'ACT_LINEAR', 'softmax': 'ACT_SOFTMAX', 'elu': 'ACT_ELU', 'softplus': 'ACT_SOFTPLUS',
                        'softsign': 'ACT_SOFTSIGN', 'relu': 'ACT_RELU', 'tanh': 'ACT_TANH', 'sigmoid': 'ACT_SIGMOID',
                        'hard_sigmoid': 'ACT_HARD_SIGMOID'
                        }
        self.bool_dic = {True: 'true', False: 'false'}

    def __del__(self):
        self.fp.close()

    def wr(self, s):
        """
        ファイルに文字列を書き込む
        :param s: str
        :return:
        """
        self.fp.write(s)

    def wr_file_header(self):
        """
        自走生成されるファイルにつけられるヘッダー。
        :return:
        """
        # TODO コピーライトやコキュートスのバージョンを追加したい
        today = datetime.date.today()
        self.fp.write("//----------------------------------------------------------------------------------------------------\n")
        self.fp.write("// This file is automatically generated.\n")
        self.fp.write("// %s\n" % today.strftime('%Y/%m/%d %H:%M:%S'))
        self.fp.write("//----------------------------------------------------------------------------------------------------\n")

    def wr_include(self, file, stdlib=False):
        """
        include分を挿入する。
        stdlib = True　の時は #include <file>　形式を、stdlib = Falseの時は
        #include "file" 形式で書き込む。

        :param file: str
        :param stdlib: bool
        :return:
        """
        if stdlib:
            self.wr('#include <%s>\n' % file)
        else:
            self.wr('#include "%s"\n' % file)

    def cr(self):
        """
        改行の挿入
        :return:
        """
        self.wr('\n')

    def write_cqt_network(self, scope=None):
        """
        コキュートスニューラルネットの定義を行う。
        scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。
        """
        name = self.compiler.get_model_name()
        scope_s = add_space(scope)

        self.wr('// cocytus network\n')
        self.wr('%sCQT_NET %s;\n' % (scope_s, name))

    def wr_layer_definition(self, scope=None):
        """
        レイヤー変数の定義を行う。
        scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。
        :return:
        """
        self.wr("//Layers\n")
        layers = self.compiler.get_layers()
        for l in layers:
            name = l.name
            cqt_layer = self.compiler.get_cqt_layer_obj(name)
            class_name = cqt_layer.keras_layer_type
            scope_s = add_space(scope)
            s = "%sLY_%s %s;\n" % (scope_s, class_name, name)
            self.wr(s)

    def wr_weight_definition(self, scope=None):
        """
        weight変数の定義を行う。
        scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。
        :return:
        """
        self.wr("//weights\n")
        layers = self.compiler.get_layers()
        for l in layers:
            name = l.name
            cqt_layer = self.compiler.get_cqt_layer_obj(name)
            class_name = cqt_layer.keras_layer_type
            if class_name == 'Conv2D':
                layer_detal = self.compiler.get_cqt_layer_obj(name)
                w_shape = layer_detal.get_Wshape()
                w_name, w_nph_name, b_name, b_nph_name = layer_detal.get_conv2d_weight_variable_name()
                w_dim_s = dim_str_from_keras_4d_shape(w_shape)
                b_dim_s = dim_str_from_keras_4d_shape_bias(w_shape)
                scope_s = add_space(scope)

                w_type = layer_detal.get_weight_type_str()

                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, w_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, b_nph_name))
                self.wr("%s%s %s%s;\n" % (scope_s, w_type, w_name, w_dim_s))
                self.wr("%s%s %s%s;\n" % (scope_s, w_type, b_name, b_dim_s))
            elif class_name == 'Dense':
                layer_detal = self.compiler.get_cqt_layer_obj(name)
                w_shape = layer_detal.get_Wshape()
                input_dim = w_shape[0]
                output_dim = w_shape[1]
                scope_s = add_space(scope)
                w_type = layer_detal.get_weight_type_str()

                w_name, w_nph_name, b_name, b_nph_name = layer_detal.get_conv2d_weight_variable_name()

                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, w_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, b_nph_name))
                self.wr("%s%s %s[%d][%d];\n" % (scope_s, w_type, w_name, output_dim, input_dim))
                self.wr("%s%s %s[%s];\n" % (scope_s, w_type, b_name, output_dim))

            elif class_name == 'BatchNormalization':
                layer_detal = self.compiler.get_cqt_layer_obj(name)
                b_dim, gm_dim, mm_dim, mv_dim = layer_detal.get_Wshape()

                beta_name, beta_nph_name, gamma_name, gamma_nph_name, mm_name, mm_nph_name, mv_name, mv_nph_name = layer_detal.get_batchnormalization_weight_variable_name()
                w_type = layer_detal.get_weight_type_str()

                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, beta_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, gamma_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, mm_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, mv_nph_name))

                self.wr("%s%s %s[%d];\n" % (scope_s, w_type, beta_name, b_dim))
                self.wr("%s%s %s[%d];\n" % (scope_s, w_type, gamma_name, gm_dim))
                self.wr("%s%s %s[%d];\n" % (scope_s, w_type, mm_name, mm_dim))
                self.wr("%s%s %s[%d];\n" % (scope_s, w_type, mv_name, mv_dim))


    def wr_output_definition(self, scope=None, conv_pointer=False):
        """
        output変数の定義を行う。
        scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。
        conv_pointerがTrueの時は、pointerで宣言する。
        :return:
        """
        self.wr("//outputs\n")
        channel_last = self.compiler.is_output_channel_last()

        layers = self.compiler.get_layers()
        for l in layers:
            name = l.name
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type
            o_shape = layer_detal.get_output_shape()


            o_name = layer_detal.get_output_variable_name()
            o_type = layer_detal.get_output_type_str()
            scope_s = add_space(scope)

            if not self.compiler.is_neon_enable():
                # neon非対応の処理
                dim_s = dim_str_from_keras_4d_shape_output(o_shape, cl=channel_last)
                if conv_pointer:
                    # pointerで宣言
                    self.wr('%s%s *%s; //%s%s\n' % (scope_s, o_type, o_name, o_name, dim_s))

                else:
                    self.wr('%s%s %s%s;\n' % (scope_s, o_type, o_name, dim_s))
            else:
                # neon 対応時に外枠を確保する。
                # 横のサイズが4の倍数で無い時はパディングを入れる。
                dim_s = dim_str_from_keras_4d_shape_output_noen(o_shape, layer_detal)

                self.wr('%s%s %s%s;\n' % (scope_s, o_type, o_name, dim_s))



    def wr_assign(self, variable_name, value, tab=1):
        """
        Cの代入分を書き出す。tabでタブの個数を指定できる。
        :param variable_name: str
        :param value: str
        :param tab: int
        :return:
        """
        tabs = '\t' * tab
        if isinstance(value, list) or isinstance(value, tuple):
            for i, v in enumerate(value):
                self.wr('%s%s[%d] = %s;\n' % (tabs, variable_name, i, str(v)))
        else:
            if str(value) == 'True':
                value = 'true'
            self.wr('%s%s = %s;\n' % (tabs, variable_name, str(value)))

    def get_config(self):
        """
        Keras Modelのコンフィグ情報を返す。
        :return:
        """
        return self.compiler.model.get_config()


class CqtGenH(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('stdio.h', stdlib=True)
        self.wr_include('string.h', stdlib=True)
        self.wr_include('assert.h', stdlib=True)
        self.wr_include('cqt.h')
        self.wr_include('cqt_net.h')
        self.cr()

        if self.compiler.is_output_channel_last():
            self.wr('#define CQT_CHANNEL_LAST\n')

        self.cr()

        self.wr('CQT_NET* cqt_init(void);\n')
        self.wr('int cqt_load_weight_from_files(CQT_NET* np, const char *path);\n')
        self.wr('int cqt_run(CQT_NET* np, void *dp);\n')
        self.wr('void cqt_close(CQT_NET* np);\n')

        self.cr()
        self.wr('extern int cqt_process;\n')
        self.cr()

        self.write_cqt_network(scope='extern')
        self.cr()

        self.wr_layer_definition(scope='extern')
        self.cr()

        self.wr_weight_definition(scope='extern')
        self.cr()

        self.wr_output_definition(scope='extern', conv_pointer=self.compiler.is_target_sdsoc())
        self.cr()


        self.fp.write('\n')


class CqtGenC(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('cqt_gen.h')
        self.wr_include('cqt_lib.h')
        self.cr()

        self.wr('int cqt_process;\n')
        self.cr()

        self.write_cqt_network()
        self.cr()

        self.wr_layer_definition()
        self.cr()

        self.wr_weight_definition()
        self.cr()

        self.wr_output_definition(conv_pointer=self.compiler.is_target_sdsoc())
        self.cr()

        self.write_cqt_init()
        self.cr()
        self.write_cqt_load_weight_from_files()
        self.cr()
        self.write_cqt_run()
        self.cr()
        self.write_cqt_close()
        self.cr()

    def write_cqt_init(self):
        """
        cqt_init関数を書き出す。
        :return:
        """
        self.wr('CQT_NET* cqt_init(void) {\n')
        model_config = self.get_config()

        layers = self.compiler.get_layers()
        layer_num = len(layers)
        cqt_net_name = self.compiler.get_model_name()
        self.wr('\t%s.layernum = %d;\n' % (cqt_net_name, layer_num))
        self.wr('\tcqt_process = 0;\n')
        self.cr()

        for (i, l) in enumerate(layers):
            name = l.name
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            self.wr('\tstrcpy(%s.layer[%d].name, "%s");\n' % (cqt_net_name, i, name))
            self.wr('\t%s.layer[%d].type = LT_%s;\n' % (cqt_net_name, i, class_name))

            # レイヤー毎の処理
            if class_name == 'InputLayer' or class_name == 'Flatten':
                # 何もしない
                pass
            elif class_name == 'Conv2D':
                self.write_conv2d_init(l)
            elif class_name == 'MaxPooling2D':
                self.write_maxpooling2d(l)
            elif class_name == 'Dense':
                self.write_dense(l)
            elif class_name == 'BatchNormalization':
                self.write_batchnormalization(l)
            elif class_name == 'LeakyReLU':
                self.write_leakyrelu(l)
            else:
                raise ValueError("Error layer %s tpye is not supported" % class_name)

            # 共通の処理
            for j in range(4):
                if j < len(layer_detal.input_dtypes):
                    c_type = conv_type_cqt_to_c(layer_detal.input_dtypes[j])
                    self.wr('\t%s.layer[%d].input_dtypes[%d] = %s;\n' % (cqt_net_name, i, j, c_type))
                else:
                    self.wr('\t%s.layer[%d].input_dtypes[%d] = CQT_DTYPE_NONE;\n' % (cqt_net_name, i, j))

            for j in range(4):
                    if j < len(layer_detal.weight_dtypes):
                        c_type = conv_type_cqt_to_c(layer_detal.weight_dtypes[j])
                        self.wr('\t%s.layer[%d].weight_dtypes[%d] = %s;\n' % (cqt_net_name, i, j, c_type))
                    else:
                        self.wr('\t%s.layer[%d].weight_dtypes[%d] = CQT_DTYPE_NONE;\n' % (cqt_net_name, i, j))

            for j in range(4):
                if j < len(layer_detal.output_dtypes):
                    c_type = conv_type_cqt_to_c(layer_detal.output_dtypes[j])
                    self.wr('\t%s.layer[%d].output_dtypes[%d] = %s;\n' % (cqt_net_name, i, j, c_type))
                else:
                    self.wr('\t%s.layer[%d].output_dtypes[%d] = CQT_DTYPE_NONE;\n' % (cqt_net_name, i, j))

            i_shape = [0 if x is None else x for x in layer_detal.get_input_shape()]
            if len(i_shape) == 4:
                self.wr('\t%s.layer[%d].cqt_input_shape[0] = %d;\n' % (cqt_net_name, i, i_shape[0]))
                self.wr('\t%s.layer[%d].cqt_input_shape[1] = %d;\n' % (cqt_net_name, i, i_shape[1]))
                self.wr('\t%s.layer[%d].cqt_input_shape[2] = %d;\n' % (cqt_net_name, i, i_shape[2]))
                self.wr('\t%s.layer[%d].cqt_input_shape[3] = %d;\n' % (cqt_net_name, i, i_shape[3]))
            elif len(o_shape) == 3:
                self.wr('\t%s.layer[%d].cqt_input_shape[0] = %d;\n' % (cqt_net_name, i, i_shape[0]))
                self.wr('\t%s.layer[%d].cqt_input_shape[1] = %d;\n' % (cqt_net_name, i, i_shape[1]))
                self.wr('\t%s.layer[%d].cqt_input_shape[2] = %d;\n' % (cqt_net_name, i, i_shape[2]))
                self.wr('\t%s.layer[%d].cqt_input_shape[3] = 0;\n' % (cqt_net_name, i))
            elif len(o_shape) == 2:
                self.wr('\t%s.layer[%d].cqt_input_shape[0] = %d;\n' % (cqt_net_name, i, i_shape[0]))
                self.wr('\t%s.layer[%d].cqt_input_shape[1] = %d;\n' % (cqt_net_name, i, i_shape[1]))
                self.wr('\t%s.layer[%d].cqt_input_shape[2] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_input_shape[3] = 0;\n' % (cqt_net_name, i))
            elif len(o_shape) == 1:
                self.wr('\t%s.layer[%d].cqt_input_shape[0] = %d;\n' % (cqt_net_name, i, i_shape[0]))
                self.wr('\t%s.layer[%d].cqt_input_shape[1] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_input_shape[2] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_input_shape[3] = 0;\n' % (cqt_net_name, i))
            else:
                raise ValueError("ERROR unsupported shape %s %s", (name, str(o_shape)))

            # Noneを0に置き換え
            o_shape = [0 if x is None else x for x in layer_detal.get_output_shape()]
            if len(o_shape) == 4:
                self.wr('\t%s.layer[%d].cqt_output_shape[0] = %d;\n' % (cqt_net_name, i, o_shape[0]))
                self.wr('\t%s.layer[%d].cqt_output_shape[1] = %d;\n' % (cqt_net_name, i, o_shape[1]))
                self.wr('\t%s.layer[%d].cqt_output_shape[2] = %d;\n' % (cqt_net_name, i, o_shape[2]))
                self.wr('\t%s.layer[%d].cqt_output_shape[3] = %d;\n' % (cqt_net_name, i, o_shape[3]))
                out_size = o_shape[3] * o_shape[2] * o_shape[1]
                out_size_s = '%d * %d * %d' % (o_shape[3], o_shape[2], o_shape[1])
            elif len(o_shape) == 3:
                self.wr('\t%s.layer[%d].cqt_output_shape[0] = %d;\n' % (cqt_net_name, i, o_shape[0]))
                self.wr('\t%s.layer[%d].cqt_output_shape[1] = %d;\n' % (cqt_net_name, i, o_shape[1]))
                self.wr('\t%s.layer[%d].cqt_output_shape[2] = %d;\n' % (cqt_net_name, i, o_shape[2]))
                self.wr('\t%s.layer[%d].cqt_output_shape[3] = 0;\n' % (cqt_net_name, i))
                out_size = o_shape[2] * o_shape[1]
                out_size_s = '%d * %d' % (o_shape[2], o_shape[1])
            elif len(o_shape) == 2:
                self.wr('\t%s.layer[%d].cqt_output_shape[0] = %d;\n' % (cqt_net_name, i, o_shape[0]))
                self.wr('\t%s.layer[%d].cqt_output_shape[1] = %d;\n' % (cqt_net_name, i, o_shape[1]))
                self.wr('\t%s.layer[%d].cqt_output_shape[2] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_output_shape[3] = 0;\n' % (cqt_net_name, i))
                out_size = o_shape[1]
                out_size_s = '%d' % o_shape[1]
            elif len(o_shape) == 1:
                self.wr('\t%s.layer[%d].cqt_output_shape[0] = %d;\n' % (cqt_net_name, i, o_shape[0]))
                self.wr('\t%s.layer[%d].cqt_output_shape[1] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_output_shape[2] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_output_shape[3] = 0;\n' % (cqt_net_name, i))
                out_size = o_shape[0]
                out_size_s = '%d' % o_shape[0]
            else:
                raise ValueError("ERROR unsupported shape %s %s", (name, str(o_shape)))

            input_q = layer_detal.input_q
            weight_q = layer_detal.weight_q
            output_q = layer_detal.output_q

            self.wr('\t%s.layer[%d].input_q = %d;\n' % (cqt_net_name, i, input_q))
            self.wr('\t%s.layer[%d].weight_q = %d;\n' % (cqt_net_name, i, weight_q))
            self.wr('\t%s.layer[%d].output_q = %d;\n' % (cqt_net_name, i, output_q))
            self.wr('\t%s.layer[%d].overflow_cnt = 0;\n' % (cqt_net_name, i))

            if self.compiler.is_neon_enable():
                padi = neon_padding_list[i_shape[1] % 4]
                self.wr('\t%s.layer[%d].neon_padding_hi = %s;\n' % (cqt_net_name, i, padi))
                pado = neon_padding_list[o_shape[1] % 4]
                self.wr('\t%s.layer[%d].neon_padding_ho = %s;\n' % (cqt_net_name, i, pado))

            o_name = layer_detal.get_output_variable_name()
            self.wr('\t%s.layer[%d].param_p = &%s;\n' % (cqt_net_name, i, name))
            self.wr('\t%s.layer[%d].data_p = &%s;\n' % (cqt_net_name, i, o_name))

            if self.compiler.is_target_sdsoc():
                # sds_alloc
                if layer_detal.output_dtypes[0] == CQT_Dtype.FLOAT32:
                    pass
                elif  layer_detal.output_dtypes[0] == CQT_Dtype.FLOAT16:
                    pass
                else:
                    print('ERROR unsuported dtype %s', layer_detal.output_dtypes[0])
                    sys.exit()

                o_shape = layer_detal.get_output_shape()
                o_type = layer_detal.get_output_type_str()

                self.cr()
                self.wr('\t%s = (%s *)sds_alloc(%s * sizeof(%s));\n' % (o_name, o_type, out_size_s, o_type))
                self.wr('\tif (%s == NULL) {\n' % o_name)
                self.wr('\t\treturn NULL;\n')
                self.wr('\t}\n')
                self.cr();


            self.cr()

        self.wr('\treturn &%s;\n' % cqt_net_name)
        self.wr('}\n')

    def write_conv2d_init(self, l):
        """
        LY_Conv2dの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        class_name = layer_detal.keras_layer_type
        w_name, w_nph_name, b_name, b_nph_name = layer_detal.get_conv2d_weight_variable_name()

        self.wr_assign("%s.filters" % name, config['filters'])
        self.wr_assign("%s.kernel_size" % name, config['kernel_size'])
        self.wr_assign("%s.strides" % name, config['strides'])
        self.wr_assign("%s.padding" % name, self.pd_dic[config['padding']])
        self.wr_assign("%s.data_format" % name, self.df_dic[config['data_format']])
        self.wr_assign("%s.dilation_rate" % name, config['dilation_rate'])
        self.wr_assign("%s.activation" % name, self.act_dic[config['activation']])
        self.wr_assign("%s.use_bias" % name, self.bool_dic[config['use_bias']])
        self.wr_assign("%s.weight_np_header_p" % name, '&'+ w_nph_name)
        self.wr_assign("%s.weight_p" % name, '&'+ w_name)
        self.wr_assign("%s.bias_np_header_p" % name, '&'+ b_nph_name)
        self.wr_assign("%s.bias_p" % name, '&'+ b_name)


    def write_maxpooling2d(self, l):
        """
        LY_MaxPooling2Dの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        class_name = layer_detal.keras_layer_type

        self.wr_assign("%s.strides" % name, config['strides'])
        self.wr_assign("%s.padding" % name, self.pd_dic[config['padding']])
        self.wr_assign("%s.data_format" % name, self.df_dic[config['data_format']])
        self.wr_assign("%s.pool_size" % name, config['pool_size'])

    def write_dense(self, l):
        """
        LY_Denseの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        class_name = layer_detal.keras_layer_type
        w_name, w_nph_name, b_name, b_nph_name = layer_detal.get_conv2d_weight_variable_name()

        self.wr_assign("%s.units" % name, config['units'])
        self.wr_assign("%s.activation" % name, self.act_dic[config['activation']])
        self.wr_assign("%s.use_bias" % name, self.bool_dic[config['use_bias']])
        self.wr_assign("%s.weight_np_header_p" % name, '&'+ w_nph_name)
        self.wr_assign("%s.weight_p" % name, '&'+ w_name)
        self.wr_assign("%s.bias_np_header_p" % name, '&'+ b_nph_name)
        self.wr_assign("%s.bias_p" % name, '&'+ b_name)

    def write_batchnormalization(self, l):
        """
        BatchNormalizationの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        self.wr_assign("%s.axis" % name, config['axis'])
        self.wr_assign("%s.momentum" % name, config['momentum'])
        self.wr_assign("%s.epsilon" % name, config['epsilon'])
        self.wr_assign("%s.center" % name, config['center'])
        self.wr_assign("%s.scale" % name, config['scale'])
        beta_name, beta_nph_name, gamma_name, gamma_nph_name, mm_name, mm_nph_name, mv_name, mv_nph_name = layer_detal.get_batchnormalization_weight_variable_name()
        self.wr_assign("%s.beta_np_header_p" % name, '&' + beta_nph_name)
        self.wr_assign("%s.beta_p" % name, '&' + beta_name)
        self.wr_assign("%s.gamma_np_header_p" % name, '&' + gamma_nph_name)
        self.wr_assign("%s.gamma_p" % name, '&' + gamma_name)
        self.wr_assign("%s.moving_mean_np_header_p" % name, '&' + mm_nph_name)
        self.wr_assign("%s.moving_mean_p" % name, '&' + mm_name)
        self.wr_assign("%s.moving_variance_np_header_p" % name, '&' + mv_nph_name)
        self.wr_assign("%s.moving_variance_p" % name, '&' + mv_name)

    def write_leakyrelu(self, l):
        """
        LeakyReLUの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        self.wr_assign("%s.alpha" % name, config['alpha'])


    def write_cqt_load_weight_from_files(self):
        self.wr('int cqt_load_weight_from_files(CQT_NET* np, const char *path) {\n')
        self.wr('\tchar buf[CQT_MAX_PATH];\n')
        self.wr('\tsize_t path_len;\n')
        self.wr('\tsize_t fname_w_len;\n')
        self.wr('\tsize_t fname_b_len;\n')
        self.wr('\tint ret;\n')
        self.wr('\n')

        #model_config = self.get_config()
        layers = self.compiler.get_layers()

        for i, l in enumerate(layers):
            name = l.name
            config = l.get_config()
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            if class_name in ['Conv2D', 'Dense']:
                if self.compiler.weight_filename_mode == 0:
                    fname_w = name + '_W_1_z.npy'
                    fname_b = name + '_b_1_z.npy'
                elif self.compiler.weight_filename_mode == 1:
                    fname_w = name + '_kernel_z.npy'
                    fname_b = name + '_bias_z.npy'
                else:
                    raise ValueError('Unkown weight_filename_mode %d' % self.compiler.weight_filename_mode)

                [variable_name_w, variable_name_w_header, variable_name_b,
                 variable_name_b_header] = layer_detal.get_conv2d_weight_variable_name()

                if class_name == 'Conv2D':
                    prev_dim = layer_detal.get_prev_layer_output_dimension(i)
                    kernel_size = config['kernel_size']
                    w_size = kernel_size[0] * kernel_size[1] * config['filters'] * prev_dim
                    b_size = config['filters']
                else:
                    # dense
                    prev_dim = layer_detal.get_prev_layer_output_dimension(i)
                    i_shape = layer_detal.get_input_shape()
                    o_shape = layer_detal.get_output_shape()
                    w_size = i_shape[-1] * o_shape[-1]
                    b_size = o_shape[-1]

                self.wr('// %s\n' % name)
                self.wr('\tpath_len = strlen(path);\n')
                self.wr('\tfname_w_len = strlen("%s");\n' % fname_w)
                self.wr('\tassert(path_len+fname_w_len<CQT_MAX_PATH);\n')
                self.wr('\tstrcpy(buf, path);\n')
                self.wr('\tstrcat(buf, "%s");\n' % fname_w)
                self.wr('\tret = load_from_numpy(%s, buf, %d, &%s);\n' % (variable_name_w, w_size, variable_name_w_header))
                self.wr('\tif(ret != CQT_RET_OK){\n')
                self.wr('\t\treturn ret;\n')
                self.wr('\t}\n')
                self.cr()

                if l.use_bias:
                    self.wr('\tfname_b_len = strlen("%s");\n' % fname_b)
                    self.wr('\tassert(path_len+fname_b_len<CQT_MAX_PATH);\n')
                    self.wr('\tstrcpy(buf, path);\n')
                    self.wr('\tstrcat(buf, "%s");\n' % fname_b)
                    self.wr('\tret = load_from_numpy(%s, buf, %d, &%s);\n' % (variable_name_b, b_size, variable_name_b_header))
                    self.wr('\tif(ret != CQT_RET_OK){\n')
                    self.wr('\t\treturn ret;\n')
                    self.wr('\t}\n')

                self.cr()
            elif class_name == 'BatchNormalization':
                # 重みのファイル名
                fname_beta = name + '_beta_z.npy'
                fname_gamma = name + '_gamma_z.npy'
                fname_mm = name + '_moving_mean_z.npy'
                fname_mv = name + '_moving_variance_z.npy'
                # 変数名
                beta_name, beta_nph_name, gamma_name, gamma_nph_name, mm_name, mm_nph_name, mv_name, mv_nph_name = layer_detal.get_batchnormalization_weight_variable_name()

                fnames = [fname_beta, fname_gamma, fname_mm, fname_mv]
                variable_names = [beta_name, gamma_name, mm_name, mv_name]
                nph_names = [beta_nph_name, gamma_nph_name, mm_nph_name, mv_nph_name]
                dims = layer_detal.get_Wshape()

                self.wr('// %s\n' % name)
                for fn, vn, npn, dim in zip(fnames, variable_names, nph_names, dims):
                    self.wr('\tfname_b_len = strlen("%s");\n' % fn)
                    self.wr('\tassert(path_len+fname_b_len<CQT_MAX_PATH);\n')
                    self.wr('\tstrcpy(buf, path);\n')
                    self.wr('\tstrcat(buf, "%s");\n' % fn)
                    self.wr('\tret = load_from_numpy(%s, buf, %d, &%s);\n' % (vn, dim, npn))
                    self.wr('\tif(ret != CQT_RET_OK){\n')
                    self.wr('\t\treturn ret;\n')
                    self.wr('\t}\n')
                    self.cr()

        self.wr('\treturn CQT_RET_OK;\n')
        self.wr('}\n')

    def write_cqt_run(self):
        self.wr('int cqt_run(CQT_NET* np, void *dp) {\n')
        self.wr('\tint ret;\n')
        self.cr()

        model_config = self.get_config()
        cqt_net_name = self.compiler.get_model_name()
        layers = self.compiler.get_layers()


        for i, l in enumerate(layers):
            name = l.name
            config = l.get_config()
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            inp = self.compiler.get_prev_layer_output_name(i)
            outp = layer_detal.get_output_variable_name()

            func_name = layer_detal.make_func_name()

            # FPGA用に関数を分ける
            if class_name == 'Conv2D' and self.compiler.is_target_sdsoc():
                size = l.kernel_size
                if size == (3, 3):
                    func_name = 'CQT_' + name + "_3x3"

            self.wr('\t//%s\n' % name)
            self.wr('\tcqt_process = %d;\n' % i)
            self.wr("\tret = %s(&(%s.layer[%d]), %s, %s);\n" % (func_name, cqt_net_name, i, inp, outp))
            self.wr('\tif(ret != CQT_RET_OK){\n')
            self.wr('\t\treturn ret;\n')
            self.wr('\t}\n')
            self.wr('\n')

        self.wr('\treturn CQT_RET_OK;\n')
        self.wr('}\n')


    def write_cqt_close(self):
        self.wr('void cqt_close(CQT_NET* np) {\n')
        self.cr()

        if self.compiler.is_target_sdsoc():

            layers = self.compiler.get_layers()
            layer_num = len(layers)
            cqt_net_name = self.compiler.get_model_name()

            for (i, l) in enumerate(layers):
                name = l.name
                layer_detal = self.compiler.get_cqt_layer_obj(name)
                o_name = layer_detal.get_output_variable_name()
                self.wr('\tsds_free(%s);\n' % o_name)

        self.wr('}\n')


class CqtLibH(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('cqt.h')
        self.wr_include('cqt_net.h')
        self.cr()

        model_config = self.get_config()
        cqt_net_name = self.compiler.get_model_name()

        func_list = []
        layers = self.compiler.get_layers()

        for i, l in enumerate(layers):
            name = l.name
            config = l.get_config()
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            func_name = layer_detal.make_func_name()

            # FPGA用に関数を分ける
            if class_name == 'Conv2D' and self.compiler.is_target_sdsoc():
                size = l.kernel_size
                if size == (3, 3):
                    func_name = 'CQT_' + name + "_3x3"
                    
            if not func_name in func_list:
                self.wr('int %s(CQT_LAYER *lp, void *inp, void *outp);\n' % func_name)
                func_list.append(func_name)

        self.cr()


class CqtDebugH(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('stdio.h', stdlib=True)
        self.wr_include('stdlib.h', stdlib=True)
        self.wr_include('string.h', stdlib=True)
        self.wr_include('assert.h', stdlib=True)
        self.wr_include('cqt.h')
        self.wr_include('cqt_net.h')
        self.cr()

        self.wr('void cqt_layerdump(int l);\n')

        layers = self.compiler.get_layers()

        for i in range(len(layers)):
            self.wr('void cqt_layer%d_dump(void);\n' % i)

        self.fp.write('\n')


class CqtDebugC(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('cqt_debug.h')
        self.cr()

        self.wr_output_definition(scope='extern', conv_pointer=self.compiler.is_target_sdsoc())
        self.cr()
        self.wr('extern NUMPY_HEADER np;\n')
        self.cr()

        layers = self.compiler.get_layers()
        self.write_cqt_layerdump(len(layers))


        for i, l in enumerate(layers):
            name = l.name
            config = l.get_config()
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            odim = layer_detal.get_output_shape()
            output_variable_name = layer_detal.get_output_variable_name()

            if len(odim) == 4:
                size1 = odim[1]
                size2 = odim[2]
                num = odim[3]

                self.wr("void cqt_layer%d_dump(void)\n" % i)
                self.wr("{\n")
                self.wr("\tNUMPY_HEADER np_0 = np;\n")
                self.wr("\tint ret;\n")
                self.cr()
                if not self.compiler.is_neon_enable():
                    self.wr("\tnp_0.shape[0] = %d;\n" % num)
                    self.wr("\tnp_0.shape[1] = %d;\n" % size1)
                    self.wr("\tnp_0.shape[2] = %d;\n" % size2)
                    self.wr("\tnp_0.shape[3] = 0;\n")
                else:
                    # neon対応
                    padding = neon_padding_list[size2 % 4]
                    # 並びが怪しい
                    self.wr("\tnp_0.shape[0] = %d;\n" % num)
                    self.wr("\tnp_0.shape[1] = %d + NEON_VTR*3;\n" % size1)
                    self.wr("\tnp_0.shape[2] = NEON_HTR + %d + %s;\n" % (size2, padding))
                    self.wr("\tnp_0.shape[3] = 0;\n")


                self.cr()
                self.wr('\tret = save_to_numpy(%s, "output/l%02d.npy", & np_0);\n' % (output_variable_name, i))
                self.wr('\tif (ret != CQT_RET_OK) {\n')
                self.wr('\t\tprintf("ERROR in layer0_output %d\\n", ret);\n')
                self.wr('\t}\n')
            else:
                size1 = odim[1]
                self.wr("void cqt_layer%d_dump(void)\n" % i)
                self.wr("{\n")
                self.wr("\tNUMPY_HEADER np_0 = np;\n")
                self.wr("\tint ret;\n")
                self.cr()
                self.wr("\tnp_0.shape[0] = %d;\n" % size1)
                self.wr("\tnp_0.shape[1] = 0;\n")
                self.wr("\tnp_0.shape[2] = 0;\n")
                self.wr("\tnp_0.shape[3] = 0;\n")
                self.cr()
                self.wr('\tret = save_to_numpy((void *)%s, "output/l%02d_0.npy", &np_0);\n' % (output_variable_name, i))
                self.wr('\tif (ret != CQT_RET_OK) {\n')
                self.wr('\t\tprintf("ERROR in layer0_output %d\\n", ret);\n')
                self.wr('\t}\n')


            self.wr("}\n")

            self.cr()

        self.cr()

    def write_cqt_layerdump(self, num):
        self.wr('void cqt_layerdump(int l)\n')
        self.wr('{\n')
        self.wr('\tswitch(l) {\n')

        for i in range(num):
            self.wr('\tcase %d:\n' % i)
            self.wr('\t\tcqt_layer%d_dump();\n' % i)
            self.wr('\t\tbreak;\n')
        self.wr('\tdefault:\n')
        self.wr('\t\tprintf("ERROR:invalid layer number %d\\n", l);\n')
        self.wr('\t\texit(1);\n')

        self.wr('\t}\n')

        self.wr('}\n')
        self.cr()




def create_c_dir(tdir):
    """
    tdir以下に以下のディレクトリを作成する。
    inc, cqt_gen, cqt_lib, weight
    :param tdir: str
    :return:
    """
    dirs = ['inc', 'cqt_gen', 'cqt_lib', 'weight', 'output']
    for d in dirs:
        path = os.path.join(tdir, d)
        if not os.path.isdir(path):
            os.makedirs(path)


def dim_str_from_keras_4d_shape(shape):
    assert(len(shape)==4)
    if shape[3] is None:
        return "[%d][%d][%d]" % (shape[2], shape[1], shape[0])
    else:
        return "[%d][%d][%d][%d]" % (shape[3], shape[2], shape[1], shape[0])


def dim_str_from_keras_4d_shape_output(shape, cl=False, neon=False):
    if len(shape) == 4:
        if cl:
            return "[%d][%d][%d]" % (shape[1], shape[2], shape[3])
        else:
            return "[%d][%d][%d]" % (shape[3], shape[2], shape[1])
    elif len(shape) == 3:
        return "[%d][%d]" % (shape[2], shape[1])
    elif len(shape) == 2:
        return "[%d]" % shape[1]
    else:
        return ""


def dim_str_from_keras_4d_shape_output_noen(shape, layer_detail):
    '''
    noen対応のパデング付、出力用領域を確保する。
    :param shape:出力の次元情報
    :param layer_detail:出力レイヤ
    :return: 文字列
    '''

    if len(shape) == 4:
        padding = neon_padding_list[shape[1] % 4]
        return "[%d][%d+NEON_VTR*3][NEON_HTR+%d+%s]" % (shape[3], shape[2], shape[1], padding)
    elif len(shape) == 3:
        print("waring no support output ")
        return "[%d][%d]" % (shape[2], shape[1])
    elif len(shape) == 2:
        print("waring no support output ")
        return "[%d]" % shape[1]
    else:
        print("waring no support output ")
        return ""


def dim_str_from_keras_4d_shape_bias(shape):
    assert(len(shape)==4)
    if (shape[3] is None) and (shape[2] is None) and (shape[1] is None):
        return "[%d]" % shape[0]
    elif (shape[3] is None) and (shape[2] is None):
        return "[%d]" % shape[1]
    elif shape[3] is None:
        return "[%d]" % shape[2]
    else:
        return "[%d]" % shape[3]


def add_space(s):
    """
    引数の文字列が空文字列でなかったら、末尾にスペースを追加した文字列を返す。
    引数が空文字列の場合は、空文字列を返す。
    :param s: str
    :return: str
    """
    if s is None:
        return  ''
    else:
        return s + ' '

def conv_type_cqt_to_c(cqt_type):
    """
    コキュートスの型情報から、Ｃの型情報（NN_DTYPE 文字列）を返す。
    :param cqt_type:
    :return:  str
    """
    dic = {CQT_Dtype.INT8: 'CQT_INT8', CQT_Dtype.UINT8: 'CQT_UINT8',
           CQT_Dtype.FLOAT32: 'CQT_FLOAT32', CQT_Dtype.NONE: 'CQT_DTYPE_NONE',
           CQT_Dtype.FIX16: 'CQT_FIX16', CQT_Dtype.FIX8: 'CQT_FIX8',
           CQT_Dtype.FLOAT16: 'CQT_FLOAT16'
           }
    return dic[cqt_type]


import os
import shutil
import datetime
from compiler.compiler import CQT_Dtype
from cocytus_net.C.function_generator import FunctionGenerator

neon_padding_list = ('NEON_HPADDING_0', 'NEON_HPADDING_3', 'NEON_HPADDING_2', 'NEON_HPADDING_1',)


class CGenerator:
    def __init__(self, compiler):
        self.compiler = compiler
        self.config = self.compiler.config

    def generate(self):
        """
        Cソースファイルを作成する
        """
        # ディレクトリの作成
        target_dir = self.config['Cocyuts']['output_dir']
        create_c_dir(target_dir)

        # ヘッダーファイルの作成、コピー
        template_dir = self.config['Cocyuts']['c_lib_dir']

        self.generate_hedarfiles(target_dir, template_dir)

        # Cソースの作成
        target_dir = self.config['Cocyuts']['output_dir']
        self.generate_cqt_gen(target_dir, template_dir)

        target_dir = self.config['Cocyuts']['output_dir']
        self.generate_cqt_debug(target_dir, template_dir)

        # ライブラリの作成
        self.generate_cqt_lib(target_dir, template_dir)



    def generate_hedarfiles(self, target_dir, template_dir):
        """
        template_dirから、target_dirへヘッダファイルをコピーする。
        :param target_dir: str
        :param template_dir: str
        :return:
        """
        headers = ['cqt.h', 'cqt_type.h', 'cqt_keras.h', 'numpy.h', 'cqt_net.h']

        if self.compiler.is_neon_enable():
            headers.append('SSE2NEON.h')

        for h in headers:
            shutil.copy(os.path.join(template_dir, h),
                        os.path.join(target_dir, 'inc'))

    def generate_cqt_gen(self, target_dir, template_dir):
        """
        target_dirで指定されたディレクトリ/cqt_gen以下にcqt_gen.hとcqt_gen.cのファイルを作成する。
        :param target_dir: str
        :return:
        """
        cqt_gen_h_path = os.path.join(target_dir, 'cqt_gen', 'cqt_gen.h')
        print("making %s" % cqt_gen_h_path)
        cqt_gen_h = CqtGenH(cqt_gen_h_path, self.compiler)
        cqt_gen_h.generate()

        cqt_gen_c_path = os.path.join(target_dir, 'cqt_gen', 'cqt_gen.c')
        print("making %s" % cqt_gen_c_path)
        cqt_gen_c = CqtGenC(cqt_gen_c_path, self.compiler)
        cqt_gen_c.generate()

        files = ['numpy.c', 'cqt_lib.c']
        for f in files:
            shutil.copy(os.path.join(template_dir, f),
                        os.path.join(target_dir, 'cqt_lib'))

    def generate_cqt_debug(self, target_dir, template_dir):
        """
        target_dirで指定されたディレクトリ/cqt_gen以下にcqt_debug.hとcqt_debug.cのファイルを作成する。
        :param target_dir: str
        :return:
        """
        cqt_gen_h_path = os.path.join(target_dir, 'cqt_gen', 'cqt_debug.h')
        print("making %s" % cqt_gen_h_path)
        cqt_gen_h = CqtDebugH(cqt_gen_h_path, self.compiler)
        cqt_gen_h.generate()

        cqt_gen_c_path = os.path.join(target_dir, 'cqt_gen', 'cqt_debug.c')
        print("making %s" % cqt_gen_c_path)
        cqt_gen_c = CqtDebugC(cqt_gen_c_path, self.compiler)
        cqt_gen_c.generate()

    def generate_cqt_lib(self, target_dir, template_dir):
        """
        ## Ｃライブラリ(cqt_lib)
        ### cqt_lib.h
        ### cqt_lib.c
        ### numpy.c
        """
        cqt_lib_h_path = os.path.join(target_dir, 'inc', 'cqt_lib.h')
        print("making %s" % cqt_lib_h_path)
        cqt_lib_h = CqtLibH(cqt_lib_h_path, self.compiler)
        cqt_lib_h.generate()

        fg = FunctionGenerator(self.compiler, self.config, target_dir, template_dir)
        fg.generate()


class CFile:
    def __init__(self, file, compier):
        self.file = file
        self.fp = open(file, 'w')
        self.compiler = compier
        self.pd_dic = {'valid': 'PD_VALID', 'same': 'PD_SAME'}
        self.df_dic = {'channels_last' : 'DF_CHANNELS_LAST', 'channels_first': 'DF_CHANNELS_FIRST'}
        self.act_dic = {'linear': 'ACT_LINEAR', 'softmax': 'ACT_SOFTMAX', 'elu': 'ACT_ELU', 'softplus': 'ACT_SOFTPLUS',
                        'softsign': 'ACT_SOFTSIGN', 'relu': 'ACT_RELU', 'tanh': 'ACT_TANH', 'sigmoid': 'ACT_SIGMOID',
                        'hard_sigmoid': 'ACT_HARD_SIGMOID'
                        }
        self.bool_dic = {True: 'true', False: 'false'}

    def __del__(self):
        self.fp.close()

    def wr(self, s):
        """
        ファイルに文字列を書き込む
        :param s: str
        :return:
        """
        self.fp.write(s)

    def wr_file_header(self):
        """
        自走生成されるファイルにつけられるヘッダー。
        :return:
        """
        # TODO コピーライトやコキュートスのバージョンを追加したい
        today = datetime.date.today()
        self.fp.write("//----------------------------------------------------------------------------------------------------\n")
        self.fp.write("// This file is automatically generated.\n")
        self.fp.write("// %s\n" % today.strftime('%Y/%m/%d %H:%M:%S'))
        self.fp.write("//----------------------------------------------------------------------------------------------------\n")

    def wr_include(self, file, stdlib=False):
        """
        include分を挿入する。
        stdlib = True　の時は #include <file>　形式を、stdlib = Falseの時は
        #include "file" 形式で書き込む。

        :param file: str
        :param stdlib: bool
        :return:
        """
        if stdlib:
            self.wr('#include <%s>\n' % file)
        else:
            self.wr('#include "%s"\n' % file)

    def cr(self):
        """
        改行の挿入
        :return:
        """
        self.wr('\n')

    def write_cqt_network(self, scope=None):
        """
        コキュートスニューラルネットの定義を行う。
        scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。
        """
        name = self.compiler.get_model_name()
        scope_s = add_space(scope)

        self.wr('// cocytus network\n')
        self.wr('%sCQT_NET %s;\n' % (scope_s, name))

    def wr_layer_definition(self, scope=None):
        """
        レイヤー変数の定義を行う。
        scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。
        :return:
        """
        self.wr("//Layers\n")
        layers = self.compiler.get_layers()
        for l in layers:
            name = l.name
            cqt_layer = self.compiler.get_cqt_layer_obj(name)
            class_name = cqt_layer.keras_layer_type
            scope_s = add_space(scope)
            s = "%sLY_%s %s;\n" % (scope_s, class_name, name)
            self.wr(s)

    def wr_weight_definition(self, scope=None):
        """
        weight変数の定義を行う。
        scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。
        :return:
        """
        self.wr("//weights\n")
        layers = self.compiler.get_layers()
        for l in layers:
            name = l.name
            cqt_layer = self.compiler.get_cqt_layer_obj(name)
            class_name = cqt_layer.keras_layer_type
            if class_name == 'Conv2D':
                layer_detal = self.compiler.get_cqt_layer_obj(name)
                w_shape = layer_detal.get_Wshape()
                w_name, w_nph_name, b_name, b_nph_name = layer_detal.get_conv2d_weight_variable_name()
                w_dim_s = dim_str_from_keras_4d_shape(w_shape)
                b_dim_s = dim_str_from_keras_4d_shape_bias(w_shape)
                scope_s = add_space(scope)

                w_type = layer_detal.get_weight_type_str()

                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, w_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, b_nph_name))
                self.wr("%s%s %s%s;\n" % (scope_s, w_type, w_name, w_dim_s))
                self.wr("%s%s %s%s;\n" % (scope_s, w_type, b_name, b_dim_s))
            elif class_name == 'Dense':
                layer_detal = self.compiler.get_cqt_layer_obj(name)
                w_shape = layer_detal.get_Wshape()
                input_dim = w_shape[0]
                output_dim = w_shape[1]
                scope_s = add_space(scope)
                w_type = layer_detal.get_weight_type_str()

                w_name, w_nph_name, b_name, b_nph_name = layer_detal.get_conv2d_weight_variable_name()

                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, w_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, b_nph_name))
                self.wr("%s%s %s[%d][%d];\n" % (scope_s, w_type, w_name, output_dim, input_dim))
                self.wr("%s%s %s[%s];\n" % (scope_s, w_type, b_name, output_dim))

            elif class_name == 'BatchNormalization':
                layer_detal = self.compiler.get_cqt_layer_obj(name)
                b_dim, gm_dim, mm_dim, mv_dim = layer_detal.get_Wshape()

                beta_name, beta_nph_name, gamma_name, gamma_nph_name, mm_name, mm_nph_name, mv_name, mv_nph_name = layer_detal.get_batchnormalization_weight_variable_name()
                w_type = layer_detal.get_weight_type_str()

                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, beta_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, gamma_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, mm_nph_name))
                self.wr('%sNUMPY_HEADER %s;\n' % (scope_s, mv_nph_name))

                self.wr("%s%s %s[%d];\n" % (scope_s, w_type, beta_name, b_dim))
                self.wr("%s%s %s[%d];\n" % (scope_s, w_type, gamma_name, gm_dim))
                self.wr("%s%s %s[%d];\n" % (scope_s, w_type, mm_name, mm_dim))
                self.wr("%s%s %s[%d];\n" % (scope_s, w_type, mv_name, mv_dim))


    def wr_output_definition(self, scope=None, conv_pointer=False):
        """
        output変数の定義を行う。
        scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。
        conv_pointerがTrueの時は、pointerで宣言する。
        :return:
        """
        self.wr("//outputs\n")
        channel_last = self.compiler.is_output_channel_last()

        layers = self.compiler.get_layers()
        for l in layers:
            name = l.name
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type
            o_shape = layer_detal.get_output_shape()


            o_name = layer_detal.get_output_variable_name()
            o_type = layer_detal.get_output_type_str()
            scope_s = add_space(scope)

            if not self.compiler.is_neon_enable():
                # neon非対応の処理
                dim_s = dim_str_from_keras_4d_shape_output(o_shape, cl=channel_last)
                if conv_pointer:
                    # pointerで宣言
                    self.wr('%s%s *%s; //%s%s\n' % (scope_s, o_type, o_name, o_name, dim_s))

                else:
                    self.wr('%s%s %s%s;\n' % (scope_s, o_type, o_name, dim_s))
            else:
                # neon 対応時に外枠を確保する。
                # 横のサイズが4の倍数で無い時はパディングを入れる。
                dim_s = dim_str_from_keras_4d_shape_output_noen(o_shape, layer_detal)

                self.wr('%s%s %s%s;\n' % (scope_s, o_type, o_name, dim_s))



    def wr_assign(self, variable_name, value, tab=1):
        """
        Cの代入分を書き出す。tabでタブの個数を指定できる。
        :param variable_name: str
        :param value: str
        :param tab: int
        :return:
        """
        tabs = '\t' * tab
        if isinstance(value, list) or isinstance(value, tuple):
            for i, v in enumerate(value):
                self.wr('%s%s[%d] = %s;\n' % (tabs, variable_name, i, str(v)))
        else:
            if str(value) == 'True':
                value = 'true'
            self.wr('%s%s = %s;\n' % (tabs, variable_name, str(value)))

    def get_config(self):
        """
        Keras Modelのコンフィグ情報を返す。
        :return:
        """
        return self.compiler.model.get_config()


class CqtGenH(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('stdio.h', stdlib=True)
        self.wr_include('string.h', stdlib=True)
        self.wr_include('assert.h', stdlib=True)
        self.wr_include('cqt.h')
        self.wr_include('cqt_net.h')
        self.cr()

        if self.compiler.is_output_channel_last():
            self.wr('#define CQT_CHANNEL_LAST\n')

        self.cr()

        self.wr('CQT_NET* cqt_init(void);\n')
        self.wr('int cqt_load_weight_from_files(CQT_NET* np, const char *path);\n')
        self.wr('int cqt_run(CQT_NET* np, void *dp);\n')
        self.wr('void cqt_close(CQT_NET* np);\n')

        self.cr()
        self.wr('extern int cqt_process;\n')
        self.cr()

        self.write_cqt_network(scope='extern')
        self.cr()

        self.wr_layer_definition(scope='extern')
        self.cr()

        self.wr_weight_definition(scope='extern')
        self.cr()

        self.wr_output_definition(scope='extern', conv_pointer=self.compiler.is_target_sdsoc())
        self.cr()


        self.fp.write('\n')


class CqtGenC(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('cqt_gen.h')
        self.wr_include('cqt_lib.h')
        self.cr()

        self.wr('int cqt_process;\n')
        self.cr()

        self.write_cqt_network()
        self.cr()

        self.wr_layer_definition()
        self.cr()

        self.wr_weight_definition()
        self.cr()

        self.wr_output_definition(conv_pointer=self.compiler.is_target_sdsoc())
        self.cr()

        self.write_cqt_init()
        self.cr()
        self.write_cqt_load_weight_from_files()
        self.cr()
        self.write_cqt_run()
        self.cr()
        self.write_cqt_close()
        self.cr()

    def write_cqt_init(self):
        """
        cqt_init関数を書き出す。
        :return:
        """
        self.wr('CQT_NET* cqt_init(void) {\n')
        model_config = self.get_config()

        layers = self.compiler.get_layers()
        layer_num = len(layers)
        cqt_net_name = self.compiler.get_model_name()
        self.wr('\t%s.layernum = %d;\n' % (cqt_net_name, layer_num))
        self.wr('\tcqt_process = 0;\n')
        self.cr()

        for (i, l) in enumerate(layers):
            name = l.name
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            self.wr('\tstrcpy(%s.layer[%d].name, "%s");\n' % (cqt_net_name, i, name))
            self.wr('\t%s.layer[%d].type = LT_%s;\n' % (cqt_net_name, i, class_name))

            # レイヤー毎の処理
            if class_name == 'InputLayer' or class_name == 'Flatten':
                # 何もしない
                pass
            elif class_name == 'Conv2D':
                self.write_conv2d_init(l)
            elif class_name == 'MaxPooling2D':
                self.write_maxpooling2d(l)
            elif class_name == 'Dense':
                self.write_dense(l)
            elif class_name == 'BatchNormalization':
                self.write_batchnormalization(l)
            elif class_name == 'LeakyReLU':
                self.write_leakyrelu(l)
            else:
                raise ValueError("Error layer %s tpye is not supported" % class_name)

            # 共通の処理
            for j in range(4):
                if j < len(layer_detal.input_dtypes):
                    c_type = conv_type_cqt_to_c(layer_detal.input_dtypes[j])
                    self.wr('\t%s.layer[%d].input_dtypes[%d] = %s;\n' % (cqt_net_name, i, j, c_type))
                else:
                    self.wr('\t%s.layer[%d].input_dtypes[%d] = CQT_DTYPE_NONE;\n' % (cqt_net_name, i, j))

            for j in range(4):
                    if j < len(layer_detal.weight_dtypes):
                        c_type = conv_type_cqt_to_c(layer_detal.weight_dtypes[j])
                        self.wr('\t%s.layer[%d].weight_dtypes[%d] = %s;\n' % (cqt_net_name, i, j, c_type))
                    else:
                        self.wr('\t%s.layer[%d].weight_dtypes[%d] = CQT_DTYPE_NONE;\n' % (cqt_net_name, i, j))

            for j in range(4):
                if j < len(layer_detal.output_dtypes):
                    c_type = conv_type_cqt_to_c(layer_detal.output_dtypes[j])
                    self.wr('\t%s.layer[%d].output_dtypes[%d] = %s;\n' % (cqt_net_name, i, j, c_type))
                else:
                    self.wr('\t%s.layer[%d].output_dtypes[%d] = CQT_DTYPE_NONE;\n' % (cqt_net_name, i, j))

            i_shape = [0 if x is None else x for x in layer_detal.get_input_shape()]
            if len(i_shape) == 4:
                self.wr('\t%s.layer[%d].cqt_input_shape[0] = %d;\n' % (cqt_net_name, i, i_shape[0]))
                self.wr('\t%s.layer[%d].cqt_input_shape[1] = %d;\n' % (cqt_net_name, i, i_shape[1]))
                self.wr('\t%s.layer[%d].cqt_input_shape[2] = %d;\n' % (cqt_net_name, i, i_shape[2]))
                self.wr('\t%s.layer[%d].cqt_input_shape[3] = %d;\n' % (cqt_net_name, i, i_shape[3]))
            elif len(o_shape) == 3:
                self.wr('\t%s.layer[%d].cqt_input_shape[0] = %d;\n' % (cqt_net_name, i, i_shape[0]))
                self.wr('\t%s.layer[%d].cqt_input_shape[1] = %d;\n' % (cqt_net_name, i, i_shape[1]))
                self.wr('\t%s.layer[%d].cqt_input_shape[2] = %d;\n' % (cqt_net_name, i, i_shape[2]))
                self.wr('\t%s.layer[%d].cqt_input_shape[3] = 0;\n' % (cqt_net_name, i))
            elif len(o_shape) == 2:
                self.wr('\t%s.layer[%d].cqt_input_shape[0] = %d;\n' % (cqt_net_name, i, i_shape[0]))
                self.wr('\t%s.layer[%d].cqt_input_shape[1] = %d;\n' % (cqt_net_name, i, i_shape[1]))
                self.wr('\t%s.layer[%d].cqt_input_shape[2] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_input_shape[3] = 0;\n' % (cqt_net_name, i))
            elif len(o_shape) == 1:
                self.wr('\t%s.layer[%d].cqt_input_shape[0] = %d;\n' % (cqt_net_name, i, i_shape[0]))
                self.wr('\t%s.layer[%d].cqt_input_shape[1] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_input_shape[2] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_input_shape[3] = 0;\n' % (cqt_net_name, i))
            else:
                raise ValueError("ERROR unsupported shape %s %s", (name, str(o_shape)))

            # Noneを0に置き換え
            o_shape = [0 if x is None else x for x in layer_detal.get_output_shape()]
            if len(o_shape) == 4:
                self.wr('\t%s.layer[%d].cqt_output_shape[0] = %d;\n' % (cqt_net_name, i, o_shape[0]))
                self.wr('\t%s.layer[%d].cqt_output_shape[1] = %d;\n' % (cqt_net_name, i, o_shape[1]))
                self.wr('\t%s.layer[%d].cqt_output_shape[2] = %d;\n' % (cqt_net_name, i, o_shape[2]))
                self.wr('\t%s.layer[%d].cqt_output_shape[3] = %d;\n' % (cqt_net_name, i, o_shape[3]))
                out_size = o_shape[3] * o_shape[2] * o_shape[1]
                out_size_s = '%d * %d * %d' % (o_shape[3], o_shape[2], o_shape[1])
            elif len(o_shape) == 3:
                self.wr('\t%s.layer[%d].cqt_output_shape[0] = %d;\n' % (cqt_net_name, i, o_shape[0]))
                self.wr('\t%s.layer[%d].cqt_output_shape[1] = %d;\n' % (cqt_net_name, i, o_shape[1]))
                self.wr('\t%s.layer[%d].cqt_output_shape[2] = %d;\n' % (cqt_net_name, i, o_shape[2]))
                self.wr('\t%s.layer[%d].cqt_output_shape[3] = 0;\n' % (cqt_net_name, i))
                out_size = o_shape[2] * o_shape[1]
                out_size_s = '%d * %d' % (o_shape[2], o_shape[1])
            elif len(o_shape) == 2:
                self.wr('\t%s.layer[%d].cqt_output_shape[0] = %d;\n' % (cqt_net_name, i, o_shape[0]))
                self.wr('\t%s.layer[%d].cqt_output_shape[1] = %d;\n' % (cqt_net_name, i, o_shape[1]))
                self.wr('\t%s.layer[%d].cqt_output_shape[2] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_output_shape[3] = 0;\n' % (cqt_net_name, i))
                out_size = o_shape[1]
                out_size_s = '%d' % o_shape[1]
            elif len(o_shape) == 1:
                self.wr('\t%s.layer[%d].cqt_output_shape[0] = %d;\n' % (cqt_net_name, i, o_shape[0]))
                self.wr('\t%s.layer[%d].cqt_output_shape[1] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_output_shape[2] = 0;\n' % (cqt_net_name, i))
                self.wr('\t%s.layer[%d].cqt_output_shape[3] = 0;\n' % (cqt_net_name, i))
                out_size = o_shape[0]
                out_size_s = '%d' % o_shape[0]
            else:
                raise ValueError("ERROR unsupported shape %s %s", (name, str(o_shape)))

            input_q = layer_detal.input_q
            weight_q = layer_detal.weight_q
            output_q = layer_detal.output_q

            self.wr('\t%s.layer[%d].input_q = %d;\n' % (cqt_net_name, i, input_q))
            self.wr('\t%s.layer[%d].weight_q = %d;\n' % (cqt_net_name, i, weight_q))
            self.wr('\t%s.layer[%d].output_q = %d;\n' % (cqt_net_name, i, output_q))
            self.wr('\t%s.layer[%d].overflow_cnt = 0;\n' % (cqt_net_name, i))

            if self.compiler.is_neon_enable():
                padi = neon_padding_list[i_shape[1] % 4]
                self.wr('\t%s.layer[%d].neon_padding_hi = %s;\n' % (cqt_net_name, i, padi))
                pado = neon_padding_list[o_shape[1] % 4]
                self.wr('\t%s.layer[%d].neon_padding_ho = %s;\n' % (cqt_net_name, i, pado))

            o_name = layer_detal.get_output_variable_name()
            self.wr('\t%s.layer[%d].param_p = &%s;\n' % (cqt_net_name, i, name))
            self.wr('\t%s.layer[%d].data_p = &%s;\n' % (cqt_net_name, i, o_name))

            if self.compiler.is_target_sdsoc():
                # sds_alloc
                if layer_detal.output_dtypes[0] == CQT_Dtype.FLOAT32:
                    pass
                elif  layer_detal.output_dtypes[0] == CQT_Dtype.FLOAT16:
                    pass
                else:
                    print('ERROR unsuported dtype %s', layer_detal.output_dtypes[0])
                    sys.exit()

                o_shape = layer_detal.get_output_shape()
                o_type = layer_detal.get_output_type_str()

                self.cr()
                self.wr('\t%s = (%s *)sds_alloc(%s * sizeof(%s));\n' % (o_name, o_type, out_size_s, o_type))
                self.wr('\tif (%s == NULL) {\n' % o_name)
                self.wr('\t\treturn NULL;\n')
                self.wr('\t}\n')
                self.cr();


            self.cr()

        self.wr('\treturn &%s;\n' % cqt_net_name)
        self.wr('}\n')

    def write_conv2d_init(self, l):
        """
        LY_Conv2dの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        class_name = layer_detal.keras_layer_type
        w_name, w_nph_name, b_name, b_nph_name = layer_detal.get_conv2d_weight_variable_name()

        self.wr_assign("%s.filters" % name, config['filters'])
        self.wr_assign("%s.kernel_size" % name, config['kernel_size'])
        self.wr_assign("%s.strides" % name, config['strides'])
        self.wr_assign("%s.padding" % name, self.pd_dic[config['padding']])
        self.wr_assign("%s.data_format" % name, self.df_dic[config['data_format']])
        self.wr_assign("%s.dilation_rate" % name, config['dilation_rate'])
        self.wr_assign("%s.activation" % name, self.act_dic[config['activation']])
        self.wr_assign("%s.use_bias" % name, self.bool_dic[config['use_bias']])
        self.wr_assign("%s.weight_np_header_p" % name, '&'+ w_nph_name)
        self.wr_assign("%s.weight_p" % name, '&'+ w_name)
        self.wr_assign("%s.bias_np_header_p" % name, '&'+ b_nph_name)
        self.wr_assign("%s.bias_p" % name, '&'+ b_name)


    def write_maxpooling2d(self, l):
        """
        LY_MaxPooling2Dの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        class_name = layer_detal.keras_layer_type

        self.wr_assign("%s.strides" % name, config['strides'])
        self.wr_assign("%s.padding" % name, self.pd_dic[config['padding']])
        self.wr_assign("%s.data_format" % name, self.df_dic[config['data_format']])
        self.wr_assign("%s.pool_size" % name, config['pool_size'])

    def write_dense(self, l):
        """
        LY_Denseの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        class_name = layer_detal.keras_layer_type
        w_name, w_nph_name, b_name, b_nph_name = layer_detal.get_conv2d_weight_variable_name()

        self.wr_assign("%s.units" % name, config['units'])
        self.wr_assign("%s.activation" % name, self.act_dic[config['activation']])
        self.wr_assign("%s.use_bias" % name, self.bool_dic[config['use_bias']])
        self.wr_assign("%s.weight_np_header_p" % name, '&'+ w_nph_name)
        self.wr_assign("%s.weight_p" % name, '&'+ w_name)
        self.wr_assign("%s.bias_np_header_p" % name, '&'+ b_nph_name)
        self.wr_assign("%s.bias_p" % name, '&'+ b_name)

    def write_batchnormalization(self, l):
        """
        BatchNormalizationの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        self.wr_assign("%s.axis" % name, config['axis'])
        self.wr_assign("%s.momentum" % name, config['momentum'])
        self.wr_assign("%s.epsilon" % name, config['epsilon'])
        self.wr_assign("%s.center" % name, config['center'])
        self.wr_assign("%s.scale" % name, config['scale'])
        beta_name, beta_nph_name, gamma_name, gamma_nph_name, mm_name, mm_nph_name, mv_name, mv_nph_name = layer_detal.get_batchnormalization_weight_variable_name()
        self.wr_assign("%s.beta_np_header_p" % name, '&' + beta_nph_name)
        self.wr_assign("%s.beta_p" % name, '&' + beta_name)
        self.wr_assign("%s.gamma_np_header_p" % name, '&' + gamma_nph_name)
        self.wr_assign("%s.gamma_p" % name, '&' + gamma_name)
        self.wr_assign("%s.moving_mean_np_header_p" % name, '&' + mm_nph_name)
        self.wr_assign("%s.moving_mean_p" % name, '&' + mm_name)
        self.wr_assign("%s.moving_variance_np_header_p" % name, '&' + mv_nph_name)
        self.wr_assign("%s.moving_variance_p" % name, '&' + mv_name)

    def write_leakyrelu(self, l):
        """
        LeakyReLUの初期化を書き出す。
        :param l:
        :return:
        """
        name = l.name
        config = l.get_config()
        layer_detal = self.compiler.get_cqt_layer_obj(name)
        self.wr_assign("%s.alpha" % name, config['alpha'])


    def write_cqt_load_weight_from_files(self):
        self.wr('int cqt_load_weight_from_files(CQT_NET* np, const char *path) {\n')
        self.wr('\tchar buf[CQT_MAX_PATH];\n')
        self.wr('\tsize_t path_len;\n')
        self.wr('\tsize_t fname_w_len;\n')
        self.wr('\tsize_t fname_b_len;\n')
        self.wr('\tint ret;\n')
        self.wr('\n')

        #model_config = self.get_config()
        layers = self.compiler.get_layers()

        for i, l in enumerate(layers):
            name = l.name
            config = l.get_config()
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            if class_name in ['Conv2D', 'Dense']:
                if self.compiler.weight_filename_mode == 0:
                    fname_w = name + '_W_1_z.npy'
                    fname_b = name + '_b_1_z.npy'
                elif self.compiler.weight_filename_mode == 1:
                    fname_w = name + '_kernel_z.npy'
                    fname_b = name + '_bias_z.npy'
                else:
                    raise ValueError('Unkown weight_filename_mode %d' % self.compiler.weight_filename_mode)

                [variable_name_w, variable_name_w_header, variable_name_b,
                 variable_name_b_header] = layer_detal.get_conv2d_weight_variable_name()

                if class_name == 'Conv2D':
                    prev_dim = layer_detal.get_prev_layer_output_dimension(i)
                    kernel_size = config['kernel_size']
                    w_size = kernel_size[0] * kernel_size[1] * config['filters'] * prev_dim
                    b_size = config['filters']
                else:
                    # dense
                    prev_dim = layer_detal.get_prev_layer_output_dimension(i)
                    i_shape = layer_detal.get_input_shape()
                    o_shape = layer_detal.get_output_shape()
                    w_size = i_shape[-1] * o_shape[-1]
                    b_size = o_shape[-1]

                self.wr('// %s\n' % name)
                self.wr('\tpath_len = strlen(path);\n')
                self.wr('\tfname_w_len = strlen("%s");\n' % fname_w)
                self.wr('\tassert(path_len+fname_w_len<CQT_MAX_PATH);\n')
                self.wr('\tstrcpy(buf, path);\n')
                self.wr('\tstrcat(buf, "%s");\n' % fname_w)
                self.wr('\tret = load_from_numpy(%s, buf, %d, &%s);\n' % (variable_name_w, w_size, variable_name_w_header))
                self.wr('\tif(ret != CQT_RET_OK){\n')
                self.wr('\t\treturn ret;\n')
                self.wr('\t}\n')
                self.cr()

                if l.use_bias:
                    self.wr('\tfname_b_len = strlen("%s");\n' % fname_b)
                    self.wr('\tassert(path_len+fname_b_len<CQT_MAX_PATH);\n')
                    self.wr('\tstrcpy(buf, path);\n')
                    self.wr('\tstrcat(buf, "%s");\n' % fname_b)
                    self.wr('\tret = load_from_numpy(%s, buf, %d, &%s);\n' % (variable_name_b, b_size, variable_name_b_header))
                    self.wr('\tif(ret != CQT_RET_OK){\n')
                    self.wr('\t\treturn ret;\n')
                    self.wr('\t}\n')

                self.cr()
            elif class_name == 'BatchNormalization':
                # 重みのファイル名
                fname_beta = name + '_beta_z.npy'
                fname_gamma = name + '_gamma_z.npy'
                fname_mm = name + '_moving_mean_z.npy'
                fname_mv = name + '_moving_variance_z.npy'
                # 変数名
                beta_name, beta_nph_name, gamma_name, gamma_nph_name, mm_name, mm_nph_name, mv_name, mv_nph_name = layer_detal.get_batchnormalization_weight_variable_name()

                fnames = [fname_beta, fname_gamma, fname_mm, fname_mv]
                variable_names = [beta_name, gamma_name, mm_name, mv_name]
                nph_names = [beta_nph_name, gamma_nph_name, mm_nph_name, mv_nph_name]
                dims = layer_detal.get_Wshape()

                self.wr('// %s\n' % name)
                for fn, vn, npn, dim in zip(fnames, variable_names, nph_names, dims):
                    self.wr('\tfname_b_len = strlen("%s");\n' % fn)
                    self.wr('\tassert(path_len+fname_b_len<CQT_MAX_PATH);\n')
                    self.wr('\tstrcpy(buf, path);\n')
                    self.wr('\tstrcat(buf, "%s");\n' % fn)
                    self.wr('\tret = load_from_numpy(%s, buf, %d, &%s);\n' % (vn, dim, npn))
                    self.wr('\tif(ret != CQT_RET_OK){\n')
                    self.wr('\t\treturn ret;\n')
                    self.wr('\t}\n')
                    self.cr()

        self.wr('\treturn CQT_RET_OK;\n')
        self.wr('}\n')

    def write_cqt_run(self):
        self.wr('int cqt_run(CQT_NET* np, void *dp) {\n')
        self.wr('\tint ret;\n')
        self.cr()

        model_config = self.get_config()
        cqt_net_name = self.compiler.get_model_name()
        layers = self.compiler.get_layers()


        for i, l in enumerate(layers):
            name = l.name
            config = l.get_config()
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            inp = self.compiler.get_prev_layer_output_name(i)
            outp = layer_detal.get_output_variable_name()

            func_name = layer_detal.make_func_name()

            # FPGA用に関数を分ける
            if class_name == 'Conv2D' and self.compiler.is_target_sdsoc():
                size = l.kernel_size
                if size == (3, 3):
                    func_name = 'CQT_' + name + "_3x3"

            self.wr('\t//%s\n' % name)
            self.wr('\tcqt_process = %d;\n' % i)
            self.wr("\tret = %s(&(%s.layer[%d]), %s, %s);\n" % (func_name, cqt_net_name, i, inp, outp))
            self.wr('\tif(ret != CQT_RET_OK){\n')
            self.wr('\t\treturn ret;\n')
            self.wr('\t}\n')
            self.wr('\n')

        self.wr('\treturn CQT_RET_OK;\n')
        self.wr('}\n')


    def write_cqt_close(self):
        self.wr('void cqt_close(CQT_NET* np) {\n')
        self.cr()

        if self.compiler.is_target_sdsoc():

            layers = self.compiler.get_layers()
            layer_num = len(layers)
            cqt_net_name = self.compiler.get_model_name()

            for (i, l) in enumerate(layers):
                name = l.name
                layer_detal = self.compiler.get_cqt_layer_obj(name)
                o_name = layer_detal.get_output_variable_name()
                self.wr('\tsds_free(%s);\n' % o_name)

        self.wr('}\n')


class CqtLibH(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('cqt.h')
        self.wr_include('cqt_net.h')
        self.cr()

        model_config = self.get_config()
        cqt_net_name = self.compiler.get_model_name()

        func_list = []
        layers = self.compiler.get_layers()

        for i, l in enumerate(layers):
            name = l.name
            config = l.get_config()
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            func_name = layer_detal.make_func_name()

            # FPGA用に関数を分ける
            if class_name == 'Conv2D' and self.compiler.is_target_sdsoc():
                size = l.kernel_size
                if size == (3, 3):
                    func_name = 'CQT_' + name + "_3x3"
                    
            if not func_name in func_list:
                self.wr('int %s(CQT_LAYER *lp, void *inp, void *outp);\n' % func_name)
                func_list.append(func_name)

        self.cr()


class CqtDebugH(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('stdio.h', stdlib=True)
        self.wr_include('stdlib.h', stdlib=True)
        self.wr_include('string.h', stdlib=True)
        self.wr_include('assert.h', stdlib=True)
        self.wr_include('cqt.h')
        self.wr_include('cqt_net.h')
        self.cr()

        self.wr('void cqt_layerdump(int l);\n')

        layers = self.compiler.get_layers()

        for i in range(len(layers)):
            self.wr('void cqt_layer%d_dump(void);\n' % i)

        self.fp.write('\n')


class CqtDebugC(CFile):
    def __init__(self, file, compiler):
        super().__init__(file, compiler)

    def __del__(self):
        super().__del__()

    def generate(self):
        self.wr_file_header()
        self.wr_include('cqt_debug.h')
        self.cr()

        self.wr_output_definition(scope='extern', conv_pointer=self.compiler.is_target_sdsoc())
        self.cr()
        self.wr('extern NUMPY_HEADER np;\n')
        self.cr()

        layers = self.compiler.get_layers()
        self.write_cqt_layerdump(len(layers))


        for i, l in enumerate(layers):
            name = l.name
            config = l.get_config()
            layer_detal = self.compiler.get_cqt_layer_obj(name)
            class_name = layer_detal.keras_layer_type

            odim = layer_detal.get_output_shape()
            output_variable_name = layer_detal.get_output_variable_name()

            if len(odim) == 4:
                size1 = odim[1]
                size2 = odim[2]
                num = odim[3]

                self.wr("void cqt_layer%d_dump(void)\n" % i)
                self.wr("{\n")
                self.wr("\tNUMPY_HEADER np_0 = np;\n")
                self.wr("\tint ret;\n")
                self.cr()
                if not self.compiler.is_neon_enable():
                    self.wr("\tnp_0.shape[0] = %d;\n" % num)
                    self.wr("\tnp_0.shape[1] = %d;\n" % size1)
                    self.wr("\tnp_0.shape[2] = %d;\n" % size2)
                    self.wr("\tnp_0.shape[3] = 0;\n")
                else:
                    # neon対応
                    padding = neon_padding_list[size2 % 4]
                    # 並びが怪しい
                    self.wr("\tnp_0.shape[0] = %d;\n" % num)
                    self.wr("\tnp_0.shape[1] = %d + NEON_VTR*3;\n" % size1)
                    self.wr("\tnp_0.shape[2] = NEON_HTR + %d + %s;\n" % (size2, padding))
                    self.wr("\tnp_0.shape[3] = 0;\n")


                self.cr()
                self.wr('\tret = save_to_numpy(%s, "output/l%02d.npy", & np_0);\n' % (output_variable_name, i))
                self.wr('\tif (ret != CQT_RET_OK) {\n')
                self.wr('\t\tprintf("ERROR in layer0_output %d\\n", ret);\n')
                self.wr('\t}\n')
            else:
                size1 = odim[1]
                self.wr("void cqt_layer%d_dump(void)\n" % i)
                self.wr("{\n")
                self.wr("\tNUMPY_HEADER np_0 = np;\n")
                self.wr("\tint ret;\n")
                self.cr()
                self.wr("\tnp_0.shape[0] = %d;\n" % size1)
                self.wr("\tnp_0.shape[1] = 0;\n")
                self.wr("\tnp_0.shape[2] = 0;\n")
                self.wr("\tnp_0.shape[3] = 0;\n")
                self.cr()
                self.wr('\tret = save_to_numpy((void *)%s, "output/l%02d_0.npy", &np_0);\n' % (output_variable_name, i))
                self.wr('\tif (ret != CQT_RET_OK) {\n')
                self.wr('\t\tprintf("ERROR in layer0_output %d\\n", ret);\n')
                self.wr('\t}\n')


            self.wr("}\n")

            self.cr()

        self.cr()

    def write_cqt_layerdump(self, num):
        self.wr('void cqt_layerdump(int l)\n')
        self.wr('{\n')
        self.wr('\tswitch(l) {\n')

        for i in range(num):
            self.wr('\tcase %d:\n' % i)
            self.wr('\t\tcqt_layer%d_dump();\n' % i)
            self.wr('\t\tbreak;\n')
        self.wr('\tdefault:\n')
        self.wr('\t\tprintf("ERROR:invalid layer number %d\\n", l);\n')
        self.wr('\t\texit(1);\n')

        self.wr('\t}\n')

        self.wr('}\n')
        self.cr()




def create_c_dir(tdir):
    """
    tdir以下に以下のディレクトリを作成する。
    inc, cqt_gen, cqt_lib, weight
    :param tdir: str
    :return:
    """
    dirs = ['inc', 'cqt_gen', 'cqt_lib', 'weight', 'output']
    for d in dirs:
        path = os.path.join(tdir, d)
        if not os.path.isdir(path):
            os.makedirs(path)


def dim_str_from_keras_4d_shape(shape):
    assert(len(shape)==4)
    if shape[3] is None:
        return "[%d][%d][%d]" % (shape[2], shape[1], shape[0])
    else:
        return "[%d][%d][%d][%d]" % (shape[3], shape[2], shape[1], shape[0])


def dim_str_from_keras_4d_shape_output(shape, cl=False, neon=False):
    if len(shape) == 4:
        if cl:
            return "[%d][%d][%d]" % (shape[1], shape[2], shape[3])
        else:
            return "[%d][%d][%d]" % (shape[3], shape[2], shape[1])
    elif len(shape) == 3:
        return "[%d][%d]" % (shape[2], shape[1])
    elif len(shape) == 2:
        return "[%d]" % shape[1]
    else:
        return ""


def dim_str_from_keras_4d_shape_output_noen(shape, layer_detail):
    '''
    noen対応のパデング付、出力用領域を確保する。
    :param shape:出力の次元情報
    :param layer_detail:出力レイヤ
    :return: 文字列
    '''

    if len(shape) == 4:
        padding = neon_padding_list[shape[1] % 4]
        return "[%d][%d+NEON_VTR*3][NEON_HTR+%d+%s]" % (shape[3], shape[2], shape[1], padding)
    elif len(shape) == 3:
        print("waring no support output ")
        return "[%d][%d]" % (shape[2], shape[1])
    elif len(shape) == 2:
        print("waring no support output ")
        return "[%d]" % shape[1]
    else:
        print("waring no support output ")
        return ""


def dim_str_from_keras_4d_shape_bias(shape):
    assert(len(shape)==4)
    if (shape[3] is None) and (shape[2] is None) and (shape[1] is None):
        return "[%d]" % shape[0]
    elif (shape[3] is None) and (shape[2] is None):
        return "[%d]" % shape[1]
    elif shape[3] is None:
        return "[%d]" % shape[2]
    else:
        return "[%d]" % shape[3]


def add_space(s):
    """
    引数の文字列が空文字列でなかったら、末尾にスペースを追加した文字列を返す。
    引数が空文字列の場合は、空文字列を返す。
    :param s: str
    :return: str
    """
    if s is None:
        return  ''
    else:
        return s + ' '

def conv_type_cqt_to_c(cqt_type):
    """
    コキュートスの型情報から、Ｃの型情報（NN_DTYPE 文字列）を返す。
    :param cqt_type:
    :return:  str
    """
    dic = {CQT_Dtype.INT8: 'CQT_INT8', CQT_Dtype.UINT8: 'CQT_UINT8',
           CQT_Dtype.FLOAT32: 'CQT_FLOAT32', CQT_Dtype.NONE: 'CQT_DTYPE_NONE',
           CQT_Dtype.FIX16: 'CQT_FIX16', CQT_Dtype.FIX8: 'CQT_FIX8',
           CQT_Dtype.FLOAT16: 'CQT_FLOAT16'
           }
    return dic[cqt_type]


Cソースファイルを作成する # ディレクトリの作成 # ヘッダーファイルの作成、コピー # Cソースの作成 # ライブラリの作成 template_dirから、target_dirへヘッダファイルをコピーする。         :param target_dir: str         :param template_dir: str         :return: target_dirで指定されたディレクトリ/cqt_gen以下にcqt_gen.hとcqt_gen.cのファイルを作成する。         :param target_dir: str         :return: target_dirで指定されたディレクトリ/cqt_gen以下にcqt_debug.hとcqt_debug.cのファイルを作成する。         :param target_dir: str         :return: ## Ｃライブラリ(cqt_lib)         ### cqt_lib.h         ### cqt_lib.c         ### numpy.c ファイルに文字列を書き込む         :param s: str         :return: 自走生成されるファイルにつけられるヘッダー。         :return: # TODO コピーライトやコキュートスのバージョンを追加したい include分を挿入する。         stdlib = True　の時は #include <file>　形式を、stdlib = Falseの時は         #include "file" 形式で書き込む。          :param file: str         :param stdlib: bool         :return: 改行の挿入         :return: コキュートスニューラルネットの定義を行う。         scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。 レイヤー変数の定義を行う。         scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。         :return: weight変数の定義を行う。         scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。         :return: output変数の定義を行う。         scopeを変更する場合は、scope引数に"extern"もしくは"static"を指定する。         conv_pointerがTrueの時は、pointerで宣言する。         :return: # neon非対応の処理 # pointerで宣言 # neon 対応時に外枠を確保する。 # 横のサイズが4の倍数で無い時はパディングを入れる。 Cの代入分を書き出す。tabでタブの個数を指定できる。         :param variable_name: str         :param value: str         :param tab: int         :return: Keras Modelのコンフィグ情報を返す。         :return: cqt_init関数を書き出す。         :return: # レイヤー毎の処理 # 何もしない # 共通の処理 # Noneを0に置き換え # sds_alloc LY_Conv2dの初期化を書き出す。         :param l:         :return: LY_MaxPooling2Dの初期化を書き出す。         :param l:         :return: LY_Denseの初期化を書き出す。         :param l:         :return: BatchNormalizationの初期化を書き出す。         :param l:         :return: LeakyReLUの初期化を書き出す。         :param l:         :return: #model_config = self.get_config() # dense # 重みのファイル名 # 変数名 # FPGA用に関数を分ける # FPGA用に関数を分ける # neon対応 # 並びが怪しい tdir以下に以下のディレクトリを作成する。     inc, cqt_gen, cqt_lib, weight     :param tdir: str     :return: noen対応のパデング付、出力用領域を確保する。     :param shape:出力の次元情報     :param layer_detail:出力レイヤ     :return: 文字列 引数の文字列が空文字列でなかったら、末尾にスペースを追加した文字列を返す。     引数が空文字列の場合は、空文字列を返す。     :param s: str     :return: str コキュートスの型情報から、Ｃの型情報（NN_DTYPE 文字列）を返す。     :param cqt_type:     :return:  str