void KWP2000::enableDebug(HardwareSerial *debug_serial, const uint8_t debug_level, const uint32_t debug_baudrate)
{
    _debug = debug_serial;
    _debug_level = debug_level;
    _debug->begin(debug_baudrate);
    _debug_enabled = true;

    if (_debug_level >= DEBUG_LEVEL_DEFAULT)
    {
        _debug->println(F("Debug enabled"));
    }
}

/*
KWP2000.cpp

*/

#ifndef KWP2000_cpp
#define KWP2000_cpp

#include <Arduino.h>

#include "KWP2000.h"
#include "ISO.h"

#warning "Not ready for Yamaha and Honda, before using this open an issue on github"

#define maybe 2 ///< used when we don't know yet the behaviour of the K-Line

#define TO_FAHRENHEIT(x) x * 1.8 + 32                                                   ///< the formula for the conversion from celsius to fahrenheit
#define TO_MPH(x) x / 1.609                                                             ///< the formula for the conversion from km/h to mp/h
#define LEN(x) ((sizeof(x) / sizeof(0 [x])) / ((size_t)(!(sizeof(x) % sizeof(0 [x]))))) ///< complex but safe macro for the length

/**
 * @brief This is a a collection of possible ECU Errors
 */
enum error_enum
{
    EE_TEST,   ///< for test purposes
    EE_START,  ///< unable to start comunication
    EE_STOP,   ///< unable to stop comunication
    EE_TO,     ///< data is not for us
    EE_FROM,   ///< data don't came from the ECU
    EE_CS,     ///< checksum error
    EE_ECHO,   ///< echo error
    EE_UNEX,   ///< unexpected error
    EE_HEADER, ///< header not regular
    EE_USER,   ///< error due to wrong call of a function
    EE_CONFIG, ///< key bytes not regular
    EE_P3MAX,  ///< time out communication
    EE_CR,     ///< check response error
    EE_ATP,    ///< problem setting/reading the timing parameter
    EE_WR,     ///< We get a reject for a request we didn't sent
    EE_US,     ///< not supported, yet
    EE_TOTAL   ///< this is just to know how many possible errors are in this enum
};

/**
 * @brief Constructor for the KWP2000 class
 *
 * @param kline_serial The Serial port you will use to communicate with the ECU
 * @param k_out_pin The TX pin of this serial
 * @param brand the brand of your bike: `SUZUKI`, `KAWASAKI`, `YAMAHA` or `HONDA`
 * @param model The model of the bike
 */
KWP2000::KWP2000(HardwareSerial *kline_serial, const uint8_t k_out_pin, const brand brand, const model model)
{
    _kline = kline_serial;
    _k_out_pin = k_out_pin;
    _brand = brand;
    _model = model;
    set_bike_specific_values(_brand, _model);

    _response = new uint8_t[ISO_MAX_DATA];
}

/**
 * @brief Enable the debug of the communication
 *
 * @param debug_serial The Serial port you will use for the debug information
 * @param debug_level The verbosity of the debug, default to `DEBUG_LEVEL_DEFAULT`
 * @param debug_baudrate The baudrate for the debug, default to `115200`
 */

/**
 * @brief Change the debug level
 *
 * @param debug_level choose between DEBUG_LEVEL_NONE DEBUG_LEVEL_DEFAULT DEBUG_LEVEL_VERBOSE
 */
void KWP2000::setDebugLevel(const uint8_t debug_level)
{
    _debug_level = debug_level;
    if (_debug_level == DEBUG_LEVEL_NONE)
    {
        _debug_enabled = false;
    }
    else
    {
        _debug_enabled = true;
    }
    if (_debug_level >= DEBUG_LEVEL_DEFAULT)
    {
        _debug->print(F("Debug level: "));
        _debug->println(debug_level == DEBUG_LEVEL_DEFAULT ? "default" : "verbose");
    }
}

/**
 * @brief Disable the debug
 */
void KWP2000::disableDebug()
{
    if (_debug_level >= DEBUG_LEVEL_DEFAULT)
    {
        _debug->println(F("Debug disabled"));
    }
    _debug->end();
    _debug_enabled = false;
}

/**
 * @brief Only for Suzuki: Enable the Dealer Mode
 *
 * @param dealer_pin The pin you will use to control it
 */
void KWP2000::enableDealerMode(const uint8_t dealer_pin)
{
    if (_brand != SUZUKI)
    {
        return;
    }
    _dealer_enabled = true;
    _dealer_pin = dealer_pin;
    pinMode(_dealer_pin, OUTPUT);
    digitalWrite(_dealer_pin, LOW);
}

/**
 * @brief Only for Suzuki: Enable/Disable the Dealer Mode
 *
 * @param dealer_mode Choose between true/false
 */
void KWP2000::setDealerMode(const uint8_t dealer_mode)
{
    if (_dealer_enabled == false)
    {
        return;
    }
    _dealer_mode = dealer_mode;
    digitalWrite(_dealer_pin, _dealer_mode);
    if (_debug_level >= DEBUG_LEVEL_DEFAULT)
    {
        _debug->print(F("Dealer mode: "));
        _debug->println(_dealer_mode == true ? "Enabled" : "Disabled");
    }
}

/**
 * @brief Only for Suzuki: Check the dealer status
 *
 * @return true if it is in dealer mode
 * @return false otherwise
 */
bool KWP2000::getDealerMode()
{
    if (_dealer_enabled == false)
    {
        return false;
    }
    return digitalRead(_dealer_pin);
}

/**
 * @brief Choose to use imperial system for the sensors values (mp/h, Fahrenheit)
 */
void KWP2000::use_imperial()
{
    _use_metric_system = false;
}

/**
 * @brief Choose to use metric system for the sensors values (km/h, Celsius)
 */
void KWP2000::use_metric()
{
    _use_metric_system = true;
}

/**
 * @brief Initialize the the communication through the K-Line
 *
 * @return `false` until the connection is not established; `true` if we connected; a `negative number` otherwise
 */
int8_t KWP2000::initKline()
{
    if (_ECU_status == true)
    {
        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("\nAlready connected"));
        }
        return 1;
    }

    if (_init_sequence_started == false)
    {
        _init_sequence_started = true;

        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("\nInitialize K-line"));
        }

        /*
        if (ISO_T_IDLE == 0)
        {
            // first attempt to init the k-line
            ISO_T_IDLE = ISO_T_IDLE_NEW;
        }
        else if (bitRead(_ECU_error, EE_P3MAX) == 1)
        {
            // after the connection has been lost due to time out of P3
            ISO_T_IDLE = 100; // should be 0
        }
        else
        {
            // after a stopKline
            ISO_T_IDLE = ISO_T_P3_MAX;
        }
        */

        _use_length_byte = false;
        if (_brand == SUZUKI || _brand == KAWASAKI)
        {
            _use_target_source_address = true;
        }
        else if (_brand == YAMAHA || _brand == HONDA)
        {
            _use_target_source_address = false;
        }

        _kline->end();

        digitalWrite(_k_out_pin, HIGH);
        pinMode(_k_out_pin, OUTPUT);

        _start_time = millis();
        _elapsed_time = 0;
        _init_phase = 0;
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Starting sequence"));
        }
    }
    _elapsed_time = millis() - _start_time;

    switch (_init_phase)
    {
    case 0: // this is a 1000ms HIGH signal
        if (_elapsed_time > ISO_T_IDLE)
        {
            _init_phase++;
        }
        break;
    case 1:
        digitalWrite(_k_out_pin, LOW);
        _init_phase++;
        break;
    case 2: // this is a 25ms LOW signal
        if (_elapsed_time > ISO_T_IDLE + ISO_T_INIL)
        {
            _init_phase++;
        }
        break;
    case 3:
        digitalWrite(_k_out_pin, HIGH);
        _init_phase++;
        break;
    case 4: // this is a 25ms HIGH signal
        if (_elapsed_time > ISO_T_IDLE + ISO_T_WUP)
        {
            _init_phase++;
        }
        break;
    case 5:
        _init_sequence_started = false;
        _start_time = 0;
        _elapsed_time = 0;
        _kline->begin(ISO_BAUDRATE);

        if (!handleRequest(start_com, LEN(start_com), true))
        {
            if (_debug_level >= DEBUG_LEVEL_DEFAULT)
            {
                _debug->println(F("Initialization failed"));
            }
            _ECU_status = false;
            setError(EE_START);
            return -2;
        }

        configureKline(); // maybe honda e yamaha shouldn't run this

        if (_brand == KAWASAKI)
        {
            if (_debug_level >= DEBUG_LEVEL_DEFAULT)
            {
                _debug->println(F("First handshake ok, now starting diagnostic session"));
            }
            if (!handleRequest(start_diagnostic, LEN(start_diagnostic)))
            {
                if (_debug_level >= DEBUG_LEVEL_DEFAULT)
                {
                    _debug->println(F("Failed to start diagnostic"));
                }
                _ECU_status = false;
                setError(EE_START);
                return -2;
            }
            if (_debug_level >= DEBUG_LEVEL_DEFAULT)
            {
                _debug->println(F("Start diagnostic successful"));
            }
        }

        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("ECU connected"));
        }
        _connection_time = millis();
        _ECU_status = true;
        _ECU_error = 0;
        return 1;
        break;
    default:
        break;
    }

    return 0;
}

/**
 * @brief Close the communication with the motorbike
 *
 * @return `false` until the connection is not closed; `true` if there aren't any errors, a `negative number` otherwise
 */
int8_t KWP2000::stopKline()
{
    if (_ECU_status == false)
    {
        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("\nAlready disconnected"));
        }
        return 1;
    }

    if (_stop_sequence_started == false)
    {
        _stop_sequence_started = true;

        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Closing K-line"));
        }

        if (handleRequest(stop_com, LEN(stop_com)) == true)
        {
            //closed without problems
            _ECU_error = 0;
        }
        else
        {
            //not closed correctly
            setError(EE_STOP);
        }

        // reset all
        for (uint16_t i = 0; i < ISO_MAX_DATA; i++)
        {
            _response[i] = 0;
        }
        _response_len = 0;
        _response_data_start = 0;

        _last_correct_response = 0;
        _last_data_print = 0;
        _last_sensors_calculated = 0;
        _last_status_print = 0;
        _connection_time = 0;

        _kline->end();

        _ECU_error = 0;
        _start_time = millis();
        _elapsed_time = 0;
    }

    _elapsed_time = millis() - _start_time;

    if (_elapsed_time < ISO_T_P3_MAX)
    {
        return 0;
    }
    else
    {
        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("ECU disconnected"));
        }
        _ECU_status = false;
        _start_time = 0;
        _elapsed_time = 0;
        _stop_sequence_started = false;
        return 1;
    }
}

/**
 * @brief Send a request to the ECU asking for data from all the sensors, then you can access the single sensor with the `get*()` functions or you can see all of them with the `printSensorsData()`
 */
void KWP2000::requestSensorsData()
{
    if (_ECU_status == false)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Not connected to the ECU"));
        }
        setError(EE_USER);
        return;
    }

    if (_debug_level >= DEBUG_LEVEL_DEFAULT)
    {
        _debug->println(F("Requesting Sensors Data"));
    }

    if (_brand == SUZUKI)
    {
        handleRequest(suzuki_request_sens, LEN(suzuki_request_sens));
        //GPS (Gear Position Sensor)
        _GPS = _response[IDX_GPS];

        // Clutch
        _CLUTCH = _response[IDX_CLUTCH];

        //RPM (Rights Per Minutes) it is split between two byte
        _RPM = _response[IDX_RPM_H] * 10 + _response[IDX_RPM_L] / 10;

        // Speed
        _SPEED = _response[IDX_SPEED] * 2;

        // TPS (Throttle Position Sensor)
        _TPS = 125 * (_response[IDX_TPS] - 55) / (256 - 55);

        // STPS (Secondary Throttle Position Sensor)
        _STPS = _response[IDX_STPS] / 2.55;

        // IAP (Intake Air Pressure)
        _IAP = _response[IDX_IAP] * 4 * 0.136;

        // IAT (Intake Air Temperature)
        _IAT = (_response[IDX_IAT] - 48) / 1.6;

        // ECT (Engine Coolant Temperature)
        _ECT = (_response[IDX_ECT] - 48) / 1.6;

        // Voltage
        _VOLT = _response[IDX_VOLT] * 100 / 126;
    }
    else if (_brand == KAWASAKI)
    {
        handleRequest(kawasaki_request_gps, LEN(kawasaki_request_gps));
        _GPS = _response[IDX_GPS];

        handleRequest(kawasaki_request_rpm, LEN(kawasaki_request_rpm));
        _RPM = ((_response[IDX_RPM_H] * 255 + _response[IDX_RPM_L]) / 255.0) * 100;

        handleRequest(kawasaki_request_speed, LEN(kawasaki_request_speed));
        _SPEED = _response[IDX_SPEED];

        handleRequest(kawasaki_request_tps, LEN(kawasaki_request_tps));
        _TPS = _response[IDX_TPS];

        handleRequest(kawasaki_request_iap, LEN(kawasaki_request_iap));
        _IAP = _response[IDX_IAP] * 4 * 0.136;

        handleRequest(kawasaki_request_iat, LEN(kawasaki_request_iat));
        _IAT = (_response[IDX_IAT] - 48) / 1.6;

        handleRequest(kawasaki_request_ect, LEN(kawasaki_request_ect));
        _ECT = (_response[IDX_ECT] - 48) / 1.6;
    }
    else if (_brand == YAMAHA)
    {
        handleRequest(yamaha_request_sens, LEN(yamaha_request_sens));
    }
    else if (_brand == HONDA)
    {
        handleRequest(honda_request_sens, LEN(honda_request_sens));
    }

    _last_sensors_calculated = millis();
}

/**
 * @brief Read the Diagnostic Trouble Codes (DTC) from the ECU
 *
 * @param Default to `READ_ONLY_ACTIVE`, accepted values `READ_TOTAL`, `READ_ALL`
 */
void KWP2000::readTroubleCodes(const trouble_code which)
{
    if (_ECU_status == false)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Not connected to the ECU"));
        }
        setError(EE_USER);
        return;
    }

    if (which == READ_TOTAL)
    {
        handleRequest(trouble_codes_all, LEN(trouble_codes_all));
    }
    else if (which == READ_ONLY_ACTIVE)
    {
        handleRequest(trouble_codes_only_active, LEN(trouble_codes_only_active));
    }
    else if (which == READ_ALL)
    {
        handleRequest(trouble_codes_with_status, LEN(trouble_codes_with_status));
    }

    const uint8_t DTC_total = _response[_response_data_start + 1]; // Diagnosis trouble codes
    if (_debug_level >= DEBUG_LEVEL_DEFAULT)
    {
        _debug->print("There are ");
        _debug->print(DTC_total);
        _debug->println(" errors\n");
        for (uint8_t n = _response_data_start + 2; n < _response_len; n++)
        {
            _debug->print(_response[n]); // todo needed more test to understand the DTC number, value and status parameters
        }
        _debug->println();
    }
}

/**
 * @brief Clear the DTC from the ECU
 *
 * @param code Optional. Only the passed `code` will be cleared
 */
void KWP2000::clearTroubleCodes(const uint8_t code)
{
    if (_ECU_status == false)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Not connected to the ECU"));
        }
        setError(EE_USER);
        return;
    }

    if (code == 0x00) // Clear all
    {
        handleRequest(clear_trouble_codes, LEN(clear_trouble_codes));
    }
    else // Clear a single error provided by the user
    {
        const uint8_t to_clear[] = {clear_trouble_codes[0], code};
        handleRequest(to_clear, LEN(to_clear));
    }
}

/**
 * @brief Keep the connection through the K-Line alive
 *
 * @param time Optional. It is calculated automatically to be a safe interval
 */
void KWP2000::keepAlive(uint16_t time)
{
    if (_kline->available() > 0)
    {
        // the ECU wants to tell something
        uint8_t in;
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println("Serial buffer not empty:");
        }
        while (_kline->available() > 0)
        {
            in = _kline->read();
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(in, HEX);
            }
        }
    }

    if (_ECU_status == false)
    {
        return; // If it is not connected it is meaningless to send a request
    }

    if (millis() - _last_correct_response >= ISO_T_P3_MAX)
    {
        // The connection has been lost
        if (_stop_sequence_started == false)
        {
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(F("\nConnection expired"));
            }
            _ECU_status = false;
            _last_data_print = 0;
            _last_sensors_calculated = 0;
            _last_status_print = 0;
            _connection_time = 0;
            _kline->end();
            setError(EE_P3MAX);
        }
        return;
    }

    if (time == 0)
    {
        time = _keep_iso_alive;
    }

    if (time > ISO_T_P3_MAX)
    {
        // Prevent human's errors
        time = _keep_iso_alive;
        setError(EE_USER);
    }

    if (millis() - _last_correct_response <= time)
    {
        // We talked with the ECU not so much time ago
        return;
    }

    if (_debug_level == DEBUG_LEVEL_VERBOSE)
    {
        _debug->print(F("\nKeeping connection alive\nLast:"));
        _debug->println(millis() - _last_correct_response);
    }

    // Send a dummy request
    if (_brand == SUZUKI)
    {
        handleRequest(tester_present_with_answer, LEN(tester_present_with_answer));
    }
    else if (_brand == KAWASAKI)
    {
        handleRequest(kawasaki_request_gps, LEN(kawasaki_request_gps));
    }
    else if (_brand == YAMAHA)
    {
        handleRequest(tester_present_with_answer, LEN(tester_present_with_answer));
    }
    else if (_brand == HONDA)
    {
        handleRequest(tester_present_with_answer, LEN(tester_present_with_answer));
    }
}

/**
 * @brief This function is the core of the library. You just need to give a PID and it will generate the header, calculate the checksum and try to send the request.
 *          Then it will check if the response is correct and if now it will try to send the request another two times, all is based on the ISO14230
 *
 * @param to_send The PID you want to send, see PID.h for more detail
 * @param send_len The length of the PID (use `sizeof` to get it)
 * @param try_once Optional, default to `false`. Choose if you want to try to send the request 3 times in case of error
 * @return `true` if the request has been sent and a correct response has been received, `false` otherwise
 */
int8_t KWP2000::handleRequest(const uint8_t to_send[], const uint8_t send_len, const uint8_t try_once)
{
    uint8_t attempt;
    uint8_t completed = false;

    if (try_once == true)
    {
        attempt = 3;
    }
    else
    {
        attempt = 1;
    }

    while (attempt <= 3 && completed == false)
    {
        sendRequest(to_send, send_len);
        listenResponse();
        if (checkResponse(to_send) == true)
        {
            completed = true;
        }
        else
        {
            if (_debug_level == DEBUG_LEVEL_VERBOSE && try_once == false)
            {
                _debug->print(F("Attempt "));
                _debug->print(attempt);
                _debug->print(F(" not luckly"));
                _debug->println(attempt < 3 ? ", trying again"
                                            : "\nWe wasn't able to comunicate");
            }
            attempt++;
        }
    }

    if (completed == true)
    {
        return true;
    }
    else
    {
        // We made more than 3 attemps so there is a problem
        return false;
    }
}

/**
 * @brief Read the time parameter and set some library timing based on them
 */
/*
void KWP2000::checkTimingParameter()
{
    if (_ECU_status == false)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Not connected to the ECU"));
        }
        setError(EE_USER);
        return;
    }

    if (_debug_level == DEBUG_LEVEL_VERBOSE)
    {
        _debug->print(F("Reading timing limits"));
    }
    if (handleRequest(atp_read_limits, LEN(atp_read_limits)) == true)
    {
        accessTimingParameter(true);
    }
    else
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Error reading timing limits"));
        }
        setError(EE_ATP);
    }

    if (_debug_level == DEBUG_LEVEL_VERBOSE)
    {
        _debug->print(F("Reading current timing paramenters"));
    }
    if (handleRequest(atp_read_current, LEN(atp_read_current)) == true)
    {
        accessTimingParameter(false);
    }
    else
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Error reading current timing paramenters"));
        }
        setError(EE_ATP);
    }
}
*/
/**
 * @brief Print the Timing Parameters from the ECU
 *
 * @param read_only Optional, default to `true`. This avoid the possibility to unintentionally change them
 */
/*
void KWP2000::accessTimingParameter(const uint8_t read_only)
{
    uint8_t p2_min_temp = _response[_response_data_start + 2];
    uint32_t p2_max_temp = _response[_response_data_start + 3];
    uint16_t p3_min_temp = _response[_response_data_start + 4];
    uint32_t p3_max_temp = _response[_response_data_start + 5];
    uint16_t p4_min_temp = _response[_response_data_start + 6];

    if (p2_max_temp <= 0xF0)
    {
        p2_max_temp *= 25;
    }
    else if ((p2_max_temp > 0xF0) && (p2_max_temp < 0xFF))
    {
        p2_max_temp = (p2_max_temp & 0xF) * 256 * 25;
    }
    else if (p2_max_temp == 0xFF)
    {
        p2_max_temp = 89601;
        setError(EE_ATP);
    }

    if (p3_max_temp <= 0xF0)
    {
        p3_max_temp *= 25;
    }
    else if ((p3_max_temp > 0xF0) && (p3_max_temp < 0xFF))
    {
        p3_max_temp = (p3_max_temp & 0xF) * 256 * 25;
    }
    else if (p3_max_temp == 0xFF)
    {
        p3_max_temp = 89601;
        setError(EE_ATP);
    }

    // any of these condition is an error
    if ((p2_min_temp > p2_max_temp) || (p3_min_temp > p3_max_temp) || (p4_min_temp > ISO_T_P4_MAX_LIMIT) || (p3_min_temp < p4_min_temp))
    {
        setError(EE_ATP);
    }

    if (read_only == false)
    {
        ISO_T_P2_MIN = p2_min_temp;
        ISO_T_P2_MAX = p2_max_temp;
        ISO_T_P3_MIN = p3_min_temp;
        ISO_T_P3_MAX = p3_max_temp;
        ISO_T_P4_MIN = p4_min_temp;

        // we set a safe margin to ask data enough often to the ECU
        _keep_iso_alive = p3_max_temp / 4;
    }

    if (_debug_level == DEBUG_LEVEL_VERBOSE)
    {
        _debug->println(F("Timing Parameter from the ECU:"));
        _debug->print("Errors:\t");
        _debug->println(bitRead(_ECU_error, EE_ATP) == 1 ? "Yes" : "No");
        _debug->print(F("P2 min:\t"));
        _debug->println(p2_min_temp);
        _debug->print(F("P2 max:\t"));
        _debug->println(p2_max_temp);
        _debug->print(F("P3 min:\t"));
        _debug->println(p3_min_temp);
        _debug->print(F("P3 max:\t"));
        _debug->println(p3_max_temp);
        _debug->print(F("P4 min:\t"));
        _debug->println(p4_min_temp);
        _debug->println();
    }
}
*/
/**
 * @brief Reset the Timing Parameters to the default settings from the ECU
 */
/*
void KWP2000::resetTimingParameter()
{
    if (_ECU_status == false)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Not connected to the ECU"));
        }
        setError(EE_USER);
        return;
    }
    if (_debug_level >= DEBUG_LEVEL_DEFAULT)
    {
        _debug->println(F("Resetting time parameters to default"));
    }
    if (handleRequest(atp_set_default, LEN(atp_set_default)) == true)
    {
        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("Changed"));
        }
    }
    else
    {
        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("Not changed"));
        }
    }
    accessTimingParameter(true);
}
*/
/**
 * @brief Change the Timing Parameters to custom ones
 *
 * @param new_atp Array of 5 elements containing the new parameters
 * @param new_atp_len The length of the array (use `sizeof` to get it)
 */
/*
void KWP2000::changeTimingParameter(uint32_t new_atp[], const uint8_t new_atp_len)
{
    if (_ECU_status == false)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Not connected to the ECU"));
        }
        setError(EE_USER);
        return;
    }

    if (_debug_level >= DEBUG_LEVEL_DEFAULT)
    {
        _debug->println(F("Changing timing parameter"));
    }

    if (new_atp_len != 5)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("The time paramenter should be an array of 5 elements"));
        }
        setError(EE_USER);
        return;
    }

    if (new_atp[0] > ISO_T_P2_MIN_LIMIT)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("P2 min too hight"));
        }
        return;
    }

    if (new_atp[1] > ISO_T_P2_MAX_LIMIT)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("P2 max too hight"));
        }
        return;
    }

    if (new_atp[2] > 255)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("P3 min too hight"));
        }
        return;
    }

    if (new_atp[3] > ISO_T_P3_MAX_LIMIT)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("P3 max too hight"));
        }
        return;
    }

    if (new_atp[4] > ISO_T_P4_MAX_LIMIT)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("P4 min too hight"));
        }
        return;
    }
    // all check passed

    // convert the data if P2 and P3 max are too high for a tiny byte
    uint32_t p2_max_temp = new_atp[1];
    uint32_t p3_max_temp = new_atp[3];

    if (p2_max_temp <= 6000)
    {
        p2_max_temp = p2_max_temp / 25;
    }
    else if (p2_max_temp > 6000)
    {
        p2_max_temp = (p2_max_temp / 256 / 25) | 0xf0;
    }
    new_atp[1] = p2_max_temp;

    if (p3_max_temp <= 6000)
    {
        p3_max_temp = p3_max_temp / 25;
    }
    else if (p3_max_temp > 6000)
    {
        p3_max_temp = (p3_max_temp / 256 / 25) | 0xf0;
    }
    new_atp[3] = p3_max_temp;

    // we need to create a new array made from the atp_set_given + new_atp + checksum
    uint8_t pid_temp[7];
    for (uint8_t n = 0; n < 7; n++)
    {
        if (n < 2) // atp_set_given
        {
            pid_temp[n] = atp_set_given[n];
        }
        else // new_atp
        {
            pid_temp[n] = new_atp[n - 2];
        }
    }

    // send it
    if (handleRequest(pid_temp, LEN(pid_temp)) == true)
    {
        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("Changed"));
        }
    }
    else
    {
        if (_debug_level >= DEBUG_LEVEL_DEFAULT)
        {
            _debug->println(F("Not changed"));
        }
    }

    // check if our values has been setted correctly
    accessTimingParameter(true);
}
*/
/////////////////// PRINT and GET ///////////////////////

/**
 * @brief Print a rich and useful set of information about the ECU status and errors
 *
 * @param time Optional, default to 5 seconds. The time between one print and the next one
 */
void KWP2000::printStatus(uint16_t time)
{
    if (time == false)
    {
        // skip
        return;
    }

    if (millis() - _last_status_print <= time)
    {
        // not enough time has passed since last time we printed the status
        return;
    }

    if (_debug_enabled == true)
    {
        _debug->print(F("\n---- STATUS ----\n"));
        _debug->print(F("Connection:\t\t"));
        _debug->println(_ECU_status == 1 ? "Connected" : "Not connected");
        _debug->print(F("Errors:\t\t\t"));
        _debug->println(_ECU_error == 0 ? "No" : "Yes");

        if (_last_correct_response != 0)
        {
            _debug->print(F("Last data:\t\t"));
            _debug->print((millis() - _last_correct_response) / 1000.0);
            _debug->print(F(" seconds ago"));
        }

        if (_connection_time != 0)
        {
            _debug->print(F("Connection time:"));
            _debug->print((millis() - _connection_time) / 1000.0);
            _debug->println(" seconds ago");
        }

        _debug->print(F("Baudrate:\t\t"));
        _debug->println(ISO_BAUDRATE);
        _debug->print(F("K-line TX pin:\t\t"));
        _debug->println(_k_out_pin);
        if (_brand == SUZUKI)
        {
            _debug->print(F("Dealer pin:\t\t"));
            _debug->println(_dealer_pin);
            _debug->print(F("Dealer mode:\t"));
            _debug->println(_dealer_mode == 1 ? "Enabled" : "Disabled");
        }
        //other stuff?
        if (_ECU_error != 0)
        {
            _debug->print(F("\n---- ERRORS ----\n"));
            for (uint8_t i = 0; i < EE_TOTAL; i++)
            {
                if (bitRead(_ECU_error, i) != 0)
                {
                    switch (i)
                    {
                    case EE_USER:
                        _debug->println(F("We called some function in a wrong way"));
                        break;
                    case EE_START:
                        _debug->println(F("Unable to start comunication"));
                        break;
                    case EE_STOP:
                        _debug->println(F("Unable to stop comunication"));
                        break;
                    case EE_TO:
                        _debug->println(F("Data is not for us"));
                        break;
                    case EE_FROM:
                        _debug->println(F("Data don't came from the ECU"));
                        break;
                    case EE_CS:
                        _debug->println(F("Checksum error"));
                        break;
                    case EE_ECHO:
                        _debug->println(F("Echo error"));
                        break;
                    case EE_UNEX:
                        _debug->println(F("Unexpected error"));
                        break;
                    case EE_HEADER:
                        _debug->println(F("Unexpected header"));
                        break;
                    case EE_CONFIG:
                        _debug->println(F("The key bytes are probably wrong"));
                        break;
                    case EE_P3MAX:
                        _debug->println(F("Time out of the communication"));
                        break;
                    case EE_CR:
                        _debug->println(F("Check response error"));
                        break;
                    case EE_ATP:
                        _debug->println(F("Problem setting the timing parameter"));
                        break;
                    case EE_WR:
                        _debug->println(F("We get a reject for a request we didn't sent"));
                        break;
                    case EE_US:
                        _debug->println(F("Unsupported, yet"));
                        break;
                    default:
                        _debug->print(F("Did I forget any enum?"));
                        _debug->println(i);
                        break;
                    }
                }
            }
        }
        _debug->print(F("---- ------- ----\n\n"));
        _last_status_print = millis();
    }
    else
    {
        setError(EE_USER);
    }
}

/**
 * @brief Print all the sensors data from the ECU, you need to run `requestSensorsData()` before
 */
void KWP2000::printSensorsData()
{
    if (_last_sensors_calculated == 0)
    {
        // we didn't run requestSensorsData
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->print(F("requestSensorsData() needs to be called before"));
        }
        setError(EE_USER);
        return;
    }

    if (_debug_enabled == true)
    {
        _debug->print(F("---- SENSORS ----\n"));
        _debug->print(F("Calculated: "));
        _debug->print(millis() - _last_sensors_calculated);
        _debug->println(F(" milliseconds ago"));
        _debug->print(F("GPS:\t"));
        _debug->println(_GPS);
        _debug->print(F("CLUTCH:\t"));
        _debug->println(_CLUTCH);
        _debug->print(F("RPM:\t"));
        _debug->println(_RPM);
        _debug->print(F("Speed:\t"));
        _debug->println(_SPEED);
        _debug->print(F("TPS:\t"));
        _debug->println(_TPS);
        _debug->print(F("STPS:\t"));
        _debug->println(_STPS);
        _debug->print(F("IAP:\t"));
        _debug->println(_IAP);
        _debug->print(F("IAT:\t"));
        _debug->println(_IAT);
        _debug->print(F("ECT:\t"));
        _debug->println(_ECT);
        _debug->print(F("VOLT:\t"));
        _debug->println(_VOLT);

        _debug->print(F("---- ------- ----\n"));
        _last_data_print = millis();
    }
    else
    {
        setError(EE_USER);
    }
}

/**
 * @brief Print the last response received from the ECU
 */
void KWP2000::printLastResponse()
{
    if (_debug_enabled == true)
    {
        _debug->println(F("Last Response from the ECU:"));
        for (uint8_t n = 0; n < _response_len; n++)
        {
            _debug->println(_response[n], HEX);
        }
    }
    else
    {
        setError(EE_USER);
    }
}

/**
 * @brief Get the connection status
 *
 * @return It could be `true` or `false`
 */
int8_t KWP2000::getStatus()
{
    return _ECU_status;
}

/**
 * @brief Get* the ECU sensor value you need
 * GPS: Gear Position Sensor
 * CLUTCH: Know if the clutch is pressed or not
 * RPM: Revolutions per Minutes
 * SPEED: Speed
 * TPS: Throttle Position Sensor
 * STPS: Secondary Throttle Position Sensor
 * IAP: Intake Air Pressure, in kPa
 * IAT: Intake Air Temperature
 * ECT: Engine Coolant Temperature
 * VOLT: Battery voltage
 * @return The sensor value from the ECU
 */
uint8_t KWP2000::getGPS()
{
    return _GPS;
}

uint8_t KWP2000::getCLUTCH()
{
    return _CLUTCH;
}

uint8_t KWP2000::getRPM()
{
    return _RPM;
}

uint8_t KWP2000::getSPEED()
{
    if (_use_metric_system == false)
    {
        _SPEED = TO_MPH(_SPEED);
    }
    return _SPEED;
}

uint8_t KWP2000::getTPS()
{
    return _TPS;
}

uint8_t KWP2000::getSTPS()
{
    return _STPS;
}

uint8_t KWP2000::getIAP()
{
    return _IAP;
}

uint8_t KWP2000::getIAT()
{
    if (_use_metric_system == false)
    {
        _IAT = TO_FAHRENHEIT(_IAT);
    }
    return _IAT;
}

uint8_t KWP2000::getECT()
{
    if (_use_metric_system == false)
    {
        _ECT = TO_FAHRENHEIT(_ECT);
    }
    return _ECT;
}

float KWP2000::getVOLT()
{
    return _VOLT;
}

/**
 * @brief Generate and send a request to the ECU
 *
 * @param pid The PID you want to send
 * @param pid_len the length of the PID, get it with `sizeof()`
 * @param wait_to_send_all Choose to wait until the tx buffer is empty
 * @param use_delay Choose to wait at the end of the function or to do other tasks
 */
void KWP2000::sendRequest(const uint8_t pid[], const uint8_t pid_len, const uint8_t wait_to_send_all, const uint8_t use_delay)
{
    uint8_t request[20];
    uint8_t request_len = 0;
    uint8_t echo = 0;
    uint8_t header_len = 1; // minimun length

    // create the request
    // make the header
    if (_use_length_byte == true)
    {
        //we use the length byte
        request[0] = format_physical;
        request[3] = pid_len;
        header_len += 1;
    }
    else // don't use the length_byte
    {
        if (pid_len >= 64)
        {
            // we are forced to use the length byte
            request[0] = format_physical;
            request[3] = pid_len;
            header_len += 1;
        }
        else
        {
            // the length byte is "inside" the format
            request[0] = format_physical | pid_len;
        }
    }

    if (_use_target_source_address == true)
    {
        // add target and source address
        request[1] = ECU_addr;
        request[2] = OUR_addr;
        header_len += 2;
    }

    request_len = header_len + pid_len + 1; // header + request + checksum

    // add the PID
    for (uint8_t k = 0; k < pid_len; k++)
    {
        request[header_len + k] = pid[k];
    }

    // checksum
    request[request_len - 1] = calc_checksum(request, request_len - 1);

    // finally we send the request
    _elapsed_time = 0;
    for (uint8_t i = 0; i < request_len; i++)
    {
        _kline->write(request[i]);
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            if (i == 0)
            {
                _debug->println(F("\nSending\t\tEcho"));
            }
            _debug->println(request[i], HEX);
        }

        _start_time = millis();
        while (_elapsed_time < ISO_T_P4_MIN)
        {
            _elapsed_time = millis() - _start_time;
            if (_kline->available() > 0)
            {
                echo = _kline->read();
                _debug->print("\t\t\t");
                _debug->println(echo, HEX);
            }
        }
        _elapsed_time = 0;

        //check if i send the correct bytes
        if (echo != request[i] && echo != 0)
        {
            setError(EE_ECHO);
        }
    }
    _start_time = 0;

    if (wait_to_send_all == true)
    {
        _kline->flush();
    }

    if (use_delay == true)
    {
        delay(ISO_T_P2_MIN);
    }
}

/**
 * @brief Listen and process the response from the ECU
 *
 * @param use_delay Choose to wait at the end of the function or to do other tasks
 */
void KWP2000::listenResponse(const uint8_t use_delay)
{
    _response_data_start = 0;
    _response_len = 0;
    memset(_response, 0, ISO_MAX_DATA);

    uint8_t masked = 0;                     // useful for bit mask operation
    uint8_t response_completed = false;     // when true no more bytes will be received
    uint32_t incoming;                      // incoming byte from the ECU
    uint8_t n_byte = 0;                     // actual length of the response, updated every times a new byte is received
    uint8_t data_to_rcv = 0;                // data to receive: bytes of the response that have to be received (not received yet)
    uint8_t data_rcvd = 0;                  // data received: bytes of the response already received
    uint32_t last_data_received = millis(); // check times for the timeout

    while ((millis() - last_data_received < ISO_T_P3_mdf) && (response_completed == false))
    {
        if (_kline->available() > 0)
        {
            incoming = _kline->read();
            _response[n_byte] = incoming;

            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                if (n_byte == 0)
                {
                    _debug->print(F("\nReceiving:"));
                }
                _debug->print(F("\n"));
                _debug->print(incoming, HEX);
            }

            last_data_received = millis(); // reset the timer for each byte received

            // Technically the ECU waits between 0 to 20 ms between sending two bytes
            // We use this time to analyze what we received

            switch (n_byte)
            {
            case 0: // the first byte is the formatter, with or without length bits

                masked = incoming & 0xC0; // 0b11000000
                if (masked == format_physical)
                {
                    if (_debug_level == DEBUG_LEVEL_VERBOSE)
                    {
                        _debug->print(F("\t- format physical"));
                    }
                }
                else if (masked == format_functional)
                {
                    if (_debug_level == DEBUG_LEVEL_VERBOSE)
                    {
                        _debug->print(F("\t- format functional"));
                    }
                    setError(EE_US);
                }
                else if (masked == format_CARB)
                {
                    if (_debug_level == DEBUG_LEVEL_VERBOSE)
                    {
                        _debug->print(F("\t- format CARB"));
                    }
                    setError(EE_US);
                }
                else
                {
                    if (_debug_level == DEBUG_LEVEL_VERBOSE)
                    {
                        _debug->print(F("\t- unexpected header"));
                    }
                    setError(EE_HEADER);
                }

                // let's see if there are length bits
                if (_use_length_byte == true || _use_length_byte == maybe)
                {
                    masked = incoming & 0x3F; // 0b00111111
                    if (masked != 0)          // the response length is inside the formatter
                    {
                        data_to_rcv = masked;
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            _debug->print(F("\t- "));
                            _debug->print(data_to_rcv);
                            _debug->print(F(" data bytes coming"));
                        }

                        if (_use_length_byte == maybe)
                        {
                            _use_length_byte = false;
                            setError(EE_TEST);
                        }
                    }
                    else // the response length is in a separete byte (the 2nd or the 4th)
                    {
                        data_to_rcv = 0;
                    }
                }
                break;

            case 1: // the second byte is be the target address or the length byte or the data

                if (_use_target_source_address == maybe)
                {
                    if (incoming == OUR_addr)
                    {
                        _use_target_source_address = true;
                        setError(EE_TEST);
                    }
                    else
                    {
                        _use_target_source_address = false;
                        setError(EE_TEST);
                    }
                }

                if (_use_target_source_address == true)
                {
                    if (incoming == OUR_addr) // it is the target byte
                    {
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            _debug->print(F("\t- ECU is communicating with us"));
                        }
                    }
                    else
                    {
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            // (I'm not jealous it's just curiosity :P)
                            _debug->print(F("\t- ECU is communicating with this address"));
                        }
                        setError(EE_TO);
                    }
                }
                else if (_use_target_source_address == false)
                {
                    if (data_to_rcv == 0) // it is the length byte
                    {
                        data_to_rcv = incoming;
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            _debug->print(F("\t- "));
                            _debug->print(data_to_rcv);
                            _debug->print(F(" data bytes coming"));
                        }
                    }
                    else // data
                    {
                        data_rcvd++;
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            _debug->print(F("\t- data"));
                        }
                        if (_response_data_start == 0)
                        {
                            _response_data_start = n_byte;
                        }
                    }
                }
                break;

            case 2: // the third byte is the source address or the data or checksum

                if (_use_target_source_address == true)
                {
                    if (incoming == ECU_addr)
                    {
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            _debug->print(F("\t- comes from the ECU"));
                        }
                    }
                    else
                    {
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            // check who sent it
                            _debug->print(F("\t- doesn't come from the ECU"));
                        }
                        setError(EE_FROM);
                    }
                }
                else // data or checksum
                {
                    if (data_to_rcv == data_rcvd) // it is the checksum
                    {
                        response_completed = true;
                        _response_len = n_byte;
                        endResponse(incoming);
                    }
                    else // there is still data outside
                    {
                        data_rcvd++;
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            _debug->print(F("\t- data"));
                        }
                        if (_response_data_start == 0)
                        {
                            _response_data_start = n_byte;
                        }
                    }
                }
                break;

            case 3: // the fourth byte is the length byte or the data or checksum

                if (data_to_rcv == 0) // it is the length byte
                {
                    data_to_rcv = incoming;
                    if (_debug_level == DEBUG_LEVEL_VERBOSE)
                    {
                        _debug->print(F("\t- data bytes coming (HEX)"));
                    }
                }
                else // data or checksum
                {
                    if (data_to_rcv == data_rcvd) // it is the checksum
                    {
                        response_completed = true;
                        _response_len = n_byte;
                        endResponse(incoming);
                    }
                    else // data
                    {
                        data_rcvd++;
                        if (_debug_level == DEBUG_LEVEL_VERBOSE)
                        {
                            _debug->print(F("\t- data"));
                        }
                        if (_response_data_start == 0)
                        {
                            _response_data_start = n_byte;
                        }
                    }
                }
                break;

            default: // data or checksum

                if (data_to_rcv == data_rcvd) // it is the checksum
                {
                    response_completed = true;
                    _response_len = n_byte;
                    endResponse(incoming);
                }
                else // data
                {
                    data_rcvd++;
                    if (_debug_level == DEBUG_LEVEL_VERBOSE)
                    {
                        _debug->print(F("\t- data"));
                    }
                    if (_response_data_start == 0)
                    {
                        _response_data_start = n_byte;
                    }
                }
                break;
            }         // end of the swith statement
            n_byte++; // read the next byte of the response
        }             // end of the if _kline.available()
    }                 // end of the while timeout

    if (use_delay == true)
    {
        delay(ISO_T_P3_MIN);
    }
}

/**
 * @brief Compare the given response to the correct one which should be received
 *
 * @param request_sent The request sent to the ECU
 * @return `true` if the response is correct, a `negative number` if is not
 */
int8_t KWP2000::checkResponse(const uint8_t request_sent[])
{
    if (_response[_response_data_start] == (request_ok(request_sent[0])))
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("\nCorrect response from the ECU\n"));
        }
        return true;
    }
    else if (_response[_response_data_start] == 0)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("\nNo response from the ECU\n"));
        }
        return -1;
    }
    else if (_response[_response_data_start] == request_rejected)
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->print(F("\nRequest rejected with code: "));
        }

        if (_response[_response_data_start + 1] != request_sent[0])
        {
            // this is not the request we sent!
            setError(EE_WR);
        }

        // the ECU rejected our request and gave us some useful info
        setError(EE_CR);

        switch (_response[_response_data_start + 2])
        {
        case 0x10:
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(F("General\n"));
            }
            return -2;

        case 0x11:
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(F("Service Not Supported\n"));
            }
            return -3;

        case 0x12:
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(F("Sub Function Not Supported or Invalid Format\n"));
            }
            return -4;

        case 0x21:
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(F("Busy, reapeat\n"));
            }
            setError(EE_US);
            //todo send again the request
            return -5;

        case 0x22:
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(F("Conditions Not Correct or Request Sequence Error\n"));
            }
            return -6;
            /*
            todo
            23 routineNotComplete
            31 requestOutOfRange
            33 securityAccessDenied
            35 invalidKey
            36 exceedNumberOfAttempts
            37 requiredTimeDelayNotExpired
            40 downloadNotAccepted
            41 improperDownloadType
            42 can 'tDownloadToSpecifiedAddress
            43 can' tDownloadNumberOfBytesRequested
            50 uploadNotAccepted
            51 improperUploadType
            52 can 'tUploadFromSpecifiedAddress
            53 can' tUploadNumberOfBytesRequested
            71 transferSuspended
            72 transferAborted
            74 illegalAddressInBlockTransfer
            75 illegalByteCountInBlockTransfer
            76 illegalBlockTransferType
            77 blockTransferDataChecksumError
            */
        case 0x78:
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(F("Request Correctly Received - Response Pending\n"));
            }
            /*
            This response code shall only be used by a server in case it
            cannot send a positive or negative response message based on the client's request message
            within the active P2 timing window. This response code shall manipulate the P2max timing
            parameter value in the server and the client. The P2max timing parameter is set to the value (in
            ms) of the P3max timing parameter. The client shall remain in the receive mode. The server(s)
            shall send multiple negative response messages with the negative response code $78 if required.
            As soon as the server has completed the task (routine) initiated by the request message it shall
            send either a positive or negative response message (negative response message with a
            response code other than $78) based on the last request message received. When the client has
            received the response message which has been preceded by the negative response message(s)
            with response code $78, the client and the server shall reset the P2max timing parameter to the
            previous timing value
            */
            setError(EE_US);
            return -7;
            /*
            79  incorrectByteCountDuringBlockTransfer
            80 - FF manufacturerSpecificCodes
            */
        default:
            if (_debug_level == DEBUG_LEVEL_VERBOSE)
            {
                _debug->println(F("Unknown error code\n"));
            }
            return -8;
        }
    }
    else
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->print(F("\nUnexpected response: "));
            for (uint8_t n = _response_data_start; n < _response_len; n++)
            {
                _debug->println(_response[n], HEX);
            }
        }
        setError(EE_CR);
        setError(EE_UNEX);
        return -9;
    }
    // the program should never arrive here
    setError(EE_UNEX);
    return -10;
}

/**
 * @brief Set errors from `error_enum`
 *
 * @param error The error you want to set
 */
void KWP2000::setError(const uint8_t error)
{
    bitSet(_ECU_error, error);
}

/**
 * @brief Clear errors from `error_enum`
 *
 * @param error The error you want to clear
 */
void KWP2000::clearError(const uint8_t error)
{
    bitClear(_ECU_error, error);
}

/**
 * @brief Configure the K-Line behaviour from the keybytes received by the ECU
 */
void KWP2000::configureKline()
{
    // get the key bytes
    if (_response[_response_data_start + 2] != 0x8F)
    {
        setError(EE_CONFIG);
    }

    uint16_t key_bytes = _response[_response_data_start + 2] << 8 | _response[_response_data_start + 1];

    uint8_t AL0 = bitRead(key_bytes, 0);
    uint8_t AL1 = bitRead(key_bytes, 1);
    if (AL1 == 1 && AL0 == 1)
    {
        // both are possible, so choose the faster one
        _use_length_byte = false;
    }
    else if (AL1 == 1 && AL0 == 0)
    {
        // length byte must be present
        _use_length_byte = true;
    }
    else if (AL1 == 0 && AL0 == 1)
    {
        // length byte not needed
        _use_length_byte = false;
    }

    // target and source
    uint8_t HB0 = bitRead(key_bytes, 2);
    uint8_t HB1 = bitRead(key_bytes, 3);
    if (HB1 == 1 && HB0 == 1)
    {
        // both are possible, so choose the faster one
        _use_target_source_address = false;
    }
    else if (HB1 == 1 && HB0 == 0)
    {
        // target and source address bytes must be present
        _use_target_source_address = true;
    }
    else if (HB1 == 0 && HB0 == 1)
    {
        // target and source address bytes not needed
        _use_target_source_address = false;
    }

    // timing
    uint8_t TP0 = bitRead(key_bytes, 4);
    uint8_t TP1 = bitRead(key_bytes, 5);
    if (TP1 == 1 && TP0 == 1)
    {
        setError(EE_CONFIG);
    }
    else if (TP1 == 1 && TP0 == 0)
    {
        _timing_parameter = true; // normal
    }
    else if (TP1 == 0 && TP0 == 1)
    {
        _timing_parameter = false; // extended
        // this allow faster comunication but I don't plan to implement it soon
        setError(EE_US);
    }

    if (AL0 == 0 && AL1 == 0 && HB0 == 0 && HB1 == 0 && TP0 == 1 && TP1 == 0)
    {
        // it will use the same configuration as in the first response
        _use_length_byte = maybe;
        _use_target_source_address = maybe;
        _timing_parameter = maybe;
    }

    // must be 1
    if (bitRead(key_bytes, 6) != 1)
    {
        setError(EE_CONFIG);
    }

    // parity of the 6 bits above
    uint8_t parity = 1;
    for (uint8_t n = 0; n < 7; n++)
    {
        if (bitRead(key_bytes, n))
        {
            parity = !parity;
        }
    }

    if (bitRead(key_bytes, 7) != parity)
    {
        setError(EE_CONFIG);
    }

    if (_debug_level == DEBUG_LEVEL_VERBOSE)
    {
        _debug->println("\nK line config:");
        _debug->print("Key bytes:\t\t\t0x");
        _debug->print(key_bytes, HEX);
        _debug->print(" - ");
        _debug->println(key_bytes, BIN);
        _debug->print("Errors:\t\t\t\t");
        _debug->println(bitRead(_ECU_error, EE_CONFIG) == 1 ? "Yes" : "No");
        _debug->print("Length byte:\t\t");
        _debug->println(_use_length_byte == 1 ? "Yes" : "No");
        _debug->print("Addresses bytes:\t");
        _debug->println(_use_target_source_address == 1 ? "Yes" : "No");
        _debug->print("Timing parameter:\t");
        _debug->println(_timing_parameter == 1 ? "Normal\n" : "Extended\n");
    }
}

/**
 * @brief The checksum is the sum of all data bytes modulo (&) 0xFF (same as being truncated to one byte)
 *
 * @param data All the bytes received
 * @param data_len The length of the response
 * @return The correct checksum
 */
uint8_t KWP2000::calc_checksum(const uint8_t data[], const uint8_t data_len)
{
    uint8_t cs = 0;
    for (uint8_t i = 0; i < data_len; i++)
    {
        cs += data[i];
    }
    return cs;
}

/**
 * @brief This is called when the last byte is received from the ECU
 *
 * @param received_checksum The last byte received which is the checksum
 */
void KWP2000::endResponse(const uint8_t received_checksum)
{
    uint8_t correct_checksum;

    if (_debug_level == DEBUG_LEVEL_VERBOSE)
    {
        _debug->println(F("\t- checksum"));
        _debug->println(F("\nEnd of response"));
        _debug->print(F("Bytes received: "));
        _debug->println(_response_len);
    }

    correct_checksum = calc_checksum(_response, _response_len);
    if (correct_checksum == received_checksum)
    {
        // the checksum is correct and everything went well!
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->println(F("Correct checksum"));
        }
        _last_correct_response = millis();
    }
    else // the checksum is not correct
    {
        if (_debug_level == DEBUG_LEVEL_VERBOSE)
        {
            _debug->print(F("Wrong checksum, expected: "));
            _debug->println(correct_checksum, HEX);
        }
        setError(EE_CS);
    }
}

#endif // KWP2000_cpp


abc_slutprov_device::abc_slutprov_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, ABC_SLUTPROV, tag, owner, clock),
		device_abcbus_card_interface(mconfig, *this)
{
}

// copyright-holders:Curt Coder
#include "emu.h"
#include "slutprov.h"

//  DEVICE DEFINITIONS

DEFINE_DEVICE_TYPE(ABC_SLUTPROV, abc_slutprov_device, "abc_slutprov", "Slutprov")

ROM_START( abc_slutprov )
	ROM_REGION( 0x800, "slutprov", 0 )
	ROM_LOAD( "slutprov.bin",   0x0000, 0x0800, CRC(a5bb56f4) SHA1(f97cb6526a1d10b189164f26157522e382ca6bc6) )
ROM_END

//  rom_region - device-specific ROM region

const tiny_rom_entry *abc_slutprov_device::device_rom_region() const
{
	return ROM_NAME( abc_slutprov );
}

//  LIVE DEVICE

//  device_start - device-specific startup

void abc_slutprov_device::device_start()
{
}

//  device_reset - device-specific reset

void abc_slutprov_device::device_reset()
{
}

//  ABC BUS INTERFACE

void abc_slutprov_device::abcbus_cs(uint8_t data)
{
}


void S9xResetSRTC ()
{
    rtc.index = -1;
    rtc.mode = MODE_READ;
}

/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 *
 * Super FX C emulator code
 *
 *
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Snes9x homepage: http://www.snes9x.com
 *
 * all copies and any derived work.
 *
 *
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */
#include <string.h>
#include <time.h>
#include "snes9x.h"
#include "srtc.h"
#include "memmap.h"

/***   The format of the rtc_data structure is:

Index Description     Range (nibble)
----- --------------  ---------------------------------------

  0   Seconds low     0-9
  1   Seconds high    0-5

  2   Minutes low     0-9
  3   Minutes high    0-5

  4   Hour low        0-9
  5   Hour high       0-2

  6   Day low         0-9
  7   Day high        0-3

  8   Month           1-C (0xC is December, 12th month)

  9   Year ones       0-9
  A   Year tens       0-9
  B   Year High       9-B  (9=19xx, A=20xx, B=21xx)

  C   Day of week     0-6  (0=Sunday, 1=Monday,...,6=Saturday)

***/

SRTC_DATA           rtc;

static int month_keys[12] = { 1, 4, 4, 0, 2, 5, 0, 3, 6, 1, 4, 6 };

/*********************************************************************************************
 *
 * Note, if you are doing a save state for this game:
 *
 * On save:
 *
 *	Call S9xUpdateSrtcTime and save the rtc data structure.
 *
 * On load:
 *
 *	restore the rtc data structure
 *      rtc.system_timestamp = time (NULL);
 *
 *
 *********************************************************************************************/

void S9xHardResetSRTC ()
{
    ZeroMemory (&rtc, sizeof (rtc));
    rtc.index = -1;
    rtc.mode = MODE_READ;
    rtc.count_enable = FALSE;
    rtc.needs_init = TRUE;

    // Get system timestamp
    rtc.system_timestamp = time (NULL);
}

/**********************************************************************************************/
/* S9xSRTCComputeDayOfWeek()                                                                  */
/* Return 0-6 for Sunday-Saturday                                                             */
/**********************************************************************************************/
unsigned int    S9xSRTCComputeDayOfWeek ()
{
    unsigned    year = rtc.data[10]*10 + rtc.data[9];
    unsigned    month = rtc.data[8];
    unsigned    day = rtc.data[7]*10 + rtc.data[6];
    unsigned    day_of_week;

    year += (rtc.data[11] - 9) * 100;

    // Range check the month for valid array indicies
    if ( month > 12 )
        month = 1;

    day_of_week = year + (year / 4) + month_keys[month-1] + day - 1;

    if(( year % 4 == 0 ) && ( month <= 2 ) )
        day_of_week--;

    day_of_week %= 7;

    return day_of_week;
}

/**********************************************************************************************/
/* S9xSRTCDaysInMonth()                                                                       */
/* Return the number of days in a specific month for a certain year                           */
/**********************************************************************************************/
int	S9xSRTCDaysInMmonth( int month, int year )
{
    int		mdays;

    switch ( month )
    {
	case 2:
		if ( ( year % 4 == 0 ) )    // DKJM2 only uses 199x - 22xx
			mdays = 29;
		else
			mdays = 28;
		break;

	case 4:
	case 6:
	case 9:
	case 11:
		mdays = 30;
		break;

	default:	// months 1,3,5,7,8,10,12
		mdays = 31;
		break;
    }

    return mdays;
}

#define DAYTICKS (60*60*24)
#define HOURTICKS (60*60)
#define MINUTETICKS 60

/**********************************************************************************************/
/* S9xUpdateSrtcTime()                                                                        */
/* Advance the  S-RTC time if counting is enabled                                             */
/**********************************************************************************************/
void	S9xUpdateSrtcTime ()
{
	time_t	cur_systime;
	long    time_diff;

    // Keep track of game time by computing the number of seconds that pass on the system
    // clock and adding the same number of seconds to the S-RTC clock structure.
    // I originally tried using mktime and localtime library functions to keep track
    // of time but some of the GNU time functions fail when the year goes to 2099
    // (and maybe less) and this would have caused a bug with DKJM2 so I'm doing
    // it this way to get around that problem.

    // Note: Dai Kaijyu Monogatari II only allows dates in the range 1996-21xx.

    if (rtc.count_enable && !rtc.needs_init)
    {
        cur_systime = time (NULL);

        // This method assumes one time_t clock tick is one second
        //        which should work on PCs and GNU systems.
        //        If your tick interval is different adjust the
	//        DAYTICK, HOURTICK, and MINUTETICK defines

        time_diff = (long) (cur_systime - rtc.system_timestamp);
	rtc.system_timestamp = cur_systime;

        if ( time_diff > 0 )
        {
	   int		seconds;
	   int		minutes;
	   int		hours;
	   int		days;
	   int		month;
	   int		year;
	   int		temp_days;

	   int		year_hundreds;
	   int		year_tens;
	   int		year_ones;

	   if ( time_diff > DAYTICKS )
	   {
	       days = time_diff / DAYTICKS;
	       time_diff = time_diff - days * DAYTICKS;
	   }
	   else
	   {
	       days = 0;
	   }

	   if ( time_diff > HOURTICKS )
	   {
	       hours = time_diff / HOURTICKS;
	       time_diff = time_diff - hours * HOURTICKS;
	   }
	   else
	   {
	       hours = 0;
	   }

	   if ( time_diff > MINUTETICKS )
	   {
	       minutes = time_diff / MINUTETICKS;
	       time_diff = time_diff - minutes * MINUTETICKS;
	   }
	   else
	   {
	       minutes = 0;
	   }

	   if ( time_diff > 0 )
	   {
	       seconds = time_diff;
	   }
	   else
	   {
	       seconds = 0;
	   }

	   seconds += (rtc.data[1]*10 + rtc.data[0]);
           if ( seconds >= 60 )
	   {
	       seconds -= 60;
	       minutes += 1;
	   }

	   minutes += (rtc.data[3]*10 + rtc.data[2]);
           if ( minutes >= 60 )
	   {
	       minutes -= 60;
	       hours += 1;
	   }

	   hours += (rtc.data[5]*10 + rtc.data[4]);
           if ( hours >= 24 )
	   {
	       hours -= 24;
	       days += 1;
	   }

	   if ( days > 0 )
	   {
	       year =  rtc.data[10]*10 + rtc.data[9];
	       year += ( 1000 + rtc.data[11] * 100 );

	       month = rtc.data[8];
	       days += (rtc.data[7]*10 + rtc.data[6]);
	       while ( days > (temp_days = S9xSRTCDaysInMmonth( month, year )) )
               {
		    days -= temp_days;
		    month += 1;
		    if ( month > 12 )
		    {
		        year += 1;
		        month = 1;
		    }
	       }

               year_tens = year % 100;
               year_ones = year_tens % 10;
               year_tens /= 10;
               year_hundreds = (year - 1000) / 100;

	       rtc.data[6] = days % 10;
	       rtc.data[7] = days / 10;
	       rtc.data[8] = month;
	       rtc.data[9] = year_ones;
	       rtc.data[10] = year_tens;
	       rtc.data[11] = year_hundreds;
	       rtc.data[12] = S9xSRTCComputeDayOfWeek ();
	   }

	   rtc.data[0] = seconds % 10;
	   rtc.data[1] = seconds / 10;
	   rtc.data[2] = minutes % 10;
	   rtc.data[3] = minutes / 10;
	   rtc.data[4] = hours % 10;
	   rtc.data[5] = hours / 10;

	   return;
        }
    }
}

/**********************************************************************************************/
/* S9xSetSRTC()                                                                               */
/* This function sends data to the S-RTC used in Dai Kaijyu Monogatari II                     */
/**********************************************************************************************/
void S9xSetSRTC (uint8 data, uint16 Address)
{

    data &= 0x0F;	// Data is only 4-bits, mask out unused bits.

    if( data >= 0xD )
    {
        // It's an RTC command

        switch ( data )
        {
            case 0xD:
                rtc.mode = MODE_READ;
                rtc.index = -1;
                break;

            case 0xE:
                rtc.mode = MODE_COMMAND;
                break;

            default:
                // Ignore the write if it's an 0xF ???
	        // Probably should switch back to read mode -- but this
	        //  sequence never occurs in DKJM2
                break;
        }

        return;
    }

    if ( rtc.mode == MODE_LOAD_RTC )
    {
        if ( (rtc.index >= 0) || (rtc.index < MAX_RTC_INDEX) )
        {
            rtc.data[rtc.index++] = data;

            if ( rtc.index == MAX_RTC_INDEX )
            {
                // We have all the data for the RTC load

                rtc.system_timestamp = time (NULL);	// Get local system time

                // Get the day of the week
                rtc.data[rtc.index++] = S9xSRTCComputeDayOfWeek ();

                // Start RTC counting again
                rtc.count_enable = TRUE;
                rtc.needs_init = FALSE;
            }

            return;
        }
        else
        {
            // Attempting to write too much data
        }
    }
    else if ( rtc.mode == MODE_COMMAND )
    {
        switch( data )
        {
            case COMMAND_CLEAR_RTC:
                // Disable RTC counter
                rtc.count_enable = FALSE;

                ZeroMemory (rtc.data, MAX_RTC_INDEX+1);
                rtc.index = -1;
                rtc.mode = MODE_COMMAND_DONE;
                break;

            case COMMAND_LOAD_RTC:
                // Disable RTC counter
                rtc.count_enable = FALSE;

                rtc.index = 0;  // Setup for writing
                rtc.mode = MODE_LOAD_RTC;
                break;

            default:
                rtc.mode = MODE_COMMAND_DONE;
                // unrecognized command - need to implement.
        }

        return;
    }
    else
    {
        if ( rtc.mode == MODE_READ )
        {
            // Attempting to write while in read mode. Ignore.
        }

        if ( rtc.mode == MODE_COMMAND_DONE )
        {
            // Maybe this isn't an error.  Maybe we should kick off
            // a new E command.  But is this valid?
        }
    }
}

/**********************************************************************************************/
/* S9xGetSRTC()                                                                               */
/* This function retrieves data from the S-RTC                                                */
/**********************************************************************************************/
uint8 S9xGetSRTC (uint16 Address)
{
    if ( rtc.mode == MODE_READ )
    {
        if ( rtc.index < 0 )
        {
            S9xUpdateSrtcTime ();	// Only update it if the game reads it
            rtc.index++;
            return ( 0x0f );        // Send start marker.
        }
        else if (rtc.index > MAX_RTC_INDEX)
        {
            rtc.index = -1;         // Setup for next set of reads
            return ( 0x0f );        // Data done marker.
        }
        else
        {
            // Feed out the data
            return rtc.data[rtc.index++];
        }
     }
     else
     {
         return 0x0;
     }
}

void S9xSRTCPreSaveState ()
{
    if (Settings.SRTC)
    {
	S9xUpdateSrtcTime ();

	int s = Memory.SRAMSize ?
		(1 << (Memory.SRAMSize + 3)) * 128 : 0;
	if (s > 0x20000)
	    s = 0x20000;

	SRAM [s + 0] = rtc.needs_init;
	SRAM [s + 1] = rtc.count_enable;
	memmove (&SRAM [s + 2], rtc.data, MAX_RTC_INDEX + 1);
	SRAM [s + 3 + MAX_RTC_INDEX] = rtc.index;
	SRAM [s + 4 + MAX_RTC_INDEX] = rtc.mode;

#ifdef LSB_FIRST
	memmove (&SRAM [s + 5 + MAX_RTC_INDEX], &rtc.system_timestamp, 8);
#else
	SRAM [s + 5  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >>  0);
	SRAM [s + 6  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >>  8);
	SRAM [s + 7  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 16);
	SRAM [s + 8  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 24);
	SRAM [s + 9  + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 32);
	SRAM [s + 10 + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 40);
	SRAM [s + 11 + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 48);
	SRAM [s + 12 + MAX_RTC_INDEX] = (uint8) (rtc.system_timestamp >> 56);
#endif
    }
}

void S9xSRTCPostLoadState ()
{
    if (Settings.SRTC)
    {
	int s = Memory.SRAMSize ?
		(1 << (Memory.SRAMSize + 3)) * 128 : 0;
	if (s > 0x20000)
	    s = 0x20000;

	rtc.needs_init = SRAM [s + 0];
	rtc.count_enable = SRAM [s + 1];
	memmove (rtc.data, &SRAM [s + 2], MAX_RTC_INDEX + 1);
	rtc.index = SRAM [s + 3 + MAX_RTC_INDEX];
	rtc.mode = SRAM [s + 4 + MAX_RTC_INDEX];

#ifdef LSB_FIRST
	memmove (&rtc.system_timestamp, &SRAM [s + 5 + MAX_RTC_INDEX], 8);
#else
	rtc.system_timestamp |= (SRAM [s +  5 + MAX_RTC_INDEX] <<  0);
	rtc.system_timestamp |= (SRAM [s +  6 + MAX_RTC_INDEX] <<  8);
	rtc.system_timestamp |= (SRAM [s +  7 + MAX_RTC_INDEX] << 16);
	rtc.system_timestamp |= (SRAM [s +  8 + MAX_RTC_INDEX] << 24);
	rtc.system_timestamp |= (SRAM [s +  9 + MAX_RTC_INDEX] << 32);
	rtc.system_timestamp |= (SRAM [s + 10 + MAX_RTC_INDEX] << 40);
	rtc.system_timestamp |= (SRAM [s + 11 + MAX_RTC_INDEX] << 48);
	rtc.system_timestamp |= (SRAM [s + 12 + MAX_RTC_INDEX] << 56);
#endif
	S9xUpdateSrtcTime ();
    }
}


FolderListModel::FolderListModel(QObject *parent) :
    QAbstractItemModel(parent)
{
    ignoreNextLayoutAboutToBeChanged = false;
    ignoreNextLayoutChanged = false;
    m_filters = QDir::AllEntries | QDir::NoDotAndDotDot | QDir::AllDirs;
    m_resolveSymlinks = false;
    m_nameFilterDisables = true;
    m_bWatcherRoot = false;
}

/**************************************************************************
** This file is part of LiteIDE
**
**
**
**
**************************************************************************/
// Module: folderlistmodel.cpp
// Creator: visualfc <example@nnthink.com>

#include "folderlistmodel.h"
#include "filesystemmodelex.h"
#include <QFileSystemWatcher>
#include <QDebug>
//lite_memory_check_begin
#if defined(WIN32) && defined(_MSC_VER) &&  defined(_DEBUG)
     #define _CRTDBG_MAP_ALLOC
     #include <stdlib.h>
     #include <crtdbg.h>
     #define DEBUG_NEW new( _NORMAL_BLOCK, __FILE__, __LINE__ )
     #define new DEBUG_NEW
#endif
//lite_memory_check_end

FolderListModel::~FolderListModel()
{
    clear();
}

void FolderListModel::clear()
{
    if (m_modelList.isEmpty()) {
        return;
    }
    this->beginRemoveRows(QModelIndex(),0,m_modelList.size());
    this->removeRows(0,m_modelList.size());
    if (m_bWatcherRoot) {
        foreach (SourceModel s, m_modelList) {
            m_watcher->removePath(s.watchPath);
            delete s.model;
        }
    }
    m_modelList.clear();
    m_indexMap.clear();
    this->endRemoveRows();
}

QModelIndex FolderListModel::addRootPath(const QString &path)
{
    if (!QDir(path).exists()) {
        return QModelIndex();
    }
    FileSystemModelEx *model = new FileSystemModelEx(this);
    model->setFilter(m_filters);
    if (!m_nameFilters.isEmpty()) {
        model->setNameFilters(m_nameFilters);
    }
    model->setNameFilterDisables(m_nameFilterDisables);
    model->setResolveSymlinks(m_resolveSymlinks);
    QModelIndex sourceIndex = model->setRootPath(path);
    if (!sourceIndex.isValid()) {
        return sourceIndex;
    }
    SourceModel m;
    m.model = model;
    m.rootPath = QDir::cleanPath(QDir::fromNativeSeparators(path));
    m.watchPath = model->filePath(sourceIndex.parent());
    if (m_bWatcherRoot) {
        if (!m_watcher->directories().contains(m.watchPath)) {
            m_watcher->addPath(m.watchPath);
        }
    }
    m.rootSourceIndex = sourceIndex;
    m.rootIndex = createIndex(m_modelList.size(),0,sourceIndex.internalPointer());
    m_indexMap.insert(sourceIndex.internalId(),model);
    m_modelList.append(m);

    connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)),
            SLOT(sourceRowsAboutToBeInserted(const QModelIndex &, int, int)));
    connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)),
            SLOT(sourceRowsInserted(const QModelIndex &, int, int)));
    connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)),
            SLOT(sourceRowsAboutToBeRemoved(const QModelIndex &, int, int)));
    connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)),
            SLOT(sourceRowsRemoved(const QModelIndex &, int, int)));
    connect(model, SIGNAL(rowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            SLOT(sourceRowsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(model, SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            SLOT(sourceRowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)),
            SLOT(sourceColumnsAboutToBeInserted(const QModelIndex &, int, int)));
    connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)),
            SLOT(sourceColumnsInserted(const QModelIndex &, int, int)));
    connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)),
            SLOT(sourceColumnsAboutToBeRemoved(const QModelIndex &, int, int)));
    connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)),
            SLOT(sourceColumnsRemoved(const QModelIndex &, int, int)));
    connect(model, SIGNAL(columnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            SLOT(sourceColumnsAboutToBeMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(model, SIGNAL(columnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)),
            SLOT(sourceColumnsMoved(const QModelIndex &, int, int, const QModelIndex &, int)));
    connect(model, SIGNAL(modelAboutToBeReset()),
            SLOT(sourceModelAboutToBeReset()));
    connect(model, SIGNAL(modelReset()),
            SLOT(sourceModelReset()));
    connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)),
            SLOT(sourceDataChanged(const QModelIndex &, const QModelIndex &)));
    connect(model, SIGNAL(headerDataChanged(Qt::Orientation,int,int)),
            SLOT(sourceHeaderDataChanged(Qt::Orientation,int,int)));
    connect(model, SIGNAL(layoutAboutToBeChanged()),
            SLOT(sourceLayoutAboutToBeChanged()));
    connect(model, SIGNAL(layoutChanged()),
            SLOT(sourceLayoutChanged()));
    return m.rootIndex;
}

void FolderListModel::removeRootPath(const QString &path)
{
    QString rootPath = QDir::cleanPath(QDir::fromNativeSeparators(path));
    QModelIndex index;
    foreach (SourceModel s, m_modelList) {
        if (s.rootPath == rootPath) {
            index = s.rootIndex;
            break;
        }
    }
    if (index.isValid()) {
        removeRoot(index);
    }
}

void FolderListModel::removeRoot(const QModelIndex &index)
{
    QMutableListIterator<SourceModel> i(m_modelList);
    while (i.hasNext()) {
        SourceModel s = i.next();
        if (s.rootIndex.internalId() == index.internalId()) {
            this->beginRemoveRows(QModelIndex(),index.row(),index.row());
            if (m_bWatcherRoot) {
                int n = 0;
                foreach (SourceModel m, m_modelList) {
                    if (m.watchPath == s.watchPath) {
                        n++;
                    }
                }
                if (n <= 1) {
                    m_watcher->removePath(s.watchPath);
                }
            }
            this->removeRow(index.row());
            i.remove();
            this->endRemoveRows();
            QMutableHashIterator<qint64,QAbstractItemModel*> i2(m_indexMap);
            while (i2.hasNext()) {
                i2.next();
                if (i2.value() == s.model) {
                    i2.remove();
                }
            }
            delete s.model;
            break;
        }
    }
}

void FolderListModel::reloadRoot(const QModelIndex &index)
{
    QMutableListIterator<SourceModel> i(m_modelList);
    int count = 0;
    while (i.hasNext()) {
        SourceModel s = i.next();
        if (s.rootIndex.internalId() == index.internalId()) {
            QModelIndex sourceIndex = s.model->setRootPath(s.rootPath);
            s.rootSourceIndex = sourceIndex;
            s.rootIndex = createIndex(count,0,sourceIndex.internalPointer());
            m_indexMap.insert(sourceIndex.internalId(),s.model);
            i.setValue(s);
            break;
        }
        count++;
    }
}

bool FolderListModel::isRootPath(const QString &path)
{
    QString rootPath = QDir::cleanPath(QDir::fromNativeSeparators(path));
    foreach (SourceModel s, m_modelList) {
        if (s.rootPath == rootPath) {
            return true;
        }
    }
    return false;
}

QList<QModelIndex> FolderListModel::rootIndexs() const
{
    QList<QModelIndex> indexs;
    foreach (SourceModel s, m_modelList) {
        indexs.append(s.rootIndex);
    }
    return indexs;
}

QStringList FolderListModel::rootPathList() const
{
    QStringList paths;
    foreach (SourceModel s, m_modelList) {
        paths.append(s.rootPath);
    }
    return paths;
}

QFileSystemModel *FolderListModel::findSource(const QModelIndex &proxyIndex) const
{
    return (QFileSystemModel*)m_indexMap[proxyIndex.internalId()];
}

QItemSelection FolderListModel::mapSelectionToSource(const QItemSelection &proxySelection) const
{
    QModelIndexList proxyIndexes = proxySelection.indexes();
    QItemSelection sourceSelection;
    for (int i = 0; i < proxyIndexes.size(); ++i) {
        const QModelIndex proxyIdx = mapToSource(proxyIndexes.at(i));
        if (!proxyIdx.isValid())
            continue;
        sourceSelection << QItemSelectionRange(proxyIdx);
    }
    return sourceSelection;
}

QItemSelection FolderListModel::mapSelectionFromSource(const QItemSelection &sourceSelection) const
{
    QModelIndexList sourceIndexes = sourceSelection.indexes();
    QItemSelection proxySelection;
    for (int i = 0; i < sourceIndexes.size(); ++i) {
        const QModelIndex srcIdx = mapFromSource(sourceIndexes.at(i));
        if (!srcIdx.isValid())
            continue;
        proxySelection << QItemSelectionRange(srcIdx);
    }
    return proxySelection;
}

QModelIndex FolderListModel::mapFromSource(const QModelIndex &sourceIndex) const
{
    if (!sourceIndex.isValid())
        return QModelIndex();
    int row = sourceIndex.row();
    for (int i = 0; i < m_modelList.size(); i++) {
        if (m_modelList[i].rootSourceIndex.internalId() == sourceIndex.internalId()) {
            row = i;
            break;
        }
    }
    QModelIndex index = createIndex(row, sourceIndex.column(), sourceIndex.internalPointer());
    m_indexMap.insert(sourceIndex.internalId(),(QAbstractItemModel*)sourceIndex.model());
    return index;
}

QModelIndex FolderListModel::mapToSource(const QModelIndex &proxyIndex) const
{
    if (!proxyIndex.isValid()) {
        return QModelIndex();
    }
    int row = proxyIndex.row();
    for (int i = 0; i < m_modelList.size(); i++) {
        if (m_modelList[i].rootIndex.internalId() == proxyIndex.internalId()) {
            row = i;
            break;
        }
    }
    FileSystemModelEx *model = (FileSystemModelEx*)findSource(proxyIndex);
    return model->createIndex(row, proxyIndex.column(), proxyIndex.internalPointer());
}

QString FolderListModel::filePath(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QString();
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((FileSystemModelEx*)sourceIndex.model())->filePath(sourceIndex);
}

QString FolderListModel::fileName(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QString();
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((FileSystemModelEx*)sourceIndex.model())->fileName(sourceIndex);
}

QFileInfo FolderListModel::fileInfo(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QFileInfo();
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((FileSystemModelEx*)sourceIndex.model())->fileInfo(sourceIndex);
}

bool FolderListModel::isDir(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return true;
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((FileSystemModelEx*)sourceIndex.model())->isDir(sourceIndex);
}

QModelIndex FolderListModel::mkdir(const QModelIndex &parent, const QString &name)
{
    if (!parent.isValid()) {
        return QModelIndex();
    }
    QModelIndex sourceIndex = mapToSource(parent);
    return ((FileSystemModelEx*)sourceIndex.model())->mkdir(sourceIndex,name);
}

bool FolderListModel::rmdir(const QModelIndex &index)
{
    if (!index.isValid()) {
        return false;
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((FileSystemModelEx*)sourceIndex.model())->rmdir(sourceIndex);
}

bool FolderListModel::remove(const QModelIndex &index)
{
    if (!index.isValid()) {
        return false;
    }
    QModelIndex sourceIndex = mapToSource(index);
    return ((FileSystemModelEx*)sourceIndex.model())->remove(sourceIndex);
}

void FolderListModel::setFilter(QDir::Filters filters)
{
    m_filters = filters;
    foreach (SourceModel s, m_modelList) {
        s.model->setFilter(filters);
    }
}

QDir::Filters FolderListModel::filter() const
{
    return m_filters;
}

void FolderListModel::setNameFilters(const QStringList &filters)
{
    m_nameFilters = filters;
    foreach (SourceModel s, m_modelList) {
        s.model->setNameFilters(filters);
    }
}

QStringList FolderListModel::nameFilters() const
{
    return m_nameFilters;
}

void FolderListModel::setNameFilterDisables(bool enable)
{
    if (m_nameFilterDisables == enable) {
        return;
    }
    m_nameFilterDisables = enable;
    foreach (SourceModel s, m_modelList) {
        s.model->setNameFilterDisables(enable);
    }
}

bool FolderListModel::nameFilterDisables() const
{
    return m_nameFilterDisables;
}

void FolderListModel::setResolveSymlinks(bool enable)
{
    if (m_resolveSymlinks == enable) {
        return;
    }
    m_resolveSymlinks = enable;
    foreach (SourceModel s, m_modelList) {
        s.model->setResolveSymlinks(enable);
    }
}

bool FolderListModel::resolveSymlinks() const
{
    return m_resolveSymlinks;
}

bool FolderListModel::isRootIndex(const QModelIndex &index) const
{
    foreach (SourceModel s, m_modelList) {
        if (s.rootIndex.internalId() == index.internalId()) {
            return true;
        }
    }
    return false;
}

void FolderListModel::setWatcherRoot(bool b)
{
    if (m_bWatcherRoot == b) {
        return;
    }
    if (m_bWatcherRoot) {
        if (!m_watcher) {
            m_watcher = new QFileSystemWatcher(this);
        }
        connect(m_watcher,SIGNAL(directoryChanged(QString)),this,SLOT(directoryChanged(QString)));
        QStringList paths;
        foreach (SourceModel s, m_modelList) {
            paths.append(s.watchPath);
        }
        paths.removeDuplicates();
        m_watcher->addPaths(paths);
    } else {
        if (m_watcher) {
            delete m_watcher;
        }
        m_watcher = 0;
    }
}

bool FolderListModel::isWatcherRoot() const
{
    return m_bWatcherRoot;
}

QList<QModelIndex> FolderListModel::indexForPath(const QString &path) const
{
    QList<QModelIndex> indexs;
    QString findPath = QDir::cleanPath(QDir::fromNativeSeparators(path));
    foreach (SourceModel s, m_modelList) {
        if (!findPath.startsWith(s.rootPath)) {
            continue;
        }
        QModelIndex sourceIndex = s.model->index(path);
        if (sourceIndex.isValid()) {
            indexs.append(this->mapFromSource(sourceIndex));
        }
    }
    return indexs;
}

Qt::DropActions FolderListModel::supportedDragActions() const
{
    return Qt::MoveAction | Qt::CopyAction;
}

bool FolderListModel::isRootSourceIndex(const QModelIndex &sourceIndex) const
{
    foreach (SourceModel s, m_modelList) {
        if (s.rootSourceIndex.internalId() == sourceIndex.internalId()) {
            return true;
        }
    }
    return false;
}

int FolderListModel::columnCount(const QModelIndex &/*parent*/) const
{
    return 1;
}

QModelIndex FolderListModel::index(int row, int column, const QModelIndex &parent) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();
    if (!parent.isValid()) {
        QModelIndex sourceIndex = m_modelList[row].rootSourceIndex;
        if (sourceIndex.column() != column) {
            sourceIndex = sourceIndex.sibling(sourceIndex.row(),column);
            m_indexMap.insert(sourceIndex.internalId(),m_modelList[row].model);
        }
        return createIndex(row,column,sourceIndex.internalPointer());
    }
    const QModelIndex sourceParent = mapToSource(parent);
    const QModelIndex sourceIndex = sourceParent.model()->index(row, column, sourceParent);
    Q_ASSERT(sourceIndex.isValid());
    return mapFromSource(sourceIndex);
}

QModelIndex FolderListModel::parent(const QModelIndex &child) const
{
    Q_ASSERT(child.isValid() ? child.model() == this : true);
    if (isRootIndex(child)) {
        return QModelIndex();
    }
    const QModelIndex sourceIndex = mapToSource(child);
    const QModelIndex sourceParent = sourceIndex.parent();
    return mapFromSource(sourceParent);
}

int FolderListModel::rowCount(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return m_modelList.size();
    }
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    QModelIndex sourceIndex = mapToSource(parent);
    return sourceIndex.model()->rowCount(sourceIndex);
}

QVariant FolderListModel::data(const QModelIndex &proxyIndex, int role) const
{
    if (!proxyIndex.isValid()) {
        return QVariant();
    }
    QModelIndex sourceIndex = mapToSource(proxyIndex);
    return sourceIndex.model()->data(sourceIndex,role);
}

QVariant FolderListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (m_modelList.isEmpty()) {
        return QVariant();
    }
    if (orientation == Qt::Horizontal) {
        const QModelIndex proxyIndex = index(0, section);
        QModelIndex sourceIndex = mapToSource(proxyIndex);
        int sourceSection = sourceIndex.column();
        return sourceIndex.model()->headerData(sourceSection,orientation,role);
    } else {
        const QModelIndex proxyIndex = index(section, 0);
        QModelIndex sourceIndex = mapToSource(proxyIndex);
        int sourceSection = sourceIndex.row();
        return sourceIndex.model()->headerData(sourceSection,orientation,role);
    }
}

QMap<int, QVariant> FolderListModel::itemData(const QModelIndex &proxyIndex) const
{
    return QAbstractItemModel::itemData(proxyIndex);
}

Qt::ItemFlags FolderListModel::flags(const QModelIndex &index) const
{
    QModelIndex sourceIndex = mapToSource(index);
    if (!sourceIndex.isValid()) {
        return Qt::NoItemFlags;
    }
    return sourceIndex.model()->flags(sourceIndex);
}

bool FolderListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    QModelIndex sourceIndex = mapToSource(index);
    return ((QAbstractItemModel*)sourceIndex.model())->setData(sourceIndex,value,role);
}

bool FolderListModel::setItemData(const QModelIndex &index, const QMap< int, QVariant >& roles)
{
    return QAbstractItemModel::setItemData(index, roles);
}

bool FolderListModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role)
{
    if (orientation == Qt::Horizontal) {
        const QModelIndex proxyIndex = index(0, section);
        QModelIndex sourceIndex = mapToSource(proxyIndex);
        int sourceSection = sourceIndex.column();
        return ((QAbstractItemModel*)sourceIndex.model())->setHeaderData(sourceSection,orientation,value,role);
    } else {
        const QModelIndex proxyIndex = index(section, 0);
        QModelIndex sourceIndex = mapToSource(proxyIndex);
        int sourceSection = sourceIndex.row();
        return ((QAbstractItemModel*)sourceIndex.model())->setHeaderData(sourceSection,orientation,value,role);
    }
}

bool FolderListModel::insertColumns(int column, int count, const QModelIndex& parent)
{
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    QModelIndex sourceIndex = mapToSource(parent);
    return ((QAbstractItemModel*)sourceIndex.model())->insertColumns(column, count, sourceIndex);
}

/*!
    \reimp
 */
bool FolderListModel::insertRows(int row, int count, const QModelIndex& parent)
{
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    QModelIndex sourceIndex = mapToSource(parent);
    return ((QAbstractItemModel*)sourceIndex.model())->insertRows(row, count, sourceIndex);
}

bool FolderListModel::removeColumns(int column, int count, const QModelIndex &parent)
{
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    QModelIndex sourceIndex = mapToSource(parent);
    return ((QAbstractItemModel*)sourceIndex.model())->removeColumns(column, count, sourceIndex);
}

bool FolderListModel::removeRows(int row, int count, const QModelIndex &parent)
{
    Q_ASSERT(parent.isValid() ? parent.model() == this : true);
    if (!parent.isValid()) {
        return QAbstractItemModel::removeRows(row,count);
    }
    QModelIndex sourceIndex = mapToSource(parent);
    return ((QAbstractItemModel*)sourceIndex.model())->removeRows(row, count, sourceIndex);
}

void FolderListModel::directoryChanged(const QString &path)
{
    if (!m_bWatcherRoot) {
        return;
    }
    foreach(SourceModel s, m_modelList) {
         if (s.watchPath == path && !QDir(s.rootPath).exists()) {
              this->removeRoot(s.rootIndex);
              break;
         }
    }
}

bool FolderListModel::canFetchMore(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return false;
    }
    QModelIndex sourceIndex = mapToSource(parent);
    return sourceIndex.model()->canFetchMore(sourceIndex);
}

void FolderListModel::fetchMore(const QModelIndex &parent)
{
    if (!parent.isValid()) {
        return;
    }
    QModelIndex sourceIndex = mapToSource(parent);
    QAbstractItemModel *model = (QAbstractItemModel*)sourceIndex.model();
    model->fetchMore(sourceIndex);
}

void FolderListModel::sort(int column, Qt::SortOrder order)
{
    foreach (SourceModel s, m_modelList) {
        s.model->sort(column,order);
    }
}

QSize FolderListModel::span(const QModelIndex &index) const
{
    if (!index.isValid()) {
        return QAbstractItemModel::span(index);
    }
    QModelIndex sourceIndex = mapToSource(index);
    return sourceIndex.model()->span(sourceIndex);
}

bool FolderListModel::hasChildren(const QModelIndex &parent) const
{
    if (!parent.isValid()) {
        return m_modelList.size() > 0;
    }
    QModelIndex sourceIndex = mapToSource(parent);
    return sourceIndex.model()->hasChildren(sourceIndex);
}

QModelIndex FolderListModel::buddy(const QModelIndex &index) const
{
    return index;
}

void FolderListModel::sourceColumnsAboutToBeInserted(const QModelIndex &parent, int start, int end)
{
    this->beginInsertColumns(mapFromSource(parent), start, end);
}

void FolderListModel::sourceColumnsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
{
    this->beginMoveColumns(this->mapFromSource(sourceParent), sourceStart, sourceEnd, this->mapFromSource(destParent), dest);
}

void FolderListModel::sourceColumnsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
    this->beginRemoveColumns(this->mapFromSource(parent), start, end);
}

void FolderListModel::sourceColumnsInserted(const QModelIndex &parent, int start, int end)
{
    Q_UNUSED(parent)
    Q_UNUSED(start)
    Q_UNUSED(end)
    this->endInsertColumns();
}

void FolderListModel::sourceColumnsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
{
    Q_UNUSED(sourceParent)
    Q_UNUSED(sourceStart)
    Q_UNUSED(sourceEnd)
    Q_UNUSED(destParent)
    Q_UNUSED(dest)
    this->endMoveColumns();
}

void FolderListModel::sourceColumnsRemoved(const QModelIndex &parent, int start, int end)
{
    Q_UNUSED(parent)
    Q_UNUSED(start)
    Q_UNUSED(end)
    this->endRemoveColumns();
}

void FolderListModel::sourceDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight)
{
    this->dataChanged(this->mapFromSource(topLeft), this->mapFromSource(bottomRight));
}

void FolderListModel::sourceHeaderDataChanged(Qt::Orientation orientation, int first, int last)
{
    this->headerDataChanged(orientation, first, last);
}

void FolderListModel::sourceLayoutAboutToBeChanged()
{
    if (ignoreNextLayoutAboutToBeChanged)
        return;

    foreach(const QPersistentModelIndex &proxyPersistentIndex, this->persistentIndexList()) {
        proxyIndexes << proxyPersistentIndex;
        const QPersistentModelIndex srcPersistentIndex = this->mapToSource(proxyPersistentIndex);
        layoutChangePersistentIndexes << srcPersistentIndex;
    }

    this->layoutAboutToBeChanged();
}

void FolderListModel::sourceLayoutChanged()
{
    if (ignoreNextLayoutChanged)
        return;

    for (int i = 0; i < proxyIndexes.size(); ++i) {
        this->changePersistentIndex(proxyIndexes.at(i), this->mapFromSource(layoutChangePersistentIndexes.at(i)));
    }

    layoutChangePersistentIndexes.clear();
    proxyIndexes.clear();

    this->layoutChanged();
}

void FolderListModel::sourceModelAboutToBeReset()
{
    this->beginResetModel();
}

void FolderListModel::sourceModelReset()
{
    this->endResetModel();
}

void FolderListModel::sourceRowsAboutToBeInserted(const QModelIndex &/*parent*/, int /*start*/, int /*end*/)
{
}

void FolderListModel::sourceRowsAboutToBeMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
{
    this->beginMoveRows(this->mapFromSource(sourceParent), sourceStart, sourceEnd, this->mapFromSource(destParent), dest);
}

void FolderListModel::sourceRowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
    this->beginRemoveRows(this->mapFromSource(parent), start, end);
}

void FolderListModel::sourceRowsInserted(const QModelIndex &parent, int start, int end)
{
    Q_UNUSED(parent)
    Q_UNUSED(start)
    Q_UNUSED(end)
    for (int i = start; i <= end; i++) {
        this->beginInsertRows(this->mapFromSource(parent), i,i);
        this->endInsertRows();
    }
}

void FolderListModel::sourceRowsMoved(const QModelIndex &sourceParent, int sourceStart, int sourceEnd, const QModelIndex &destParent, int dest)
{
    Q_UNUSED(sourceParent)
    Q_UNUSED(sourceStart)
    Q_UNUSED(sourceEnd)
    Q_UNUSED(destParent)
    Q_UNUSED(dest)
    this->endMoveRows();
}

void FolderListModel::sourceRowsRemoved(const QModelIndex &parent, int start, int end)
{
    Q_UNUSED(parent)
    Q_UNUSED(start)
    Q_UNUSED(end)
    this->endRemoveRows();
}