namespace pxsim {

    export class EV3OutputState {

        constructor() {
            let data = new Uint8Array(10)
            MMapMethods.register("/dev/lms_pwm", {
                data,
                beforeMemRead: () => {
                    //console.log("pwm before read");
                    for (let i = 0; i < 10; ++i)
                        data[i] = 0
                },
                read: buf => {
                    // console.log("pwm read");
                    if (buf.data.length == 0) return 2;
                    const cmd = buf.data[0];
                    switch (cmd) {
                        case DAL.opOutputTest:
                            const port = buf.data[1];
                            let r = 0;
                            ev3board().getMotor(port)
                                .filter(motor => !motor.isReady())
                                .forEach(motor => r |= (1 << motor.port));
                            pxsim.BufferMethods.setNumber(buf, BufferMethods.NumberFormat.UInt8LE, 2, r);
                            break;
                        default:
                            let v = "vSIM"
                            for (let i = 0; i < buf.data.length; ++i)
                                buf.data[i] = v.charCodeAt(i) || 0
                            break;
                    }
                    return buf.data.length
                },
                write: buf => {
                    if (buf.data.length == 0) return 2;
                    const cmd = buf.data[0];
                    switch (cmd) {
                        case DAL.opProgramStart: {
                            // init
                            return 2;
                        }
                        case DAL.opOutputReset: {
                            // reset
                            const port = buf.data[1];
                            const motors = ev3board().getMotor(port);
                            motors.forEach(motor => motor.reset());
                            return 2;
                        }
                        case DAL.opOutputClearCount:
                            const port = buf.data[1];
                            const motors = ev3board().getMotor(port);
                            motors.forEach(motor => motor.clearCount());
                            break;
                        case DAL.opOutputStepPower:
                        case DAL.opOutputStepSpeed:
                        case DAL.opOutputTimePower:
                        case DAL.opOutputTimeSpeed: {
                            // step speed
                            const port = buf.data[1];
                            const speed = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2); // signed byte
                            // note that b[3] is padding                            
                            const step1 = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int32LE, 4);
                            const step2 = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int32LE, 8);
                            const step3 = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int32LE, 12);
                            const brake = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 16);
                            //console.log(buf);
                            const motors = ev3board().getMotor(port);
                            motors.forEach(motor => motor.setSpeedCmd(cmd, [speed, step1, step2, step3, brake]));
                            return 2;
                        }
                        case DAL.opOutputStepSync:
                        case DAL.opOutputTimeSync: {
                            const port = buf.data[1];
                            const speed = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2); // signed byte
                            // note that b[3] is padding
                            const turnRatio = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int16LE, 4);
                            // b[6], b[7] is padding
                            const stepsOrTime = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int32LE, 8);
                            const brake = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 12);

                            const motors = ev3board().getMotor(port);
                            // cancel any other sync command
                            for(const motor of ev3board().getMotors().filter(motor => motors.indexOf(motor) < 0)) {
                                motor.clearSyncCmd()
                            }

                            // apply commands to all motors
                            for (const motor of motors) {
                                const otherMotor = motors.filter(m => m.port != motor.port)[0];
                                motor.setSyncCmd(
                                    otherMotor,
                                    cmd, [speed, turnRatio, stepsOrTime, brake]);
                            }
                            return 2;
                        }
                        case DAL.opOutputStop: {
                            // stop
                            const port = buf.data[1];
                            const brake = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2);
                            const motors = ev3board().getMotor(port);
                            motors.forEach(motor => motor.stop());
                            return 2;
                        }
                        case DAL.opOutputPower: 
                        case DAL.opOutputSpeed: {
                            // setSpeed
                            const port = buf.data[1];
                            const speed = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2);
                            const motors = ev3board().getMotor(port);
                            motors.forEach(motor => motor.setSpeedCmd(cmd, [speed]));
                            return 2;
                        }
                        case DAL.opOutputStart: {
                            // start
                            const port = buf.data[1];
                            const motors = ev3board().getMotor(port);
                            motors.forEach(motor => motor.start());
                            return 2;
                        }
                        case DAL.opOutputPolarity: {
                            console.error("opOutputPolarity not supported");
                            return 2;
                        }
                        case DAL.opOutputSetType: {
                            const portIndex = buf.data[1]; // not a port but a port index 0..3
                            const large = buf.data[2] == 0x07;
                            const motor = ev3board().getMotors()[portIndex];
                            if (motor)
                                motor.setLarge(large);
                            return 2;
                        }
                        default:
                            console.warn('unknown cmd: ' + cmd);
                            break;
                    }
                    return 2
                },
                ioctl: (id, buf) => {
                    return 2;
                }
            });
        }
    }
}