package com.jme3.scene.plugins.blender.particles;

import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.effect.influencers.EmptyParticleInfluencer;
import com.jme3.effect.influencers.NewtonianParticleInfluencer;
import com.jme3.effect.influencers.ParticleInfluencer;
import com.jme3.effect.shapes.EmitterMeshConvexHullShape;
import com.jme3.effect.shapes.EmitterMeshFaceShape;
import com.jme3.effect.shapes.EmitterMeshVertexShape;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.plugins.blender.AbstractBlenderHelper;
import com.jme3.scene.plugins.blender.BlenderContext;
import com.jme3.scene.plugins.blender.file.BlenderFileException;
import com.jme3.scene.plugins.blender.file.DynamicArray;
import com.jme3.scene.plugins.blender.file.Pointer;
import com.jme3.scene.plugins.blender.file.Structure;

import java.util.logging.Logger;

public class ParticlesHelper extends AbstractBlenderHelper {
    private static final Logger LOGGER              = Logger.getLogger(ParticlesHelper.class.getName());

    // part->type
    public static final int     PART_EMITTER        = 0;
    public static final int     PART_REACTOR        = 1;
    public static final int     PART_HAIR           = 2;
    public static final int     PART_FLUID          = 3;

    // part->flag
    public static final int     PART_REACT_STA_END  = 1;
    public static final int     PART_REACT_MULTIPLE = 2;
    public static final int     PART_LOOP           = 4;
    // public static final int PART_LOOP_INSTANT =8;
    public static final int     PART_HAIR_GEOMETRY  = 16;
    public static final int     PART_UNBORN         = 32;                                               // show unborn particles
    public static final int     PART_DIED           = 64;                                               // show died particles
    public static final int     PART_TRAND          = 128;
    public static final int     PART_EDISTR         = 256;                                              // particle/face from face areas
    public static final int     PART_STICKY         = 512;                                              // collided particles can stick to collider
    public static final int     PART_DIE_ON_COL     = 1 << 12;
    public static final int     PART_SIZE_DEFL      = 1 << 13;                                          // swept sphere deflections
    public static final int     PART_ROT_DYN        = 1 << 14;                                          // dynamic rotation
    public static final int     PART_SIZEMASS       = 1 << 16;
    public static final int     PART_ABS_LENGTH     = 1 << 15;
    public static final int     PART_ABS_TIME       = 1 << 17;
    public static final int     PART_GLOB_TIME      = 1 << 18;
    public static final int     PART_BOIDS_2D       = 1 << 19;
    public static final int     PART_BRANCHING      = 1 << 20;
    public static final int     PART_ANIM_BRANCHING = 1 << 21;
    public static final int     PART_SELF_EFFECT    = 1 << 22;
    public static final int     PART_SYMM_BRANCHING = 1 << 24;
    public static final int     PART_HAIR_BSPLINE   = 1024;
    public static final int     PART_GRID_INVERT    = 1 << 26;
    public static final int     PART_CHILD_EFFECT   = 1 << 27;
    public static final int     PART_CHILD_SEAMS    = 1 << 28;
    public static final int     PART_CHILD_RENDER   = 1 << 29;
    public static final int     PART_CHILD_GUIDE    = 1 << 30;

    // part->from
    public static final int     PART_FROM_VERT      = 0;
    public static final int     PART_FROM_FACE      = 1;
    public static final int     PART_FROM_VOLUME    = 2;
    public static final int     PART_FROM_PARTICLE  = 3;
    public static final int     PART_FROM_CHILD     = 4;

    // part->phystype
    public static final int     PART_PHYS_NO        = 0;
    public static final int     PART_PHYS_NEWTON    = 1;
    public static final int     PART_PHYS_KEYED     = 2;
    public static final int     PART_PHYS_BOIDS     = 3;

    // part->draw_as
    public static final int     PART_DRAW_NOT       = 0;
    public static final int     PART_DRAW_DOT       = 1;
    public static final int     PART_DRAW_CIRC      = 2;
    public static final int     PART_DRAW_CROSS     = 3;
    public static final int     PART_DRAW_AXIS      = 4;
    public static final int     PART_DRAW_LINE      = 5;
    public static final int     PART_DRAW_PATH      = 6;
    public static final int     PART_DRAW_OB        = 7;
    public static final int     PART_DRAW_GR        = 8;
    public static final int     PART_DRAW_BB        = 9;

    /**
     * This constructor parses the given blender version and stores the result. Some functionalities may differ in
     * different blender versions.
     * @param blenderVersion
     *            the version read from the blend file
     * @param blenderContext
     *            the blender context
     */
    public ParticlesHelper(String blenderVersion, BlenderContext blenderContext) {
        super(blenderVersion, blenderContext);
    }

    @SuppressWarnings("unchecked")
    public ParticleEmitter toParticleEmitter(Structure particleSystem) throws BlenderFileException {
        ParticleEmitter result = null;
        Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part");
        if (pParticleSettings.isNotNull()) {
            Structure particleSettings = pParticleSettings.fetchData().get(0);

            int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue();

            // draw type will be stored temporarily in the name (it is used during modifier applying operation)
            int drawAs = ((Number) particleSettings.getFieldValue("draw_as")).intValue();
            char nameSuffix;// P - point, L - line, N - None, B - Bilboard
            switch (drawAs) {
                case PART_DRAW_NOT:
                    nameSuffix = 'N';
                    totPart = 0;// no need to generate particles in this case
                    break;
                case PART_DRAW_BB:
                    nameSuffix = 'B';
                    break;
                case PART_DRAW_OB:
                case PART_DRAW_GR:
                    nameSuffix = 'P';
                    LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");// TODO: support groups and aobjects
                    break;
                case PART_DRAW_LINE:
                    nameSuffix = 'L';
                    LOGGER.warning("Lines not yet supported! Using point representation instead!");// TODO: support lines
                default:// all others are rendered as points in blender
                    nameSuffix = 'P';
            }
            result = new ParticleEmitter(particleSettings.getName() + nameSuffix, Type.Triangle, totPart);
            if (nameSuffix == 'N') {
                return result;// no need to set anything else
            }

            // setting the emitters shape (the shapes meshes will be set later during modifier applying operation)
            int from = ((Number) particleSettings.getFieldValue("from")).intValue();
            switch (from) {
                case PART_FROM_VERT:
                    result.setShape(new EmitterMeshVertexShape());
                    break;
                case PART_FROM_FACE:
                    result.setShape(new EmitterMeshFaceShape());
                    break;
                case PART_FROM_VOLUME:
                    result.setShape(new EmitterMeshConvexHullShape());
                    break;
                default:
                    LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')');
            }

            // reading acceleration
            DynamicArray<Number> acc = (DynamicArray<Number>) particleSettings.getFieldValue("acc");
            result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue());

            // setting the colors
            result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f));
            result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f));

            // reading size
            float sizeFactor = nameSuffix == 'B' ? 1.0f : 0.3f;
            float size = ((Number) particleSettings.getFieldValue("size")).floatValue() * sizeFactor;
            result.setStartSize(size);
            result.setEndSize(size);

            // reading lifetime
            int fps = blenderContext.getBlenderKey().getFps();
            float lifetime = ((Number) particleSettings.getFieldValue("lifetime")).floatValue() / fps;
            float randlife = ((Number) particleSettings.getFieldValue("randlife")).floatValue() / fps;
            result.setLowLife(lifetime * (1.0f - randlife));
            result.setHighLife(lifetime);

            // preparing influencer
            ParticleInfluencer influencer;
            int phystype = ((Number) particleSettings.getFieldValue("phystype")).intValue();
            switch (phystype) {
                case PART_PHYS_NEWTON:
                    influencer = new NewtonianParticleInfluencer();
                    ((NewtonianParticleInfluencer) influencer).setNormalVelocity(((Number) particleSettings.getFieldValue("normfac")).floatValue());
                    ((NewtonianParticleInfluencer) influencer).setVelocityVariation(((Number) particleSettings.getFieldValue("randfac")).floatValue());
                    ((NewtonianParticleInfluencer) influencer).setSurfaceTangentFactor(((Number) particleSettings.getFieldValue("tanfac")).floatValue());
                    ((NewtonianParticleInfluencer) influencer).setSurfaceTangentRotation(((Number) particleSettings.getFieldValue("tanphase")).floatValue());
                    break;
                case PART_PHYS_BOIDS:
                case PART_PHYS_KEYED:// TODO: support other influencers
                    LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!");
                case PART_PHYS_NO:
                default:
                    influencer = new EmptyParticleInfluencer();
            }
            result.setParticleInfluencer(influencer);
        }
        return result;
    }
}
