namespace PipeTap.Logic.WirePlumber {
    public Wp.Core core;
    public Wp.ObjectManager node_manager;

    public class Ctrl : Logic.Ctrl, Object {
        public Wp.Node node;
        public uint32 id;
        public float min;
        public float max;
        public int _value = 0;
        public string wp_type;
        public int value { get { return _value; } set { _value = value; send_value(value); } }
        public CtrlType ctrl_type { get; }

        public Ctrl(CtrlType pt_type, Wp.Node node, uint id, string type, float min, float max, float typ) {
            this.node = node;
            this.min = min;
            this.max = max;
            this.wp_type = type;
            this.id = id;
            _value = (int) ((typ - min) / (max - min) * 1000);
            _ctrl_type = pt_type;
        }

        private void send_value(int value) {
            var pb = new Wp.SpaPodBuilder.object("Spa:Pod:Object:Param:Props", "Props");
            pb.add_property_id(id);
            switch (wp_type) {
            case "float":
                var adjusted = value / 1000.0 * (max - min) + min;
                message("Sending float %f", (float) adjusted);
                pb.add_float((float) adjusted);
                break;
            case "bool":
                var adjusted = value == 1;
                message("Sending bool %s", adjusted ? "true" : "false");
                pb.add_boolean(adjusted);
                break;
            default:
                message("Unknown type %s", wp_type);
                break;
            }
            var pod = pb.end();
            node.set_param("Props", 0, pod);
        }
    }

    void init_manager() {
        message("Core connected");
        node_manager = new Wp.ObjectManager();
        // Use V4L2 ONLY FOR TESTING
        var interest_lcam = new Wp.ObjectInterest.type(typeof (Wp.Node));
        var interest_v4l2 = new Wp.ObjectInterest.type(typeof (Wp.Node));

        interest_lcam.add_constraint(Wp.ConstraintType.PW_PROPERTY, "device.api", Wp.ConstraintVerb.EQUALS, "libcamera");
        interest_v4l2.add_constraint(Wp.ConstraintType.PW_PROPERTY, "device.api", Wp.ConstraintVerb.EQUALS, "v4l2");

        node_manager.add_interest_full(interest_lcam);
        node_manager.add_interest_full(interest_v4l2);

        node_manager.request_object_features(typeof (Wp.Node), Wp.OBJECT_FEATURES_ALL);

        node_manager.object_added.connect((obj) => {
            Wp.Node node = obj as Wp.Node;
            var name = node.properties["node.nick"];
            message("Found camera %s", name);
            node.params_changed.connect((str) => message("%s", str));
            node.state_changed.connect(() => {
                message("Camera %s state changed -> %s", name, node.state == Wp.NodeState.RUNNING ? "Running" : "Not running :D");
                if (node.state == Wp.NodeState.RUNNING) {
                    app.device = name;
                    return;
                } else if (app.device == name) {
                    app.device = null;
                }
            });
            message("== Enumerating properties ==");
            var found_ctrls = new List<Logic.Ctrl> ();
            var params = node.enum_params_sync("PropInfo", null);
            for (Value param_info_val; params.next(out param_info_val); ) {
                Wp.SpaPod param_info_pod = (Wp.SpaPod) param_info_val;
                var iterator = param_info_pod.new_iterator();

                string description = "IDK";
                uint32 id = 0;
                string type = "IDK";

                for (Value param_kv_val; iterator.next(out param_kv_val); ) {
                    Wp.SpaPod param_kv_pod = (Wp.SpaPod) param_kv_val;
                    string key;
                    Wp.SpaPod value;
                    param_kv_pod.get_property(out key, out value);
                    switch (key) {
                        case "id":
                            value.get_id(out id);
                            break;
                        case "description":
                            value.get_string(out description);
                            break;
                        case "type":
                            if (!value.is_choice())
                                break;
                            Wp.SpaPod choice = value.get_choice_child();
                            if (choice.is_double())
                                type = "double";
                            else if (choice.is_float())
                                type = "float";
                            else if (choice.is_int())
                                type = "int";
                            else if (choice.is_boolean())
                                type = "bool";
                            else if (choice.is_rectangle())
                                type = "rect (unsupported)";
                            break;
                        default:
                            warning("Report ME: Unexpected key in property enumeration \"%s\"", key);
                            break;
                    }
                }
                switch (description) {
                    case "Brightness":
                        if (type != "float") {
                            warning("Wrong type");
                            break;
                        }
                        message("Found EXPOSURE");
                        var ctrl = new Ctrl(Logic.Ctrl.CtrlType.Exposure, node, id, type, 0, 2, 1);
                        found_ctrls.append(ctrl);
                        break;
                    case "LensPosition":
                        if (type != "float") {
                            warning("Wrong type");
                            break;
                        }
                        message("Found FOCUS");
                        var ctrl = new Ctrl(Logic.Ctrl.CtrlType.Focus, node, id, type, 0, 100, 50);
                        found_ctrls.append(ctrl);
                        break;
                    case "AeEnable":
                        if (type != "bool") {
                            warning("Wrong type");
                            break;
                        }
                        message("Found AE enable");
                        var ctrl = new Ctrl(Logic.Ctrl.CtrlType.ExposureEnable, node, id, type, 0, 1, 1);
                        found_ctrls.append(ctrl);
                        break;
                    case "Contrast":
                        if (type != "float") {
                            warning("Wrong type");
                            break;
                        }
                        message("Found Contrast");
                        var ctrl = new Ctrl(Logic.Ctrl.CtrlType.Contrast, node, id, type, 0, 2, 1);
                        found_ctrls.append(ctrl);
                        break;
                    default:
                        message("Unknown ctrl %s of %s", description, type);
                        break;
                }
                // message("%s: %s -> 0x%lx", description, type, id);
            }
            message("== End of property enumeration ==");
            message("list is %s", found_ctrls == null ? "null" : "not null");
            ctrls[name] = (owned) found_ctrls;
            message("%s %s", name, ctrls[name] == null ? "Is nulllll" : "OK");
            foreach (var ctrl in ctrls.get(name)) {
                message("CTRL %u", ctrl.ctrl_type);
            }
        });

        core.install_object_manager(node_manager);
    }

    public void init() {
        Wp.init(Wp.InitFlags.PIPEWIRE | Wp.InitFlags.SPA_TYPES);
        core = new Wp.Core(null, null, null);
        core.activate.begin(Wp.CoreFeatures.CONNECTED, null, init_manager);
    }
}