/**
 * @license Licensed under the Apache License, Version 2.0 (the "License"):
 *          http://www.apache.org/licenses/LICENSE-2.0
 */

/**
 * @fileoverview Implements the required data for functions for selecting
 *     amongst different Arduino boards.
 */
'use strict';

goog.provide('Blockly.Python.Boards');

goog.require('Blockly.Python');


/**
 * Helper function to generate an array of pins (each an array of length 2) for
 * the digital IO.
 * @param {!integer} pinStart Start number for the IOs pin list to generate.
 * @param {!integer} pinEnd Last inclusive number for the list to generate.
 * @return {!array} Two dimensional array with the name and value for the
 *     digital IO pins.
 */
Blockly.Python.Boards.generateDigitalIo = function(pinStart, pinEnd) {
    var digitalIo = [];
    for (var i = pinStart; i < (pinEnd + 1); i++) {
        digitalIo.push([i.toString(), i.toString()]);
    }
    return digitalIo;
};

/**
 * Helper function to generate an array of pins (each an array of length 2) for
 * the analogue IO.
 * @param {!integer} pinStart Start number for the IOs pin list to generate.
 * @param {!integer} pinEnd Last inclusive number for the list to generate.
 * @return {!array} Two dimensional array with the name and value for the
 *     analogue IO pins.
 */
Blockly.Python.Boards.generateAnalogIo = function(pinStart, pinEnd) {
    var analogIo = [];
    for (var i = pinStart; i < (pinEnd + 1); i++) {
        analogIo.push(['A' + i.toString(), 'A' + i.toString()]);
    }
    return analogIo;
};

/**
 * Creates a new Board Profile copying all the attributes from an existing
 * profile, with the exception of the name, and optionally the description and
 * compiler flag.
 * @param {!string} name_ Mandatory new name of the new board profile.
 * @param {string=} description Optional new description of the new profile.
 * @param {string=} compilerFlag Optional new description of the new profile.
 * @return {!Object} Duplicated object with the different argument data.
 */
Blockly.Python.Boards.duplicateBoardProfile =
    function(originalBoard, name_, description, compilerFlag) {
        return {
            name: name_,
            description: description || originalBoard.description,
            compilerFlag: compilerFlag || originalBoard.compilerFlag,
            analogPins: originalBoard.analogPins,
            digitalPins: originalBoard.digitalPins,
            pwmPins: originalBoard.pwmPins,
            serial: originalBoard.serial,
            serialPins: originalBoard.serialPins,
            serialSpeed: originalBoard.serialSpeed,
            spi: originalBoard.spi,
            spiPins: originalBoard.spiPins,
            spiClockDivide: originalBoard.spiClockDivide,
            i2c: originalBoard.i2c,
            i2cPins: originalBoard.i2cPins,
            i2cSpeed: originalBoard.i2cSpeed,
            builtinLed: originalBoard.builtinLed,
            interrupt: originalBoard.interrupt
        }
    };

/** Object to contain all Arduino board profiles. */
Blockly.Python.Boards.profiles = new Object();

/** Arduino Uno board profile. */
Blockly.Python.Boards.profiles.uno = {
    name: 'Arduino Uno',
    description: 'Arduino Uno standard compatible board',
    compilerFlag: 'arduino:avr:uno',
    analogPins: Blockly.Python.Boards.generateAnalogIo(0, 5),
    digitalPins: Blockly.Python.Boards.generateDigitalIo(0, 13).concat(
        Blockly.Python.Boards.generateAnalogIo(0, 5)),
    pwmPins: [
        ['3', '3'],
        ['5', '5'],
        ['6', '6'],
        ['9', '9'],
        ['10', '10'],
        ['11', '11'],
        ['13', '13']
    ],
    serial: [
        ['serial', 'Serial'],
    ],
    serialPins: {
        Serial: [
            ['RX', '0'],
            ['TX', '1']
        ]
    },
    serialSpeed: [
        ['300', '300'],
        ['600', '600'],
        ['1200', '1200'],
        ['2400', '2400'],
        ['4800', '4800'],
        ['9600', '9600'],
        ['14400', '14400'],
        ['19200', '19200'],
        ['28800', '28800'],
        ['31250', '31250'],
        ['38400', '38400'],
        ['57600', '57600'],
        ['115200', '115200']
    ],
    spi: [
        ['SPI', 'SPI']
    ],
    spiPins: {
        SPI: [
            ['MOSI', '11'],
            ['MISO', '12'],
            ['SCK', '13']
        ]
    },
    spiClockDivide: [
        ['2 (8MHz)', 'SPI_CLOCK_DIV2'],
        ['4 (4MHz)', 'SPI_CLOCK_DIV4'],
        ['8 (2MHz)', 'SPI_CLOCK_DIV8'],
        ['16 (1MHz)', 'SPI_CLOCK_DIV16'],
        ['32 (500KHz)', 'SPI_CLOCK_DIV32'],
        ['64 (250KHz)', 'SPI_CLOCK_DIV64'],
        ['128 (125KHz)', 'SPI_CLOCK_DIV128']
    ],
    i2c: [
        ['I2C', 'Wire']
    ],
    i2cPins: {
        Wire: [
            ['SDA', 'A4'],
            ['SCL', 'A5']
        ]
    },
    i2cSpeed: [
        ['100kHz', '100000L'],
        ['400kHz', '400000L']
    ],
    builtinLed: [
        ['BUILTIN_1', '13']
    ],
    interrupt: [
        ['interrupt0', '2'],
        ['interrupt1', '3']
    ]
};

/** Arduino Nano board profile (ATmega328p). */
Blockly.Python.Boards.profiles.nano_328 = {
    name: 'Arduino Nano 328',
    description: 'Arduino Nano with ATmega328 board',
    compilerFlag: 'arduino:avr:nano:cpu=atmega328',
    analogPins: Blockly.Python.Boards.generateAnalogIo(0, 7),
    digitalPins: Blockly.Python.Boards.generateDigitalIo(0, 13).concat(
        Blockly.Python.Boards.generateAnalogIo(0, 7)),
    pwmPins: Blockly.Python.Boards.profiles.uno.pwmPins,
    serial: Blockly.Python.Boards.profiles.uno.serial,
    serialPins: Blockly.Python.Boards.profiles.uno.serialPins,
    serialSpeed: Blockly.Python.Boards.profiles.uno.serialSpeed,
    spi: Blockly.Python.Boards.profiles.uno.spi,
    spiPins: Blockly.Python.Boards.profiles.uno.spiPins,
    spiClockDivide: Blockly.Python.Boards.profiles.uno.spiClockDivide,
    i2c: Blockly.Python.Boards.profiles.uno.i2c,
    i2cPins: Blockly.Python.Boards.profiles.uno.i2cPins,
    i2cSpeed: Blockly.Python.Boards.profiles.uno.i2cSpeed,
    builtinLed: Blockly.Python.Boards.profiles.uno.builtinLed,
    interrupt: Blockly.Python.Boards.profiles.uno.interrupt
};
Blockly.Python.Boards.profiles.nano_168 =
    Blockly.Python.Boards.duplicateBoardProfile(
        Blockly.Python.Boards.profiles.nano_328,
        'Arduino Nano 168',
        'Arduino Nano with ATmega168 compatible board',
        'arduino:avr:nano:cpu=atmega168');

/** Arduino Duemilanove boards profile (ATmega168p, ATmega328p). */
Blockly.Python.Boards.profiles.duemilanove_168p = {
    name: 'Arduino Nano 168p',
    description: 'Arduino Duemilanove with ATmega168p compatible board',
    compilerFlag: 'arduino:avr:diecimila:cpu=atmega168',
    analogPins: Blockly.Python.Boards.profiles.uno.analogPins,
    digitalPins: Blockly.Python.Boards.profiles.uno.digitalPins,
    pwmPins: Blockly.Python.Boards.profiles.uno.pwmPins,
    serial: Blockly.Python.Boards.profiles.uno.serial,
    serialPins: Blockly.Python.Boards.profiles.uno.serialPins,
    serialSpeed: Blockly.Python.Boards.profiles.uno.serialSpeed,
    spi: Blockly.Python.Boards.profiles.uno.spi,
    spiPins: Blockly.Python.Boards.profiles.uno.spiPins,
    spiClockDivide: Blockly.Python.Boards.profiles.uno.spiClockDivide,
    i2c: Blockly.Python.Boards.profiles.uno.i2c,
    i2cPins: Blockly.Python.Boards.profiles.uno.i2cPins,
    i2cSpeed: Blockly.Python.Boards.profiles.uno.i2cSpeed,
    builtinLed: Blockly.Python.Boards.profiles.uno.builtinLed,
    interrupt: Blockly.Python.Boards.profiles.uno.interrupt
};
Blockly.Python.Boards.profiles.duemilanove_328p =
    Blockly.Python.Boards.duplicateBoardProfile(
        Blockly.Python.Boards.profiles.duemilanove_168p,
        'Arduino Duemilanove 328p',
        'Arduino Duemilanove with ATmega328p compatible board',
        'arduino:avr:diecimila');

/** Arduino Mega board profile. */
Blockly.Python.Boards.profiles.mega = {
    name: 'Arduino Mega',
    description: 'Arduino Mega-compatible board',
    compilerFlag: 'arduino:avr:mega',
    analogPins: Blockly.Python.Boards.generateAnalogIo(0, 15),
    //TODO: Check if the Mega can use analogue pins as digital, it would be
    //      logical but it is not clear on the arduino.cc website
    digitalPins: Blockly.Python.Boards.generateDigitalIo(0, 53),
    pwmPins: Blockly.Python.Boards.generateDigitalIo(2, 13).concat(
        Blockly.Python.Boards.generateDigitalIo(44, 46)),
    serial: [
        ['serial', 'Serial'],
        ['serial_1', 'Serial1'],
        ['serial_2', 'Serial2'],
        ['serial_3', 'Serial3']
    ],
    serialPins: {
        Serial: [
            ['TX', '0'],
            ['RX', '1']
        ],
        Serial1: [
            ['TX', '18'],
            ['TX', '19']
        ],
        Serial2: [
            ['TX', '16'],
            ['TX', '17']
        ],
        Serial3: [
            ['TX', '14'],
            ['TX', '15']
        ]
    },
    serialSpeed: Blockly.Python.Boards.profiles.uno.serialSpeed,
    spi: [
        ['SPI', 'SPI']
    ],
    spiPins: {
        SPI: [
            ['MOSI', '51'],
            ['MISO', '50'],
            ['SCK', '52']
        ]
    },
    //TODO: confirm the clock divides are the same for the DUE and UNO
    spiClockDivide: Blockly.Python.Boards.profiles.uno.spiClockDivide,
    i2c: [
        ['I2C', 'Wire']
    ],
    i2cPins: {
        Wire: [
            ['SDA', '20'],
            ['SCL', '21']
        ]
    },
    i2cSpeed: [
        ['100kHz', '100000L'],
        ['400kHz', '400000L']
    ],
    builtinLed: Blockly.Python.Boards.profiles.uno.builtinLed,
    interrupt: [
        ['interrupt0', '2'],
        ['interrupt1', '3'],
        ['interrupt2', '21'],
        ['interrupt3', '20'],
        ['interrupt4', '19'],
        ['interrupt5', '18']
    ]
};

/** Arduino Leonardo board profile. */
Blockly.Python.Boards.profiles.leonardo = {
    name: 'Arduino Leonardo',
    description: 'Arduino Leonardo-compatible board',
    compilerFlag: 'arduino:avr:leonardo',
    analogPins: Blockly.Python.Boards.generateAnalogIo(0, 7),
    digitalPins: Blockly.Python.Boards.generateDigitalIo(0, 13),
    pwmPins: Blockly.Python.Boards.profiles.uno.pwmPins,
    serial: Blockly.Python.Boards.profiles.uno.serial,
    serialPins: Blockly.Python.Boards.profiles.uno.serialPins,
    serialSpeed: Blockly.Python.Boards.profiles.uno.serialSpeed,
    spi: [
        ['SPI', 'SPI']
    ],
    spiPins: {
        SPI: [
            ['MOSI', 'ICSP-4'],
            ['MISO', 'ICSP-1'],
            ['SCK', 'ICSP-3']
        ]
    },
    //TODO: confirm the clock divides are the same for the Leonardo and UNO
    spiClockDivide: Blockly.Python.Boards.profiles.uno.spiClockDivide,
    i2c: [
        ['I2C', 'Wire']
    ],
    i2cPins: {
        Wire: [
            ['SDA', '2'],
            ['SCL', '3']
        ]
    },
    i2cSpeed: Blockly.Python.Boards.profiles.uno.i2cSpeed,
    builtinLed: Blockly.Python.Boards.profiles.uno.builtinLed,
    interrupt: [
        ['interrupt0', '3'],
        ['interrupt1', '2'],
        ['interrupt2', '0'],
        ['interrupt3', '1'],
        ['interrupt4', '17']
    ]
};

/** Arduino Yun board processor and profile is identical to Leonardo. */
Blockly.Python.Boards.profiles.yun =
    Blockly.Python.Boards.duplicateBoardProfile(
        Blockly.Python.Boards.profiles.leonardo,
        'Arduino Yun',
        'Arduino Yun compatible board');

/** Atmel Xplained mini boards profile (atmega328p, atmega328pb, atmega168pb).*/
Blockly.Python.Boards.profiles.atmel_atmega328p_xplained_mini = {
    name: 'Atmel atmega328p Xplained mini',
    description: 'Atmel Xplained mini board with atmega328p (Uno compatible)',
    compilerFlag: 'atmel:avr:atmega328p_xplained_mini',
    analogPins: Blockly.Python.Boards.profiles.uno.analogPins,
    digitalPins: Blockly.Python.Boards.profiles.uno.digitalPins.concat(
        [
            ['20', '20']
        ]),
    pwmPins: Blockly.Python.Boards.profiles.uno.pwmPins,
    serial: Blockly.Python.Boards.profiles.uno.serial,
    serialPins: Blockly.Python.Boards.profiles.uno.serialPins,
    serialSpeed: Blockly.Python.Boards.profiles.uno.serialSpeed,
    spi: Blockly.Python.Boards.profiles.uno.spi,
    spiPins: Blockly.Python.Boards.profiles.uno.spiPins,
    spiClockDivide: Blockly.Python.Boards.profiles.uno.spiClockDivide,
    i2c: Blockly.Python.Boards.profiles.uno.i2c,
    i2cPins: Blockly.Python.Boards.profiles.uno.i2cPins,
    i2cSpeed: Blockly.Python.Boards.profiles.uno.i2cSpeed,
    builtinLed: [
        ['BUILTIN_LED', '13']
    ],
    interrupt: Blockly.Python.Boards.profiles.uno.interrupt,
    builtinButton: [
        ['BUILTIN_BUTTON', '20']
    ]
};
Blockly.Python.Boards.profiles.atmel_atmega328pb_xplained_mini =
    Blockly.Python.Boards.duplicateBoardProfile(
        Blockly.Python.Boards.profiles.atmel_atmega328p_xplained_mini,
        'Atmel atmega328pb Xplained mini',
        'Atmel Xplained mini board with atmega328pb (Arduino Uno compatible)',
        'atmel:avr:atmega328pb_xplained_mini');
Blockly.Python.Boards.profiles.atmel_atmega168pb_xplained_mini =
    Blockly.Python.Boards.duplicateBoardProfile(
        Blockly.Python.Boards.profiles.atmel_atmega328p_xplained_mini,
        'Atmel atmega168pb Xplained mini',
        'Atmel Xplained mini board with atmega168pb (Arduino Uno compatible)',
        'atmel:avr:atmega168pb_xplained_mini');

/** ESP8266 for the Adafruit Huzzah. */
Blockly.Python.Boards.profiles.esp8266_huzzah = {
    name: 'Adafruit Feather HUZZAH',
    description: 'Adafruit HUZZAH ESP8266 compatible board',
    compilerFlag: 'esp8266:esp8266:generic',
    analogPins: [
        ['A0', 'A0']
    ],
    digitalPins: [
        ['0', '0'],
        ['2', '2'],
        ['4', '4'],
        ['5', '5'],
        ['12', '12'],
        ['13', '13'],
        ['14', '14'],
        ['15', '15'],
        ['16', '16']
    ],
    pwmPins: [
        ['2', '2']
    ],
    serial: [
        ['serial', 'Serial']
    ],
    serialPins: {
        Serial: [
            ['RX', 'RX'],
            ['TX', 'TX']
        ]
    },
    serialSpeed: Blockly.Python.Boards.profiles.uno.serial,
    spi: [
        ['SPI', 'SPI']
    ],
    spiPins: {
        SPI: [
            ['MOSI', '13'],
            ['MISO', '12'],
            ['SCK', '14']
        ]
    },
    spiClockDivide: Blockly.Python.Boards.profiles.uno.spiClockDivide,
    i2c: [
        ['I2C', 'Wire']
    ],
    i2cPins: {
        Wire: [
            ['SDA', '4'],
            ['SCL', '5']
        ]
    },
    i2cSpeed: Blockly.Python.Boards.profiles.uno.i2cSpeed,
    builtinLed: [
        ['BUILTIN_1', '0']
    ],
    interrupt: [
        ['interrupt0', '2'],
        ['interrupt1', '3']
    ]
};

/** ESP8266 for the Wemos D1 R2. */
Blockly.Python.Boards.profiles.esp8266_wemos_d1 = {
    name: 'Wemos D1',
    description: 'Wemos D1 R2 compatible board',
    compilerFlag: 'esp8266:esp8266:generic',
    analogPins: [
        ['A0', 'A0']
    ],
    digitalPins: [
        ['D0', 'D0'],
        ['D1', 'D1'],
        ['D2', 'D2'],
        ['D3', 'D3'],
        ['D4', 'D4'],
        ['D5', 'D5'],
        ['D6', 'D7'],
        ['D8', 'D8']
    ],
    pwmPins: [
        ['D1', 'D1'],
        ['D2', 'D2'],
        ['D3', 'D3'],
        ['D4', 'D4'],
        ['D5', 'D5'],
        ['D6', 'D7'],
        ['D8', 'D8']
    ],
    serial: [
        ['serial', 'Serial']
    ],
    serialPins: {
        Serial: [
            ['RX', 'RX'],
            ['TX', 'TX']
        ]
    },
    serialSpeed: Blockly.Python.Boards.profiles.uno.serialSpeed,
    spi: [
        ['SPI', 'SPI']
    ],
    spiPins: {
        SPI: [
            ['MOSI', 'D7'],
            ['MISO', 'D6'],
            ['SCK', 'D5']
        ]
    },
    spiClockDivide: Blockly.Python.Boards.profiles.uno.spiClockDivide,
    i2c: [
        ['I2C', 'Wire']
    ],
    i2cPins: {
        Wire: [
            ['SDA', 'D2'],
            ['SCL', 'D1']
        ]
    },
    i2cSpeed: Blockly.Python.Boards.profiles.uno.i2cSpeed,
    builtinLed: [
        ['BUILTIN_1', 'D4']
    ],
    interrupt: [
        ['D0', 'D0'],
        ['D1', 'D1'],
        ['D2', 'D2'],
        ['D3', 'D3'],
        ['D4', 'D4'],
        ['D5', 'D5'],
        ['D6', 'D7'],
        ['D8', 'D8']
    ]
};

/** Set default profile to Arduino standard-compatible board */
Blockly.Python.Boards.selected = Blockly.Python.Boards.profiles.leonardo;

/**
 * Changes the Arduino board profile selected, which trigger a refresh of the
 * blocks that use the profile.
 * @param {Blockly.Workspace} workspace Workspace to trigger the board change.
 * @param {string} newBoard Name of the new profile to set.
 */
Blockly.Python.Boards.changeBoard = function(workspace, newBoard) {
    if (Blockly.Python.Boards.profiles[newBoard] === undefined) {
        console.log('Tried to set non-existing Arduino board: ' + newBoard);
        return;
    }
    Blockly.Python.Boards.selected = Blockly.Python.Boards.profiles[newBoard];
    // Update the pin out of all the blocks that uses them
    var blocks = workspace.getAllBlocks();
    for (var i = 0; i < blocks.length; i++) {
        var updateFields = blocks[i].updateFields;
        if (updateFields) {
            updateFields.call(blocks[i]);
        }
    }
};

/**
 * Refreshes the contents of a block Field Dropdown.
 * This is use to refresh the blocks after the board profile has been changed.
 * @param {!Blockly.Block} block Generated code.
 * @param {!string} fieldName Name of the block FieldDropdown to refresh.
 * @param {!string} boardKey Name of the board profile property to fetch.
 */
Blockly.Python.Boards.refreshBlockFieldDropdown =
    function(block, fieldName, boardKey) {
        var field = block.getField(fieldName);
        var fieldValue = field.getValue();
        var dataArray = Blockly.Python.Boards.selected[boardKey];
        field.menuGenerator_ = dataArray;

        var currentValuePresent = false;
        for (var i = 0; i < dataArray.length; i++) {
            if (fieldValue == dataArray[i][1]) {
                currentValuePresent = true;
            }
        }
        // If the old value is not present any more, add a warning to the block.
        if (!currentValuePresent) {
            block.setWarningText(
                'The old pin value ' + fieldValue + ' is no longer available.', 'bPin');
        } else {
            block.setWarningText(null, 'bPin');
        }
    };