'''
DESCRIPTION.
- Reads a FreeCAD .FCStd file.
- Set tesselation parts to mesh.
- Return scene as JSON dictionary.
'''

import json
import FreeCAD
import Part
import Mesh
import MeshPart
import logging
import math
from freecad.robossembler.utils.is_object_solid import is_object_solid
from freecad.robossembler.utils.processor import process_file
from freecad.robossembler.utils.custom_parser import CustomArgumentParser

logger = logging.getLogger(__name__)


def freecad_to_json(**kwargs):
    ''' Reads a FreeCAD .FCStd file and return json assembly. '''

    scene = {}
    js_objs = {}

    doc = FreeCAD.open(kwargs['fcstd_path'])
    docname = doc.Name

    # collect all materials
    fem_mats = []
    for fem_mat in doc.Objects:
        if fem_mat.isDerivedFrom('App::MaterialObjectPython'):
            fem_mats.append(fem_mat)

    for obj in doc.Objects:
        js_obj = {}

        if kwargs['skiphidden']:
            if not obj.Visibility:
                continue

        if obj.isDerivedFrom('PartDesign::CoordinateSystem'):
            js_obj['type'] = 'LCS'

        elif obj.isDerivedFrom('Part::Feature'):
            js_obj['type'] = 'PART'
            # filter for nonsolids

            if is_object_solid(obj) or hasattr(obj, kwargs['property_forse_nonsolid']):
                # create mesh from shape
                shape = obj.Shape
                shape = obj.Shape.copy()
                shape.Placement = obj.Placement.inverse().multiply(shape.Placement)
                meshfromshape = doc.addObject('Mesh::Feature', 'Mesh')
                if kwargs['tesselation_method'] == 'Standard':
                    meshfromshape.Mesh = MeshPart.meshFromShape(
                        Shape=shape,
                        LinearDeflection=kwargs['linear_deflection'],
                        AngularDeflection=math.radians(kwargs['angular_deflection']),
                        Relative=False)
                elif kwargs['tesselation_method'] == 'FEM':
                    meshfromshape.Mesh = MeshPart.meshFromShape(
                        Shape=shape,
                        MaxLength=kwargs['fem_size'])
                else:
                    raise TypeError('Wrong tesselation method! '
                                    'Standard and FEM methods are supported only!')

                t = meshfromshape.Mesh.Topology
                verts = [[v.x, v.y, v.z] for v in t[0]]
                faces = t[1]
                js_obj['mesh'] = (verts, faces)

                # one material for the whole object
                for fem_mat in fem_mats:
                    for ref in fem_mat.References:
                        if ref[0].Label == obj.Label:
                            js_obj['material'] = fem_mat.Material

        # skip for other object's types
        else:
            continue

        js_obj['fc_location'] = tuple(obj.Placement.Base)
        js_obj['fc_rotation'] = obj.Placement.Rotation.Q

        # construct assembly hierarchy
        obj_parent = obj.getParentGeoFeatureGroup()
        obj_child_name = None
        parents = {}
        deep_index = 0
        while obj_parent:
            parent = {}
            parent['fc_location'] = tuple(obj_parent.Placement.Base)
            parent['fc_rotation'] = obj_parent.Placement.Rotation.Q
            obj_child_name = obj_parent.Label
            obj_parent = obj_parent.getParentGeoFeatureGroup()
            if obj_parent:
                parent['parent'] = obj_parent.Label
            else:
                parent['parent'] = None
            parents[obj_child_name] = parent
            parent['deep_index'] = deep_index
            deep_index += 1
        js_obj['hierarchy'] = parents

        js_objs[obj.Label] = js_obj

    FreeCAD.closeDocument(docname)

    scene[kwargs['fcstd_path']] = js_objs

    logger.info('Passed %s objects without errors', len(js_objs))

    print(json.dumps(scene))


# to run script via FreeCADCmd
parser = CustomArgumentParser()
parser.add_argument(
    '--fcstd_path',
    type=str,
    help='Path to source FreeCAD scene',
    required=True
)
parser.add_argument(
    '--tesselation_method',
    type=str,
    help='Select tesselation method: Standard or FEM.',
    default='Standard',
    required=False
)
parser.add_argument(
    '--linear_deflection',
    type=float,
    help='Max linear distance error',
    default=0.1,
    required=False
)
parser.add_argument(
    '--angular_deflection',
    type=float,
    help='Max angular distance error',
    default=20.0,
    required=False
)
parser.add_argument(
    '--fem_size',
    type=float,
    help='For FEM method only! Finite element size in mm',
    default=50.0,
    required=False
)
parser.add_argument(
    '--skiphidden',
    type=bool,
    help='Skip processing for hidden FreeCAD objects',
    default=True,
    required=False
)
parser.add_argument(
    '--property_forse_nonsolid',
    type=str,
    help='FreeCAD property to enable processing for nonsolid objects',
    default='Robossembler_NonSolid',
    required=False
)

fc_kwargs = vars(parser.parse_known_args()[0])

freecad_to_json(**fc_kwargs)

logger.info('FreeCAD scene passed!')
