"""
  femagtools.dxfsl.converter
  ~~~~~~~~~~~~~~~~~~~~~~~~~~

  read a dxf file and create a plot or fsl file

 Authors: Ronald Tanner, Beat Holm
"""
import os
from femagtools.dxfsl.geom import Geometry, dxfshapes, femshapes
from femagtools.dxfsl.shape import Shape
from femagtools.dxfsl.fslrenderer import FslRenderer, agndst
from femagtools.dxfsl.plotrenderer import PlotRenderer
import logging
import logging.config
import numpy as np
import sys

logger = logging.getLogger(__name__)


def symmetry_search(machine,
                    plt,  # plotter
                    kind,
                    symtol=0.0,
                    sympart=0,
                    is_inner=False,
                    is_outer=False,
                    show_plots=True,
                    debug_mode=False,
                    rows=1,
                    cols=1,
                    num=1):
    logger.info("symmetry search for %s", kind)

    machine.clear_cut_lines()
    if show_plots and debug_mode:
        plt.render_elements(machine.geom, Shape,
                            neighbors=True, title=kind)

    if sympart > 0:
        if not machine.is_full():
            logger.error("force symmetry failed")
            sys.exit(1)
        machine_ok = machine.get_forced_symmetry(sympart)
        machine_ok.set_minmax_radius()
        machine_ok.create_auxiliary_lines()
        machine_ok.set_kind(kind)
        logger.info("*** End of symmetry search for %s ***", kind)
        return machine_ok

    if not machine.find_symmetry(symtol):
        logger.info(" - {}: no symmetry axis found".format(kind))
        if show_plots:
            plt.add_emptyplot(rows, cols, num, 'no symmetry axis')

        machine_mirror = machine.get_symmetry_mirror()
        machine_slice = machine
    else:
        logger.info(" - {}: symmetry axis found !!".format(kind))
        if show_plots:
            plt.render_elements(machine.geom, Shape,
                                title=kind+' (symmetrylines)',
                                draw_inside=True,
                                rows=rows, cols=cols, num=num, show=False)
        machine_slice = machine.get_symmetry_slice()
        if machine_slice is None:
            machine.kind = kind
            logger.info(" - no slice extracted ?!?")
            return machine

        machine_mirror = machine_slice.get_symmetry_mirror()

    if machine_mirror is None:
        logger.info(" - no mirror found")
        if not machine_slice.is_startangle_zero():
            machine_slice.rotate_to(0.0)
            machine_slice.set_alfa_and_corners()

        machine_ok = machine_slice
    else:
        if show_plots and debug_mode:
            plt.render_elements(machine_mirror.mirror_geom, Shape,
                                title='Mirror of '+kind,
                                rows=rows, cols=cols, num=num, show=True)

        logger.info(" - mirror found")
        machine_next_mirror = machine_mirror.get_symmetry_mirror()
        while machine_next_mirror is not None:
            logger.info(" - another mirror found")
            machine_mirror = machine_next_mirror
            machine_next_mirror = machine_mirror.get_symmetry_mirror()

        machine_ok = machine_mirror

    machine_ok.set_minmax_radius()
    # machine_ok.complete_hull(is_inner, is_outer)
    machine_ok.create_auxiliary_lines()
    machine_ok.set_kind(kind)

    logger.info("*** End of symmetry search for %s ***", kind)
    return machine_ok


def convert(dxfile,
            rtol=1e-03,
            atol=0.005,
            mindist=0.0,
            symtol=0.001,
            sympart=0,
            split=False,
            inner_name='inner',
            outer_name='outer',
            part=(),
            airgap=0.0,
            airgap2=0.0,
            da=0.0,
            dy=0.0,
            nodedist=1,
            view_only=False,
            view_korr=False,
            show_plots=False,
            show_areas=False,
            write_fsl=True,
            write_png=False,
            write_id=False,
            debug_mode=False):
    layers = ()
    conv = {}

    basename = os.path.basename(dxfile).split('.')[0]
    logger.info("start processing %s", basename)

    if part:
        if part[0] not in ('rotor', 'stator'):
            logger.error('FATAL: Parameter rotor or stator expected')
            return dict(error='unknown part {}'.format(part))
        if part[1] not in ('in', 'out'):
            logger.error('"{}" has to be defined in/out'.format(part[0]))
            return dict(error='unknown location {}'.format(part[1]))
    else:
        if da:
            logger.warn("distance airgap (da) ignored")
            da = 0.0
        if dy:
            logger.warn("distance yoke (dy) ignored")
            dy = 0.0

    try:
        if dxfile.split('.')[-1] == 'fem':
            basegeom = Geometry(femshapes(dxfile),
                                rtol=rtol,
                                atol=atol,
                                split=split)
        else:
            basegeom = Geometry(dxfshapes(dxfile,
                                          mindist=mindist,
                                          layers=layers),
                                rtol=rtol,
                                atol=atol,
                                split=split)
    except FileNotFoundError as ex:
        logger.error(ex)
        return dict()

    logger.info("total elements %s", len(basegeom.g.edges()))

    p = PlotRenderer()

    if view_only:
        logger.info("View only")
        if view_korr:
            logger.info("With Corrections")
            basegeom.search_all_overlapping_elements()
            basegeom.search_all_appendices()

        p.render_elements(basegeom, Shape,
                          neighbors=True,
                          png=write_png,
                          show=True)
        return dict()

    basegeom.search_all_overlapping_elements()

    machine_base = basegeom.get_machine()
    if show_plots:
        p.render_elements(basegeom, Shape,
                          title=os.path.basename(dxfile),
                          with_hull=False,
                          rows=3, cols=2, num=1, show=debug_mode)

    basegeom.search_all_appendices()

    if not machine_base.is_a_machine():
        logger.warn("it's Not a Machine!!")
        return dict(error='machine not detected')

    if not (machine_base.part > 0):
        # machine shape is unclear
        machine_base.set_center(0.0, 0.0)
        machine_base.set_radius(9999999)

    machine = machine_base

    if machine.part_of_circle() == 0:
        logger.warn("No arc segment found")
        return dict(error='no arc segment found')

    machine.clear_cut_lines()
    machine.move_to_middle()
    if show_plots and debug_mode:
        p.render_elements(machine.geom, Shape,
                          title='Areas',
                          neighbors=True,
                          with_corners=False, show=True)

    if machine.airgap(airgap, airgap2, symtol):
        p.render_elements(machine.geom, Shape,
                          title='Search for airgap failed',
                          with_corners=False, show=True)
        return dict(error='no airgap found')

    if show_plots:
        p.render_elements(basegeom, Shape, neighbors=True,
                          title='Original with nodes',
                          rows=3, cols=2, num=2, show=False)

    machine.repair_hull()
    machine.geom.delete_all_appendices()

    if machine.has_airgap():
        machine_inner = machine.copy(0.0, 2*np.pi, True, True)
        machine_inner = symmetry_search(machine_inner,
                                        p,  # plot
                                        inner_name,
                                        is_inner=True,
                                        symtol=symtol,
                                        show_plots=show_plots,
                                        rows=3,  # rows
                                        cols=2,  # columns
                                        num=3)   # start num
        machine_inner.set_inner()

        machine_outer = machine.copy(0.0, 2*np.pi, True, False)
        machine_outer = symmetry_search(machine_outer,
                                        p,  # plot
                                        outer_name,
                                        is_outer=True,
                                        symtol=symtol,
                                        show_plots=show_plots,
                                        rows=3,  # rows
                                        cols=2,  # columns
                                        num=4)   # start num

        machine_inner.sync_with_counterpart(machine_outer)

        machine_inner.search_subregions()
        machine_outer.search_subregions()

        if machine_inner.has_mirrored_windings():
            logger.info("undo mirrored windings of %s", inner_name)
            machine_inner = machine_inner.undo_mirror()
            machine_inner.sync_with_counterpart(machine_outer)
            machine_inner.search_subregions()
            machine_inner.create_mirror_lines_outside_windings()

        elif machine_outer.has_mirrored_windings():
            logger.info("undo mirrored windings of %s", outer_name)
            machine_outer = machine_outer.undo_mirror()
            machine_inner.sync_with_counterpart(machine_outer)
            machine_outer.search_subregions()
            machine_outer.create_mirror_lines_outside_windings()

        machine_inner.delete_tiny_elements(mindist)
        machine_outer.delete_tiny_elements(mindist)
        logger.info("END of work: %s", basename)

        if show_plots:
            p.render_elements(machine_inner.geom, Shape,
                              draw_inside=True, title=inner_name,
                              rows=3, cols=2, num=5, show=False,
                              # with_nodes=True,
                              # neighbors=True,
                              write_id=write_id,
                              fill_areas=True)

            p.render_elements(machine_outer.geom, Shape,
                              draw_inside=True, title=outer_name,
                              rows=3, cols=2, num=6, show=False,
                              # with_nodes=True,
                              # neighbors=True,
                              write_id=write_id,
                              fill_areas=True)
            if write_png:
                p.write_plot(basename)
            else:
                p.show_plot()

        if show_areas:
            p.render_elements(machine_inner.geom, Shape,
                              title=inner_name,
                              show=True,
                              draw_inside=True,
                              neighbors=True,
                              fill_areas=True)
            p.render_areas(machine_inner.geom,
                           title=inner_name,
                           with_nodes=True,
                           single_view=True)

            p.render_elements(machine_outer.geom, Shape,
                              title=outer_name,
                              show=True,
                              draw_inside=True,
                              neighbors=True,
                              fill_areas=True)
            p.render_areas(machine_outer.geom,
                           title=outer_name,
                           with_nodes=True,
                           single_view=True)

        if write_fsl:
            if machine_inner.is_full() or machine_outer.is_full():
                logger.warning("it's not possible to create fsl-file")
                return None

            fslrenderer = FslRenderer(basename)
            inner = fslrenderer.render(machine_inner, inner=True)
            outer = fslrenderer.render(machine_outer, outer=True)

            if machine_inner.geom.is_rotor():
                conv['fsl_magnet'] = inner
                conv['fsl_stator'] = outer
            else:
                conv['fsl_magnet'] = inner
                conv['fsl_rotor'] = outer

            params = create_femag_parameters(machine_inner,
                                             machine_outer,
                                             nodedist)

            conv.update(params)
            conv['fsl'] = fslrenderer.render_main(
                machine,
                machine_inner, machine_outer,
                inner, outer,
                params)
    else:
        # No airgap found. This must be an inner or outer part
        name = "No_Airgap"
        inner = False
        outer = False
        params = None

        if part:
            if part[1] == 'in':
                name = inner_name
                inner = True
            else:
                name = outer_name
                outer = True

        machine = symmetry_search(machine,
                                  p,  # plot
                                  name,
                                  symtol=symtol,
                                  sympart=sympart,
                                  show_plots=show_plots,
                                  rows=3,  # rows
                                  cols=2,  # cols
                                  num=3)   # start num

        if da > 0.0 or dy > 0.0:
            if inner:
                r_out = da / 2.0
                r_in = dy / 2.0
            elif outer:
                r_out = dy / 2.0
                r_in = da / 2.0
            else:
                r_out = 0.0
                r_in = 0.0
            if machine.cut_is_possible(r_in, r_out):
                logger.info("make a cut")
                machine = machine.cut(r_in, r_out)

        if part:
            if part[0] == 'stator':
                machine.geom.set_stator()
                machine.geom.search_stator_subregions(part[1])

                if machine.has_mirrored_windings():
                    logger.info("undo mirror of stator")
                    machine = machine.undo_mirror()
                    machine.geom.set_stator()
                    machine.geom.search_stator_subregions(part[1])
                    machine.create_mirror_lines_outside_windings()

                params = create_femag_parameters_stator(machine,
                                                        part[1])
            else:
                machine.geom.set_rotor()
                machine.geom.search_rotor_subregions(part[1])
                params = create_femag_parameters_rotor(machine,
                                                       part[1])
        else:
            machine.geom.search_subregions()

        machine.delete_tiny_elements(mindist)
        logger.info("END of work: %s", basename)

        if show_plots:
            p.render_elements(machine.geom, Shape,
                              draw_inside=True, title=name,
                              rows=3, cols=2, num=5, show=False,
                              # with_nodes=True,
                              # neighbors=True,
                              write_id=write_id,
                              fill_areas=True)
            if write_png:
                p.write_plot(basename)
            else:
                p.show_plot()

        if show_areas:
            p.render_elements(machine.geom, Shape,
                              title=name,
                              show=True,
                              draw_inside=True,
                              neighbors=True,
                              fill_areas=True)
            p.render_areas(machine.geom,
                           title=name,
                           with_nodes=True,
                           single_view=True)

        if write_fsl:
            if machine.is_full():
                logger.warning("it's not possible to create fsl-file")
                return None

            fslrenderer = FslRenderer(basename)
            conv['fsl'] = fslrenderer.render(machine, inner, outer)
            if params:
                conv.update(params)

    conv['name'] = basename
    return conv


def create_femag_parameters(m_inner, m_outer, nodedist=1):
    if not (m_inner and m_outer):
        return {}

    params = {}
    geom_inner = m_inner.geom
    geom_outer = m_outer.geom

    parts_inner = int(m_inner.get_symmetry_part())
    parts_outer = int(m_outer.get_symmetry_part())

    if parts_inner > parts_outer:
        num_slots = parts_inner
        num_poles = parts_outer
        num_sl_gen = int(geom_inner.get_symmetry_copies()+1)
        alfa_slot = geom_inner.get_alfa()
        alfa_pole = geom_outer.get_alfa()
    else:
        num_slots = parts_outer
        num_poles = parts_inner
        num_sl_gen = int(geom_outer.get_symmetry_copies()+1)
        alfa_slot = geom_outer.get_alfa()
        alfa_pole = geom_inner.get_alfa()

    params['tot_num_slot'] = num_slots
    params['num_sl_gen'] = num_sl_gen
    params['num_poles'] = num_poles
    params['nodedist'] = nodedist

    params['dy1'] = 2*geom_outer.max_radius
    params['da1'] = 2*geom_outer.min_radius
    params['da2'] = 2*geom_inner.max_radius
    params['dy2'] = 2*geom_inner.min_radius
    params['agndst'] = agndst(params['da1'], params['da2'],
                              num_slots, num_poles,
                              nodedist)
    params['alfa_slot'] = alfa_slot
    params['alfa_pole'] = alfa_pole

    if num_slots == 0 or num_poles == 0:
        if num_slots == 0:
            logger.warning("No slots found")
        if num_poles == 0:
            logger.warning("No poles found")
        logger.warning("Model not ready for femag")
        return {'error': "Model not ready for femag"}

    if not np.isclose(alfa_slot * num_slots,
                      alfa_pole * num_poles):
        logger.warning("slots and poles dont match")
        return {'error': "Model not ready for femag"}

    return params


def create_femag_parameters_stator(motor, position):
    params = {}
    num_slots = motor.get_num_slots()
    params['tot_num_slot'] = num_slots
    if position == 'in':
        params['da2'] = 2*motor.geom.max_radius
        params['dy2'] = 2*motor.geom.min_radius
    else:
        params['dy1'] = 2*motor.geom.max_radius
        params['da1'] = 2*motor.geom.min_radius
    return params


def create_femag_parameters_rotor(motor, position):
    params = {}
    num_poles = motor.get_num_poles()
    params['num_poles'] = num_poles
    if position == 'in':
        params['da2'] = 2*motor.geom.max_radius
        params['dy2'] = 2*motor.geom.min_radius
    else:
        params['dy1'] = 2*motor.geom.max_radius
        params['da1'] = 2*motor.geom.min_radius
    return params
