#include "klgd_ff_plugin_p.h"
 #include <linux/slab.h>
+#include <linux/fixp-arith.h>
 
+#define DIR_TO_DEGREES(dir) (360 - ((((dir > 0xc000) ? (u32)dir + 0x4000 - 0xffff : (u32)dir + 0x4000) * 360) / 0xffff))
+
+static int ffpl_handle_state_change(struct klgd_plugin_private *priv, struct klgd_command_stream *s, struct ffpl_effect *eff,
+                                   const bool ignore_combined);
 static bool ffpl_has_gain(const struct ff_effect *eff);
 static bool ffpl_needs_replacing(const struct ff_effect *ac_eff, const struct ff_effect *la_eff);
 
+static u16 ffpl_degrees_from_x_y(const int x, const int y)
+{
+       u16 degrees;
+       /* Precalculated tan() values expanded by FRAC_N */
+       static const unsigned int precalc_tan[] = {0, 4, 8, 13, 17, 22, 26, 31, 35, 40,                                 /* 0 - 9 deg */
+                                                  45, 49, 54, 59, 63, 68, 73, 78, 83, 88,                              /* 10 - 19 deg */
+                                                  93, 98, 108, 113, 119, 124, 130, 136, 141,                           /* 20 - 29 deg */
+                                                  147, 153, 159, 166, 172, 179, 185, 192, 200, 207,                    /* 30 - 39 deg */
+                                                  214, 222, 230, 238, 247, 255, 265, 274, 284, 294,                    /* 40 - 49 deg */
+                                                  305, 316, 327, 339, 352, 365, 379, 394, 409, 426,                    /* 50 - 59 deg */
+                                                  443, 461, 481, 502, 524, 548, 574, 603, 633, 666,                    /* 60 - 69 deg */
+                                                  703, 743, 787, 837, 892, 955, 1026, 1108, 1204, 1317,                /* 70 - 79 deg */
+                                                  1451, 1616, 1821, 2084, 2435, 2926, 3660, 4884, 7330, 14666};        /* 80 - 89 deg */
+
+       for (degrees = 0; degrees < ARRAY_SIZE(precalc_tan); degrees++) {
+               unsigned int tan = (abs(y) << FRAC_N) / abs(x);
+               if (tan <= precalc_tan[degrees])
+                       return degrees;
+       }
+
+       return 90;
+}
+
+static void ffpl_x_y_to_lvl_dir(const int x, const int y, s16 *level, u16 *direction)
+{
+       const u16 angle = (x == 0) ? 90 : ffpl_degrees_from_x_y(x, y);
+
+       printk(KERN_NOTICE "KLGDFF: Angle = %u\n", angle);
+       *level = int_sqrt(x * x + y * y);
+       /* 1st quadrant */
+       if (x >= 0 && y >= 0)
+               *direction = ((270 - angle) * 0xffff) / 360;
+       /* 2nd quadrant */
+       else if (x < 0 && y >= 0)
+               *direction = ((90 + angle) * 0xffff) / 360;
+       /* 3rd quadrant */
+       else if (x < 0 && y < 0)
+               *direction = ((90 - angle) * 0xffff) / 360;
+       /* 4th quadrant */
+       else if (x > 0 && y < 0)
+               *direction = ((270 + angle) * 0xffff) / 360;
+       else
+               *direction = 0;
+}
+
+static bool ffpl_is_combinable(const struct ff_effect *eff)
+{
+       /* TODO: Proper decision of what is a combinable effect */
+       return eff->type == FF_CONSTANT;
+}
+
+
+bool ffpl_constant_force_to_x_y(const struct ff_effect *eff, int *x, int *y)
+{
+       int degrees;
+
+       if (eff->type != FF_CONSTANT)
+               return false;
+
+       degrees = DIR_TO_DEGREES(eff->direction);
+       printk(KERN_NOTICE "KLGDFF: DIR_TO_DEGREES > Dir: %u, Deg: %u\n", eff->direction, degrees);
+       *x += (eff->u.constant.level * fixp_cos(degrees)) >> FRAC_N;
+       *y += (eff->u.constant.level * fixp_sin(degrees)) >> FRAC_N;
+
+       return true;
+}
+
+static void ffpl_recalc_combined(struct klgd_plugin_private *priv)
+{
+       size_t idx;
+       struct ff_effect *cb_latest = &priv->combined_effect.latest;
+       int x = 0;
+       int y = 0;
+
+       for (idx = 0; idx < priv->effect_count; idx++) {
+               const struct ffpl_effect *eff = &priv->effects[idx];
+
+               switch (eff->change) {
+               case FFPL_DONT_TOUCH:
+                       if (eff->state != FFPL_STARTED)
+                               break;
+               case FFPL_TO_START:
+               case FFPL_TO_UPDATE:
+                       ffpl_constant_force_to_x_y(&eff->latest, &x, &y);
+                       break;
+               default:
+                       break;
+               }
+       }
+
+       ffpl_x_y_to_lvl_dir(x, y, &cb_latest->u.constant.level, &cb_latest->direction);
+       cb_latest->type = FF_CONSTANT;
+       printk(KERN_NOTICE "KLGDFF: Resulting combined CF effect > x: %d, y: %d, level: %d, direction: %d\n", x, y, cb_latest->u.constant.level,
+              cb_latest->direction);
+}
+
+static int ffpl_handle_combinable_effects(struct klgd_plugin_private *priv, struct klgd_command_stream *s)
+{
+       size_t idx;
+       bool needs_update = false;
+       size_t active_effects = 0;
+
+       for (idx = 0; idx < priv->effect_count; idx++) {
+               struct ffpl_effect *eff = &priv->effects[idx];
+
+               if (!ffpl_is_combinable(&eff->latest))
+                       continue;
+
+               switch (eff->change) {
+               case FFPL_DONT_TOUCH:
+                       if (eff->state == FFPL_STARTED)
+                               active_effects++;
+                       printk(KERN_NOTICE "KLGDFF: Unchanged combinable effect, total active effects %lu\n", active_effects);
+                       break;
+               case FFPL_TO_START:
+                       eff->state = FFPL_STARTED;
+                       eff->active = eff->latest;
+               case FFPL_TO_UPDATE:
+                       active_effects++;
+                       needs_update = true;
+                       printk(KERN_NOTICE "KLGDFF: Altered combinable effect, total active effects %lu\n", active_effects);
+                       break;
+               default:
+                       needs_update = true;
+                       eff->state = FFPL_EMPTY;
+                       printk(KERN_NOTICE "KLGDFF: Stopped combinable effect, total active effects %lu\n", active_effects);
+                       break;
+               }
+       }
+
+       /* Combined effect needs recalculation */
+       if (needs_update) {
+               if (active_effects) {
+                       printk(KERN_NOTICE "KLGDFF: Combined effect needs an update, total effects active: %lu\n", active_effects);
+                       ffpl_recalc_combined(priv);
+                       if (priv->combined_effect.state == FFPL_STARTED)
+                               priv->combined_effect.change = FFPL_TO_UPDATE;
+                       else
+                               priv->combined_effect.change = FFPL_TO_START;
+
+                       return 0;
+               }
+               /* No combinable effects are active, remove the effect from device */
+               printk(KERN_NOTICE "KLGDFF: No combinable effects are active, erase the combined effect from device\n");
+               priv->combined_effect.change = FFPL_TO_ERASE;
+       }
+
+       return 0;
+}
+
 static int ffpl_erase_effect(struct klgd_plugin_private *priv, struct klgd_command_stream *s, struct ffpl_effect *eff)
 {
        if (eff->uploaded_to_device) {
        eff->latest = *effect;
 
        if (eff->state != FFPL_EMPTY) {
+               printk(KERN_NOTICE "KLGDFF: Updating effect in slot %d\n", effect->id);
                if (ffpl_needs_replacing(&eff->active, &eff->latest)) {
                        eff->replace = true;
                        eff->change = FFPL_TO_UPLOAD;
        struct klgd_plugin_private *priv = self->private;
        struct klgd_command_stream *s;
        size_t idx;
+       int ret;
 
        s = klgd_alloc_stream();
        if (!s)
                return NULL; /* TODO: Error handling */
   
+       ret = ffpl_handle_combinable_effects(priv, s);
+       if (ret)
+               printk(KERN_WARNING "KLGDFF: Cannot process combinable effects, ret %d\n", ret);
+
        for (idx = 0; idx < priv->effect_count; idx++) {
                struct ffpl_effect *eff = &priv->effects[idx];
-               int ret = 0;
-
-               printk(KERN_NOTICE "KLGDFF - Processing effect %lu\n", idx);
-               /* Latest effect is of different type than currently active effect,
-                * remove it from the device and upload the latest one */
-               if (eff->replace) {
-                       switch (eff->change) {
-                       case FFPL_TO_ERASE:
-                               printk("KLGDFF - Rpl chg - TO_ERASE\n");
-                               switch (eff->state) {
-                               case FFPL_STARTED:
-                                       ret = ffpl_stop_effect(priv, s, eff);
-                                       if (ret)
-                                               break;
-                               case FFPL_UPLOADED:
-                                       ret = ffpl_erase_effect(priv, s, eff);
-                                       /* TODO: Handle error */
-                               default:
-                                       /* Nothing to do - the effect that is replacing the old effect is about to be erased anyway 
-                                        * State of the effect to be replaced should also never be EMPTY */
-                                       break;
-                               }
-                               break;
-                       case FFPL_TO_UPLOAD:
-                       case FFPL_TO_STOP: /* There is no difference between stopping or uploading an effect when we are replacing it */
-                               printk("KLGDFF - Rpl chg - TO_UPLOAD/TO_STOP\n");
-                               switch (eff->state) {
-                               case FFPL_STARTED:
-                                       /* Overwrite the currently active effect and set it to UPLOADED state */
-                                       if (priv->has_owr_to_upl) {
-                                               ret = ffpl_replace_effect(priv, s, eff, FFPL_OWR_TO_UPL);
-                                               if (ret)
-                                                       break;
-                                               continue;
-                                       }
-                                       ret = ffpl_stop_effect(priv, s, eff);
-                                       if (ret)
-                                               break;
-                               case FFPL_UPLOADED:
-                                       ret = ffpl_erase_effect(priv, s, eff);
-                                       if (ret)
-                                               break;
-                               case FFPL_EMPTY: /* State cannot actually be FFPL_EMPTY becuase only uploaded or started effects have to be replaced like this */
-                                       ret = ffpl_upload_effect(priv, s, eff);
-                                       break;
-                               }
-                               break;
-                       case FFPL_TO_START:
-                       case FFPL_TO_UPDATE: /* There is no difference between staring or updating an effect when we are replacing it */
-                               printk("KLGDFF - Rpl chg - TO_START/TO_UPDATE\n");
-                               switch (eff->state) {
-                               case FFPL_STARTED:
-                                       if (priv->has_owr_to_srt) {
-                                               ret = ffpl_replace_effect(priv, s, eff, FFPL_OWR_TO_SRT);
-                                               if (ret)
-                                                       break;
-                                               continue;
-                                       }
-                                       ret = ffpl_stop_effect(priv, s, eff);
-                                       if (ret)
-                                               break;
-                               case FFPL_UPLOADED:
-                                       ret = ffpl_erase_effect(priv, s, eff);
-                                       if (ret)
-                                               break;
-                               case FFPL_EMPTY: /* State cannot actually be FFPL_EMPTY - same as above applies */
-                                       ret = ffpl_upload_effect(priv, s, eff);
-                                       if (ret)
-                                               break;
-                                       ret = ffpl_start_effect(priv, s, eff);
-                                       break;
-                               }
-                               break;
-                       case FFPL_DONT_TOUCH:
-                               printk(KERN_WARNING "Got FFPL_DONT_TOUCH change for effect that should be replaced - this should not happen!\n");
-                               break;
-                       default:
-                               printk(KERN_WARNING "Unhandled state change while replacing effect\n");
-                               break;
-                       }
-                       if (ret)
-                               printk(KERN_WARNING "Error %d while replacing effect %lu\n", ret, idx);
-                       else {
-                               eff->replace = false;
-                               eff->change = FFPL_DONT_TOUCH;
-                               continue;
-                       }
-               }
 
+               printk(KERN_NOTICE "KLGDFF: Processing effect %lu\n", idx);
+               ret = ffpl_handle_state_change(priv, s, eff, true);
+               /* TODO: Do something useful with the return code */
+               if (ret)
+                       printk(KERN_WARNING "KLGDFF: Cannot get command stream effect %lu\n", idx);
+       }
+
+       /* Handle combined effect here */
+       ret = ffpl_handle_state_change(priv, s, &priv->combined_effect, false);
+       if (ret)
+               printk(KERN_WARNING "KLGDFF: Cannot get command stream for combined effect\n");
+
+       return s;
+}
+
+static bool ffpl_get_update_time(struct klgd_plugin *self, const unsigned long now, unsigned long *t)
+{
+       struct klgd_plugin_private *priv = self->private;
+       size_t idx, events = 0;
+
+       for (idx = 0; idx < priv->effect_count; idx++) {
+               struct ffpl_effect *eff = &priv->effects[idx];
+
+               /* Tell KLGD to attend to us as soon as possible if an effect has to change state */
+               if (eff->change == FFPL_DONT_TOUCH)
+                       continue;
+               *t = now;
+               events++;
+       }
+
+       return events ? true : false;
+}
+
+static int ffpl_handle_state_change(struct klgd_plugin_private *priv, struct klgd_command_stream *s, struct ffpl_effect *eff,
+                                   const bool ignore_combined)
+{
+       int ret;
+       
+       /* Latest effect is of different type than currently active effect,
+        * remove it from the device and upload the latest one */
+       if (eff->replace) {
                switch (eff->change) {
-               case FFPL_TO_UPLOAD:
-                       printk(KERN_INFO "KLGDFF - Chg TO_UPLOAD\n");
+               case FFPL_TO_ERASE:
+                       printk(KERN_NOTICE "KLGDFF: Rpl chg - TO_ERASE\n");
                        switch (eff->state) {
-                       case FFPL_EMPTY:
-                               ret = ffpl_upload_effect(priv, s, eff);
-                               break;
                        case FFPL_STARTED:
                                ret = ffpl_stop_effect(priv, s, eff);
-                               break;
+                               if (ret)
+                                       break;
+                       case FFPL_UPLOADED:
+                               ret = ffpl_erase_effect(priv, s, eff);
+                               if (ret)
+                                       break;
                        default:
+                               /* Nothing to do - the effect that is replacing the old effect is about to be erased anyway 
+                                * State of the effect to be replaced should also never be EMPTY */
+                               ret = 0;
                                break;
                        }
                        break;
-               case FFPL_TO_START:
-                       printk(KERN_INFO "KLGDFF - Chg TO_START\n");
+               case FFPL_TO_UPLOAD:
+               case FFPL_TO_STOP: /* There is no difference between stopping or uploading an effect when we are replacing it */
+                       printk("KLGDFF: Rpl chg - TO_UPLOAD/TO_STOP\n");
                        switch (eff->state) {
-                       case FFPL_EMPTY:
-                               if (priv->has_emp_to_srt) {
-                                       ret = ffpl_start_effect(priv, s, eff);
+                       case FFPL_STARTED:
+                               /* Overwrite the currently active effect and set it to UPLOADED state */
+                               if (priv->has_owr_to_upl) {
+                                       ret = ffpl_replace_effect(priv, s, eff, FFPL_OWR_TO_UPL);
                                        break;
                                }
-                               ret = ffpl_upload_effect(priv, s, eff);
+                               ret = ffpl_stop_effect(priv, s, eff);
                                if (ret)
                                        break;
                        case FFPL_UPLOADED:
-                               ret = ffpl_start_effect(priv, s, eff);
+                               ret = ffpl_erase_effect(priv, s, eff);
+                               if (ret)
+                                       break;
+                       case FFPL_EMPTY: /* State cannot actually be FFPL_EMPTY becuase only uploaded or started effects have to be replaced like this */
+                               /* Combinable effects are taken care of elsewhere and should not be uploaded individually */
+                               if (!ffpl_is_combinable(&eff->latest))
+                                       ret = ffpl_upload_effect(priv, s, eff);
+                               else
+                                       ret = 0;
                                break;
                        default:
-                               break;
+                               printk(KERN_WARNING "KLGDFF: Unhandled effect state\n");
+                               ret = -EINVAL;
                        }
                        break;
-               case FFPL_TO_STOP:
-                       printk(KERN_INFO "KLGDFF - Chg TO_STOP\n");
+               case FFPL_TO_START:
+               case FFPL_TO_UPDATE: /* There is no difference between staring or updating an effect when we are replacing it */
+                       printk("KLGDFF: Rpl chg - TO_START/TO_UPDATE\n");
                        switch (eff->state) {
                        case FFPL_STARTED:
+                               if (priv->has_owr_to_srt) {
+                                       ret = ffpl_replace_effect(priv, s, eff, FFPL_OWR_TO_SRT);
+                                       break;
+                               }
                                ret = ffpl_stop_effect(priv, s, eff);
-                               break;
-                       case FFPL_EMPTY:
-                               ret = ffpl_upload_effect(priv, s, eff);
-                               break;
-                       default:
-                               break;
-                       }
-                       break;
-               case FFPL_TO_ERASE:
-                       printk(KERN_INFO "KLGDFF - Chg TO_ERASE\n");
-                       switch (eff->state) {
+                               if (ret)
+                                       break;
                        case FFPL_UPLOADED:
                                ret = ffpl_erase_effect(priv, s, eff);
-                               break;
-                       case FFPL_STARTED:
-                               ret = ffpl_stop_effect(priv, s, eff);
                                if (ret)
                                        break;
-                               ret = ffpl_erase_effect(priv, s, eff);
+                       case FFPL_EMPTY: /* State cannot actually be FFPL_EMPTY - same as above applies */
+                               /* Combinable effects are taken care of elsewhere and should not be uploaded and started individually */
+                               if (ffpl_is_combinable(&eff->latest)) {
+                                       ret = 0;
+                                       break;
+                               }
+                               ret = ffpl_upload_effect(priv, s, eff);
+                               if (ret)
+                                       break;
+                               ret = ffpl_start_effect(priv, s, eff);
                                break;
                        default:
+                               printk(KERN_WARNING "KLGDFF: Unhandled effect state\n");
+                               ret = -EINVAL;
                                break;
                        }
                        break;
-               case FFPL_TO_UPDATE:
-                       printk(KERN_INFO "KLGDFF - Chg TO_UPDATE\n");
-                       ret = ffpl_update_effect(priv, s, eff);
-                       break;
                case FFPL_DONT_TOUCH:
-                       printk(KERN_INFO "KLGDFF - Chg - NO CHANGE\n");
-                       continue;
+                       printk(KERN_WARNING "KLGDFF: Got FFPL_DONT_TOUCH change for effect that should be replaced - this should not happen!\n");
+                       ret = -EINVAL;
+                       break;
                default:
-                       pr_debug("Unhandled state\n");
+                       printk(KERN_WARNING "KLGDFF: Unhandled state change while replacing effect\n");
+                       ret = -EINVAL;
+                       break;
                }
-
-               /* TODO: Handle errors */
                if (ret)
-                       printk(KERN_WARNING "Error %d while processing effect %lu\n", ret, idx);
-               else
-                       eff->change = FFPL_DONT_TOUCH;
-       }
-
-       return s;
-}
+                       printk(KERN_WARNING "KLGDFF: Error %d while replacing effect\n", ret);
 
-static bool ffpl_get_update_time(struct klgd_plugin *self, const unsigned long now, unsigned long *t)
-{
-       struct klgd_plugin_private *priv = self->private;
-       size_t idx, events = 0;
+               eff->replace = false;
+               eff->change = FFPL_DONT_TOUCH;
+               return ret;
+       }
 
-       for (idx = 0; idx < priv->effect_count; idx++) {
-               struct ffpl_effect *eff = &priv->effects[idx];
+       /* Combinable effects have already been handled, do not try to handle then again individually */
+       if (ffpl_is_combinable(&eff->latest) && ignore_combined) {
+               printk(KERN_NOTICE "KLGDFF: Effect is combinable\n");
+               ret = 0;
+               goto out;
+       }
 
-               /* Tell KLGD to attend to us as soon as possible if an effect has to change state */
-               if (eff->change == FFPL_DONT_TOUCH)
-                       continue;
-               *t = now;
-               events++;
+       switch (eff->change) {
+       case FFPL_TO_UPLOAD:
+               printk(KERN_INFO "KLGDFF: Chg TO_UPLOAD\n");
+               switch (eff->state) {
+               case FFPL_EMPTY:
+                       ret = ffpl_upload_effect(priv, s, eff);
+                       break;
+               case FFPL_STARTED:
+                       ret = ffpl_stop_effect(priv, s, eff);
+                       break;
+               default:
+                       ret = 0;
+                       break;
+               }
+               break;
+       case FFPL_TO_START:
+               printk(KERN_INFO "KLGDFF: Chg TO_START\n");
+               switch (eff->state) {
+               case FFPL_EMPTY:
+                       if (priv->has_emp_to_srt) {
+                               ret = ffpl_start_effect(priv, s, eff);
+                               break;
+                       }
+                       ret = ffpl_upload_effect(priv, s, eff);
+                       if (ret)
+                               return ret;
+               case FFPL_UPLOADED:
+                       ret = ffpl_start_effect(priv, s, eff);
+                       break;
+               default:
+                       ret = 0;
+                       break;
+               }
+               break;
+       case FFPL_TO_STOP:
+               printk(KERN_INFO "KLGDFF: Chg TO_STOP\n");
+               switch (eff->state) {
+               case FFPL_STARTED:
+                       ret = ffpl_stop_effect(priv, s, eff);
+                       break;
+               case FFPL_EMPTY:
+                       ret = ffpl_upload_effect(priv, s, eff);
+                       break;
+               default:
+                       ret = 0;
+                       break;
+               }
+               break;
+       case FFPL_TO_ERASE:
+               printk(KERN_INFO "KLGDFF: Chg TO_ERASE\n");
+               switch (eff->state) {
+               case FFPL_UPLOADED:
+                       ret = ffpl_erase_effect(priv, s, eff);
+                       break;
+               case FFPL_STARTED:
+                       ret = ffpl_stop_effect(priv, s, eff);
+                       if (ret)
+                               break;
+                       ret = ffpl_erase_effect(priv, s, eff);
+                       break;
+               default:
+                       ret = 0;
+                       break;
+               }
+               break;
+       case FFPL_TO_UPDATE:
+               printk(KERN_INFO "KLGDFF: Chg TO_UPDATE\n");
+               ret = ffpl_update_effect(priv, s, eff);
+               break;
+       case FFPL_DONT_TOUCH:
+               printk(KERN_INFO "KLGDFF: Chg - NO CHANGE\n");
+               return 0;
+       default:
+               return -EINVAL;
+               pr_debug("KLGDFF: Unhandled effect state change\n");
        }
 
-       return events ? true : false;
+out:
+       eff->change = FFPL_DONT_TOUCH;
+
+       return ret;
 }
 
 static bool ffpl_has_gain(const struct ff_effect *eff)
                        input_set_capability(dev, EV_FF, idx + FF_EFFECT_MIN);
                }
        }
+
        return 0;
 
 err_out2:
 static bool ffpl_needs_replacing(const struct ff_effect *ac_eff, const struct ff_effect *la_eff)
 {
        if (ac_eff->type != la_eff->type) {
-               printk(KERN_NOTICE "KLGDFF - Effects are of different type - replacing (%d x %d)\n", ac_eff->type, la_eff->type);
+               printk(KERN_NOTICE "KLGDFF: Effects are of different type - replacing (%d x %d)\n", ac_eff->type, la_eff->type);
                return true;
        }
 
        if (ac_eff->type == FF_PERIODIC) {
                if (ac_eff->u.periodic.waveform != la_eff->u.periodic.waveform) {
-                       printk(KERN_NOTICE "KLGDFF - Effects have different waveforms - replacing\n");
+                       printk(KERN_NOTICE "KLGDFF: Effects have different waveforms - replacing\n");
                        return true;
                }
        }
 
-       printk(KERN_NOTICE "KLGDFF - Effect does not have to be replaced, updating\n");
+       printk(KERN_NOTICE "KLGDFF: Effect does not have to be replaced, updating\n");
        return false;
 }