<commit_before>import HocSinh from '../models/hocsinh-model';
import HocSinh_LopHoc from '../models/hocsinh_lophoc-model';
import LopHoc from '../models/lophoc-model';
import { Sequelize } from '../models/index';
import { generateStudentID } from '../utilities/id_generates';
import { changeToYYYYMMDD, changeToDDMMYYYY } from '../utilities/date_times';
import commonObj from '../utilities/common_object';

function getObjReq(req) {
    let objReq = {
        hoTen: req.body.name,
        ngaySinh: changeToYYYYMMDD(req.body.birthday),
        gioiTinh: req.body.gender,
        diaChi: req.body.address,
        email: req.body.email,
        namNhapHoc: req.body.schoolYearID
    };

    const objKeys = ['hoTen', 'ngaySinh', 'gioiTinh', 'diaChi', 'email', 'namNhapHoc'];

    for(let i = 0; i < objKeys.length; ++i) {
        if(!objReq[objKeys[i]])
            delete objReq[objKeys[i]];
    }

    return objReq;
}

function getReqOptionParams(req) {
    let objReq = {};

    objReq.delete_flag = '0';

    if(req.query.studentID)
        objReq.hocSinh_pkey = req.query.studentID;

    if(req.query.studentCode)
        objReq.maHocSinh = req.query.studentCode;

    if(req.query.yearAdmission)
        objReq.namNhapHoc = req.query.yearAdmission;

    if(req.query.email)
        objReq.email = req.query.email;

    if(req.query.gender)
        obj.gioiTinh = req.query.gender;

    if(req.query.name)
        objReq.hoTen = { $like: '%' + req.query.name + '%' }

    if(req.query.address)
        objReq.diaChi = { $like: '%' + req.query.address + '%' }

    if(req.query.birthdayFrom) {
        const temp = changeToYYYYMMDD(req.query.birthdayFrom);
        objReq.ngaySinh = { $gte: temp }
    }

    if(req.query.birthdayTo) {
        const temp = changeToYYYYMMDD(req.query.birthdayTo);
        objReq.ngaySinh = Object.assign({}, objReq.ngaySinh, { $lte: temp });
    }

    return objReq;
}

function createStu(req, res) {
    generateStudentID()
    .then((studentID) => {
        return HocSinh.create({
                maHocSinh: studentID,
                hoTen: req.body.name,
                ngaySinh: changeToYYYYMMDD(req.body.birthday),
                gioiTinh: req.body.gender,
                diaChi: req.body.address,
                email: req.body.email,
                namNhapHoc: req.body.schoolYearID
            });
    })
    .then((student) => {
        res.status(200).json({
            success: true,
            message: "Created student successfully"
        })
    })
    .catch((err) => {
        console.log(err);
        res.status(500).json({
            success: false,
            message: "Failed to create student"
        })
    });
}

function deleteStu(req, res) {
    HocSinh.findOne({
        where: {
            hocSinh_pkey: req.body.studentID || req.query.studentID,
        }
    })
    .then((result) => {
        if(result) {
            HocSinh.update({
                delete_flag: '1'
            }, {
                where: {
                    hocSinh_pkey: result.hocSinh_pkey
                }
            })
            .then((rows) => {

                if(rows[0] === 1) {
                     return res.status(200).json({
                        success: true,
                        message: "Delete student successfully"
                    });
                } else {
                    return res.status(200).json({
                        success: false,
                        message: "No student is deleted"
                    });
                }
            });

        } else {
            return res.status(200).json({
                success: false,
                message: "No student found to delete"
            });
        }
    })
    .catch((err) => {
        console.log(err);
        return res.status(500).json({
            success: false,
            message: "Failed to delete student"
        });
    });
}

function updateStu(req, res) {
    HocSinh.findOne({
        where: {
            hocSinh_pkey: req.body.studentID,
            delete_flag: '0'
        }
    })
    .then((result) => {
        if(result) {
            let objReq = getObjReq(req);

            HocSinh.update(objReq, {
                where: {
                    hocSinh_pkey: result.hocSinh_pkey
                }
            })
            .then((rows) => {

                if(rows[0] === 1) {
                     return res.status(200).json({
                        success: true,
                        message: "Update student successfully"
                    });
                } else {
                    return res.status(200).json({
                        success: false,
                        message: "No student is updated"
                    });
                }
            });

        } else {
            return res.status(200).json({
                success: false,
                message: "No student found to update"
            });
        }
    })
    .catch((err) => {
        console.log(err);
        return res.status(500).json({
            success: false,
            message: "Failed to update student"
        });
    });
}

function searchStudents(req, res) {
    let objReq = getReqOptionParams(req);
    let objReturning = [];
    HocSinh.findAll({ where: objReq })
    .then((result) => {
        const len = result.length;
        if(len > 0) {
            getClassForStudent(req, res, result, 0, objReturning);
        } else {
            return res.status(200).json({
                success: false,
                message: "No student found"
            });
        }
    })
    .catch((err) => {
        console.log(err);
        return res.status(500).json({
            success: false,
            message: "Failed to get students"
        });
    });
}

function getClassForStudent(req, res, result, index, objReturning) {
    if(index > result.length - 1) {
        console.log(objReturning);
        getPrevClass(req, res, objReturning, 0);
    } else if(result[index].inClass) {
        HocSinh_LopHoc.findOne({
            where: { maHocSinh: result[index].hocSinh_pkey },
            include: [{
                model: LopHoc,
                where: {
                    maNamHoc: commonObj.schoolYearID
                }
            }]
        })
        .then((result2) => {
            objReturning[objReturning.length] = {
                studentID: result[index].hocSinh_pkey,
                studentCode: result[index].maHocSinh,
                name: result[index].hoTen,
                birthday: changeToDDMMYYYY(result[index].ngaySinh),
                gender: result[index].gioiTinh,
                address: result[index].diaChi,
                email: result[index].email,
                yearAdmission: result[index].namNhapHoc,
                inClass: result[index].inClass,
                classID:  result2['M_LOP_HOC'].maLop_pkey,
                className:  result2['M_LOP_HOC'].tenLop,
                prevClasses: []
            };
            getClassForStudent(req, res, result, ++index, objReturning);
        })
        .catch((err) => {
            console.log(err);
            return res.status(500).json({
                success: false,
                message: "Failed to get students"
            });
        });
    } else {
        objReturning[objReturning.length] = {
            studentID: result[index].hocSinh_pkey,
            studentCode: result[index].maHocSinh,
            name: result[index].hoTen,
            birthday: changeToDDMMYYYY(result[index].ngaySinh),
            gender: result[index].gioiTinh,
            address: result[index].diaChi,
            email: result[index].email,
            yearAdmission: result[index].namNhapHoc,
            inClass: result[index].inClass,
            classID:  -1,
            className:  "",
            prevClasses: []
        };
        getClassForStudent(req, res, result, ++index, objReturning);
    }
}

function getPrevClass(req, res, objReturning, index) {
    if(index > objReturning.length - 1) {
        return res.status(200).json({
            success: true,
            message: "Get students successfully",
            datas: objReturning
        });
    } else if(objReturning[index].classID) {
        HocSinh_LopHoc.findAll({
            where: {
                maHocSinh: objReturning[index].studentID,
                maLopHoc:  { $ne: objReturning[index].classID }
            },
            include: [{
                model: LopHoc
            }]
        })
        .then((result2) => {
            console.log(result2, result2.length);
            for(let i = 0; i < result2.length; ++i) {
                objReturning[index].prevClasses[objReturning[index].prevClasses.length] = {
                    classID: result2[i]['M_LOP_HOC'].maLop_pkey,
                    className: result2[i]['M_LOP_HOC'].tenLop
                }
            }

            getPrevClass(req, res, objReturning, ++index);
        })
        .catch((err) => {
            console.log(err);
            return res.status(500).json({
                success: false,
                message: "Failed to get students"
            });
        });
    } else {
        getPrevClass(req, res, objReturning, ++index);
    }
}

function findStus(req, res) {
    let objReq = getReqOptionParams(req);
    let objReturning = [];
    const reqSchoolYearNow = req.query.schoolYearNow;

    HocSinh.findAll({
        where: objReq,
        include: [{
            model: HocSinh_LopHoc,
            include: [{
                    model: LopHoc,
                    where: {
                        maNamHoc: reqSchoolYearNow //|| Sequelize.col('M_LOP_HOC.MA_NAM_HOC')
                    },
                    required: false
                }
            ],
            required: false
        }]
    })
    .then((result) => {
        if(result.length > 0) {

            for(let i = 0; i < result.length; ++i) {
                let classID = -1;
                let className = '';
                let schoolYearNow = -1;

                if(result[i]['HOCSINH_LOPHOCs'][0] && result[i]['HOCSINH_LOPHOCs'][0]['M_LOP_HOC']) {
                    classID = Number(result[i]['HOCSINH_LOPHOCs'][0].maLopHoc);
                    className = result[i]['HOCSINH_LOPHOCs'][0]['M_LOP_HOC'].tenLop;
                    schoolYearNow = Number(result[i]['HOCSINH_LOPHOCs'][0]['M_LOP_HOC'].maNamHoc);
                }

                objReturning[objReturning.length] = {
                    studentID: result[i].hocSinh_pkey,
                    studentCode: result[i].maHocSinh,
                    name: result[i].hoTen,
                    birthday: changeToDDMMYYYY(result[i].ngaySinh),
                    gender: result[i].gioiTinh,
                    address: result[i].diaChi,
                    email: result[i].email,
                    yearAdmission: result[i].namNhapHoc,
                    inClass: result[i].inClass,
                    classID:  classID,
                    className:  className,
                    schoolYearNow: schoolYearNow
                }
            }

            if(reqSchoolYearNow)
                for(let i = 0; i < objReturning.length; ++i) {
                    if(objReturning[i].schoolYearNow !== Number(reqSchoolYearNow)) {
                        objReturning.splice(i, 1);
                        --i;
                    }
                }

            if(objReturning.length > 0)
                return res.status(200).json({
                    success: true,
                    message: "Get students successfully",
                    datas: objReturning
                });
            else
                return res.status(200).json({
                    success: false,
                    message: "No student found"
                });
        } else {
            return res.status(200).json({
                success: false,
                message: "No student found"
            });
        }
    })
    .catch((err) => {
        console.log(err);
        return res.status(500).json({
            success: false,
            message: "Failed to get students"
        });
    })
}

export default { createStu, deleteStu, updateStu, findStus, searchStudents };<commit_msg>add prevClassesString params in response searchStudents<commit_after>import HocSinh from '../models/hocsinh-model';
import HocSinh_LopHoc from '../models/hocsinh_lophoc-model';
import LopHoc from '../models/lophoc-model';
import { Sequelize } from '../models/index';
import { generateStudentID } from '../utilities/id_generates';
import { changeToYYYYMMDD, changeToDDMMYYYY } from '../utilities/date_times';
import commonObj from '../utilities/common_object';

function getObjReq(req) {
    let objReq = {
        hoTen: req.body.name,
        ngaySinh: changeToYYYYMMDD(req.body.birthday),
        gioiTinh: req.body.gender,
        diaChi: req.body.address,
        email: req.body.email,
        namNhapHoc: req.body.schoolYearID
    };

    const objKeys = ['hoTen', 'ngaySinh', 'gioiTinh', 'diaChi', 'email', 'namNhapHoc'];

    for(let i = 0; i < objKeys.length; ++i) {
        if(!objReq[objKeys[i]])
            delete objReq[objKeys[i]];
    }

    return objReq;
}

function getReqOptionParams(req) {
    let objReq = {};

    objReq.delete_flag = '0';

    if(req.query.studentID)
        objReq.hocSinh_pkey = req.query.studentID;

    if(req.query.studentCode)
        objReq.maHocSinh = req.query.studentCode;

    if(req.query.yearAdmission)
        objReq.namNhapHoc = req.query.yearAdmission;

    if(req.query.email)
        objReq.email = req.query.email;

    if(req.query.gender)
        obj.gioiTinh = req.query.gender;

    if(req.query.name)
        objReq.hoTen = { $like: '%' + req.query.name + '%' }

    if(req.query.address)
        objReq.diaChi = { $like: '%' + req.query.address + '%' }

    if(req.query.birthdayFrom) {
        const temp = changeToYYYYMMDD(req.query.birthdayFrom);
        objReq.ngaySinh = { $gte: temp }
    }

    if(req.query.birthdayTo) {
        const temp = changeToYYYYMMDD(req.query.birthdayTo);
        objReq.ngaySinh = Object.assign({}, objReq.ngaySinh, { $lte: temp });
    }

    return objReq;
}

function createStu(req, res) {
    generateStudentID()
    .then((studentID) => {
        return HocSinh.create({
                maHocSinh: studentID,
                hoTen: req.body.name,
                ngaySinh: changeToYYYYMMDD(req.body.birthday),
                gioiTinh: req.body.gender,
                diaChi: req.body.address,
                email: req.body.email,
                namNhapHoc: req.body.schoolYearID
            });
    })
    .then((student) => {
        res.status(200).json({
            success: true,
            message: "Created student successfully"
        })
    })
    .catch((err) => {
        console.log(err);
        res.status(500).json({
            success: false,
            message: "Failed to create student"
        })
    });
}

function deleteStu(req, res) {
    HocSinh.findOne({
        where: {
            hocSinh_pkey: req.body.studentID || req.query.studentID,
        }
    })
    .then((result) => {
        if(result) {
            HocSinh.update({
                delete_flag: '1'
            }, {
                where: {
                    hocSinh_pkey: result.hocSinh_pkey
                }
            })
            .then((rows) => {

                if(rows[0] === 1) {
                     return res.status(200).json({
                        success: true,
                        message: "Delete student successfully"
                    });
                } else {
                    return res.status(200).json({
                        success: false,
                        message: "No student is deleted"
                    });
                }
            });

        } else {
            return res.status(200).json({
                success: false,
                message: "No student found to delete"
            });
        }
    })
    .catch((err) => {
        console.log(err);
        return res.status(500).json({
            success: false,
            message: "Failed to delete student"
        });
    });
}

function updateStu(req, res) {
    HocSinh.findOne({
        where: {
            hocSinh_pkey: req.body.studentID,
            delete_flag: '0'
        }
    })
    .then((result) => {
        if(result) {
            let objReq = getObjReq(req);

            HocSinh.update(objReq, {
                where: {
                    hocSinh_pkey: result.hocSinh_pkey
                }
            })
            .then((rows) => {

                if(rows[0] === 1) {
                     return res.status(200).json({
                        success: true,
                        message: "Update student successfully"
                    });
                } else {
                    return res.status(200).json({
                        success: false,
                        message: "No student is updated"
                    });
                }
            });

        } else {
            return res.status(200).json({
                success: false,
                message: "No student found to update"
            });
        }
    })
    .catch((err) => {
        console.log(err);
        return res.status(500).json({
            success: false,
            message: "Failed to update student"
        });
    });
}

function searchStudents(req, res) {
    let objReq = getReqOptionParams(req);
    let objReturning = [];
    HocSinh.findAll({ where: objReq })
    .then((result) => {
        const len = result.length;
        if(len > 0) {
            getClassForStudent(req, res, result, 0, objReturning);
        } else {
            return res.status(200).json({
                success: false,
                message: "No student found"
            });
        }
    })
    .catch((err) => {
        console.log(err);
        return res.status(500).json({
            success: false,
            message: "Failed to get students"
        });
    });
}

function getClassForStudent(req, res, result, index, objReturning) {
    if(index > result.length - 1) {
        console.log(objReturning);
        getPrevClass(req, res, objReturning, 0);
    } else if(result[index].inClass) {
        HocSinh_LopHoc.findOne({
            where: { maHocSinh: result[index].hocSinh_pkey },
            include: [{
                model: LopHoc,
                where: {
                    maNamHoc: commonObj.schoolYearID
                }
            }]
        })
        .then((result2) => {
            objReturning[objReturning.length] = {
                studentID: result[index].hocSinh_pkey,
                studentCode: result[index].maHocSinh,
                name: result[index].hoTen,
                birthday: changeToDDMMYYYY(result[index].ngaySinh),
                gender: result[index].gioiTinh,
                address: result[index].diaChi,
                email: result[index].email,
                yearAdmission: result[index].namNhapHoc,
                inClass: result[index].inClass,
                classID:  result2['M_LOP_HOC'].maLop_pkey,
                className:  result2['M_LOP_HOC'].tenLop,
                prevClasses: [],
                prevClassesString: ''
            };
            getClassForStudent(req, res, result, ++index, objReturning);
        })
        .catch((err) => {
            console.log(err);
            return res.status(500).json({
                success: false,
                message: "Failed to get students"
            });
        });
    } else {
        objReturning[objReturning.length] = {
            studentID: result[index].hocSinh_pkey,
            studentCode: result[index].maHocSinh,
            name: result[index].hoTen,
            birthday: changeToDDMMYYYY(result[index].ngaySinh),
            gender: result[index].gioiTinh,
            address: result[index].diaChi,
            email: result[index].email,
            yearAdmission: result[index].namNhapHoc,
            inClass: result[index].inClass,
            classID:  -1,
            className:  "",
            prevClasses: [],
            prevClassesString: ''
        };
        getClassForStudent(req, res, result, ++index, objReturning);
    }
}

function getPrevClass(req, res, objReturning, index) {
    if(index > objReturning.length - 1) {
        return res.status(200).json({
            success: true,
            message: "Get students successfully",
            datas: objReturning
        });
    } else if(objReturning[index].classID) {
        HocSinh_LopHoc.findAll({
            where: {
                maHocSinh: objReturning[index].studentID,
                maLopHoc:  { $ne: objReturning[index].classID }
            },
            include: [{
                model: LopHoc
            }]
        })
        .then((result2) => {
            for(let i = 0; i < result2.length; ++i) {
                objReturning[index].prevClasses[objReturning[index].prevClasses.length] = {
                    classID: result2[i]['M_LOP_HOC'].maLop_pkey,
                    className: result2[i]['M_LOP_HOC'].tenLop
                };
                objReturning[index].prevClassesString += result2[i]['M_LOP_HOC'].tenLop;
            }

            getPrevClass(req, res, objReturning, ++index);
        })
        .catch((err) => {
            console.log(err);
            return res.status(500).json({
                success: false,
                message: "Failed to get students"
            });
        });
    } else {
        getPrevClass(req, res, objReturning, ++index);
    }
}

function findStus(req, res) {
    let objReq = getReqOptionParams(req);
    let objReturning = [];
    const reqSchoolYearNow = req.query.schoolYearNow;

    HocSinh.findAll({
        where: objReq,
        include: [{
            model: HocSinh_LopHoc,
            include: [{
                    model: LopHoc,
                    where: {
                        maNamHoc: reqSchoolYearNow //|| Sequelize.col('M_LOP_HOC.MA_NAM_HOC')
                    },
                    required: false
                }
            ],
            required: false
        }]
    })
    .then((result) => {
        if(result.length > 0) {

            for(let i = 0; i < result.length; ++i) {
                let classID = -1;
                let className = '';
                let schoolYearNow = -1;

                if(result[i]['HOCSINH_LOPHOCs'][0] && result[i]['HOCSINH_LOPHOCs'][0]['M_LOP_HOC']) {
                    classID = Number(result[i]['HOCSINH_LOPHOCs'][0].maLopHoc);
                    className = result[i]['HOCSINH_LOPHOCs'][0]['M_LOP_HOC'].tenLop;
                    schoolYearNow = Number(result[i]['HOCSINH_LOPHOCs'][0]['M_LOP_HOC'].maNamHoc);
                }

                objReturning[objReturning.length] = {
                    studentID: result[i].hocSinh_pkey,
                    studentCode: result[i].maHocSinh,
                    name: result[i].hoTen,
                    birthday: changeToDDMMYYYY(result[i].ngaySinh),
                    gender: result[i].gioiTinh,
                    address: result[i].diaChi,
                    email: result[i].email,
                    yearAdmission: result[i].namNhapHoc,
                    inClass: result[i].inClass,
                    classID:  classID,
                    className:  className,
                    schoolYearNow: schoolYearNow
                }
            }

            if(reqSchoolYearNow)
                for(let i = 0; i < objReturning.length; ++i) {
                    if(objReturning[i].schoolYearNow !== Number(reqSchoolYearNow)) {
                        objReturning.splice(i, 1);
                        --i;
                    }
                }

            if(objReturning.length > 0)
                return res.status(200).json({
                    success: true,
                    message: "Get students successfully",
                    datas: objReturning
                });
            else
                return res.status(200).json({
                    success: false,
                    message: "No student found"
                });
        } else {
            return res.status(200).json({
                success: false,
                message: "No student found"
            });
        }
    })
    .catch((err) => {
        console.log(err);
        return res.status(500).json({
            success: false,
            message: "Failed to get students"
        });
    })
}

export default { createStu, deleteStu, updateStu, findStus, searchStudents };<|endoftext|>


<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError, InvalidOrder } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class cex extends Exchange {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'cex',
            'name': 'CEX.IO',
            'countries': [ 'GB', 'EU', 'CY', 'RU' ],
            'rateLimit': 1500,
            'hasCORS': true,
            'hasFetchTickers': true,
            'hasFetchOHLCV': true,
            'hasFetchOpenOrders': true,
            'timeframes': {
                '1m': '1m',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg',
                'api': 'https://cex.io/api',
                'www': 'https://cex.io',
                'doc': 'https://cex.io/cex-api',
                'fees': [
                    'https://cex.io/fee-schedule',
                    'https://cex.io/limits-commissions',
                ],
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
                'uid': true,
            },
            'api': {
                'public': {
                    'get': [
                        'currency_limits/',
                        'last_price/{pair}/',
                        'last_prices/{currencies}/',
                        'ohlcv/hd/{yyyymmdd}/{pair}',
                        'order_book/{pair}/',
                        'ticker/{pair}/',
                        'tickers/{currencies}/',
                        'trade_history/{pair}/',
                    ],
                    'post': [
                        'convert/{pair}',
                        'price_stats/{pair}',
                    ],
                },
                'private': {
                    'post': [
                        'active_orders_status/',
                        'archived_orders/{pair}/',
                        'balance/',
                        'cancel_order/',
                        'cancel_orders/{pair}/',
                        'cancel_replace_order/{pair}/',
                        'close_position/{pair}/',
                        'get_address/',
                        'get_myfee/',
                        'get_order/',
                        'get_order_tx/',
                        'open_orders/{pair}/',
                        'open_orders/',
                        'open_position/{pair}/',
                        'open_positions/{pair}/',
                        'place_order/{pair}/',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.16 / 100,
                    'taker': 0.25 / 100,
                },
                'funding': {
                    'withdraw': {
                        // 'USD': undefined,
                        // 'EUR': undefined,
                        // 'RUB': undefined,
                        // 'GBP': undefined,
                        'BTC': 0.001,
                        'ETH': 0.01,
                        'BCH': 0.001,
                        'DASH': 0.01,
                        'BTG': 0.001,
                        'ZEC': 0.001,
                        'XRP': 0.02,
                        'XLM': undefined,
                    },
                    'deposit': {
                        // 'USD': amount => amount * 0.035 + 0.25,
                        // 'EUR': amount => amount * 0.035 + 0.24,
                        // 'RUB': amount => amount * 0.05 + 15.57,
                        // 'GBP': amount => amount * 0.035 + 0.2,
                        'BTC': 0.0,
                        'ETH': 0.0,
                        'BCH': 0.0,
                        'DASH': 0.0,
                        'BTG': 0.0,
                        'ZEC': 0.0,
                        'XRP': 0.0,
                        'XLM': 0.0,
                    },
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetCurrencyLimits ();
        let result = [];
        for (let p = 0; p < markets['data']['pairs'].length; p++) {
            let market = markets['data']['pairs'][p];
            let id = market['symbol1'] + '/' + market['symbol2'];
            let symbol = id;
            let [ base, quote ] = symbol.split ('/');
            result.push ({
                'id': id,
                'info': market,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'precision': {
                    'price': this.precisionFromString (market['minPrice']),
                    'amount': -1 * Math.log10 (market['minLotSize']),
                },
                'limits': {
                    'amount': {
                        'min': market['minLotSize'],
                        'max': market['maxLotSize'],
                    },
                    'price': {
                        'min': parseFloat (market['minPrice']),
                        'max': parseFloat (market['maxPrice']),
                    },
                    'cost': {
                        'min': market['minLotSizeS2'],
                        'max': undefined,
                    },
                },
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostBalance ();
        let result = { 'info': response };
        let ommited = [ 'username', 'timestamp' ];
        let balances = this.omit (response, ommited);
        let currencies = Object.keys (balances);
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            if (currency in balances) {
                let account = {
                    'free': this.safeFloat (balances[currency], 'available', 0.0),
                    'used': this.safeFloat (balances[currency], 'orders', 0.0),
                    'total': 0.0,
                };
                account['total'] = this.sum (account['free'], account['used']);
                result[currency] = account;
            }
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetOrderBookPair (this.extend ({
            'pair': this.marketId (symbol),
        }, params));
        let timestamp = orderbook['timestamp'] * 1000;
        return this.parseOrderBook (orderbook, timestamp);
    }

    parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
        return [
            ohlcv[0] * 1000,
            ohlcv[1],
            ohlcv[2],
            ohlcv[3],
            ohlcv[4],
            ohlcv[5],
        ];
    }

    async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        if (!since)
            since = this.milliseconds () - 86400000; // yesterday
        let ymd = this.Ymd (since);
        ymd = ymd.split ('-');
        ymd = ymd.join ('');
        let request = {
            'pair': market['id'],
            'yyyymmdd': ymd,
        };
        let response = await this.publicGetOhlcvHdYyyymmddPair (this.extend (request, params));
        let key = 'data' + this.timeframes[timeframe];
        let ohlcvs = JSON.parse (response[key]);
        return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit);
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = undefined;
        let iso8601 = undefined;
        if ('timestamp' in ticker) {
            timestamp = parseInt (ticker['timestamp']) * 1000;
            iso8601 = this.iso8601 (timestamp);
        }
        let volume = this.safeFloat (ticker, 'volume');
        let high = this.safeFloat (ticker, 'high');
        let low = this.safeFloat (ticker, 'low');
        let bid = this.safeFloat (ticker, 'bid');
        let ask = this.safeFloat (ticker, 'ask');
        let last = this.safeFloat (ticker, 'last');
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': iso8601,
            'high': high,
            'low': low,
            'bid': bid,
            'ask': ask,
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': last,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': volume,
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let currencies = Object.keys (this.currencies);
        let response = await this.publicGetTickersCurrencies (this.extend ({
            'currencies': currencies.join ('/'),
        }, params));
        let tickers = response['data'];
        let result = {};
        for (let t = 0; t < tickers.length; t++) {
            let ticker = tickers[t];
            let symbol = ticker['pair'].replace (':', '/');
            let market = this.markets[symbol];
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let ticker = await this.publicGetTickerPair (this.extend ({
            'pair': market['id'],
        }, params));
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market = undefined) {
        let timestamp = parseInt (trade['date']) * 1000;
        return {
            'info': trade,
            'id': trade['tid'],
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': trade['type'],
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['amount']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTradeHistoryPair (this.extend ({
            'pair': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let order = {
            'pair': this.marketId (symbol),
            'type': side,
            'amount': amount,
        };
        if (type === 'limit') {
            order['price'] = price;
        } else {
            // for market buy CEX.io requires the amount of quote currency to spend
            if (side === 'buy') {
                if (!price) {
                    throw new InvalidOrder ('For market buy orders ' + this.id + " requires the amount of quote currency to spend, to calculate proper costs call createOrder (symbol, 'market', 'buy', amount, price)");
                }
                order['amount'] = amount * price;
            }
            order['order_type'] = type;
        }
        let response = await this.privatePostPlaceOrderPair (this.extend (order, params));
        return {
            'info': response,
            'id': response['id'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privatePostCancelOrder ({ 'id': id });
    }

    parseOrder (order, market = undefined) {
        let timestamp = parseInt (order['time']);
        let symbol = undefined;
        if (!market) {
            let symbol = order['symbol1'] + '/' + order['symbol2'];
            if (symbol in this.markets)
                market = this.market (symbol);
        }
        let status = order['status'];
        if (status === 'a') {
            status = 'open'; // the unified status
        } else if (status === 'cd') {
            status = 'canceled';
        } else if (status === 'c') {
            status = 'canceled';
        } else if (status === 'd') {
            status = 'closed';
        }
        let price = this.safeFloat (order, 'price');
        let amount = this.safeFloat (order, 'amount');
        let remaining = this.safeFloat (order, 'pending');
        if (!remaining)
            remaining = this.safeFloat (order, 'remains');
        let filled = amount - remaining;
        let fee = undefined;
        let cost = undefined;
        if (market) {
            symbol = market['symbol'];
            cost = this.safeFloat (order, 'ta:' + market['quote']);
            let baseFee = 'fa:' + market['base'];
            let quoteFee = 'fa:' + market['quote'];
            let feeRate = this.safeFloat (order, 'tradingFeeMaker');
            if (!feeRate)
                feeRate = this.safeFloat (order, 'tradingFeeTaker', feeRate);
            if (feeRate)
                feeRate /= 100.0; // convert to mathematically-correct percentage coefficients: 1.0 = 100%
            if (baseFee in order) {
                fee = {
                    'currency': market['base'],
                    'rate': feeRate,
                    'cost': this.safeFloat (order, baseFee),
                };
            } else if (quoteFee in order) {
                fee = {
                    'currency': market['quote'],
                    'rate': feeRate,
                    'cost': this.safeFloat (order, quoteFee),
                };
            }
        }
        if (!cost)
            cost = price * filled;
        return {
            'id': order['id'],
            'datetime': this.iso8601 (timestamp),
            'timestamp': timestamp,
            'status': status,
            'symbol': symbol,
            'type': undefined,
            'side': order['type'],
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'trades': undefined,
            'fee': fee,
            'info': order,
        };
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let request = {};
        let method = 'privatePostOpenOrders';
        let market = undefined;
        if (symbol) {
            market = this.market (symbol);
            request['pair'] = market['id'];
            method += 'Pair';
        }
        let orders = await this[method] (this.extend (request, params));
        for (let i = 0; i < orders.length; i++) {
            orders[i] = this.extend (orders[i], { 'status': 'open' });
        }
        return this.parseOrders (orders, market, since, limit);
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostGetOrder (this.extend ({
            'id': id.toString (),
        }, params));
        return this.parseOrder (response);
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let auth = nonce + this.uid + this.apiKey;
            let signature = this.hmac (this.encode (auth), this.encode (this.secret));
            body = this.urlencode (this.extend ({
                'key': this.apiKey,
                'signature': signature.toUpperCase (),
                'nonce': nonce,
            }, query));
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if (!response) {
            throw new ExchangeError (this.id + ' returned ' + this.json (response));
        } else if (response === true) {
            return response;
        } else if ('e' in response) {
            if ('ok' in response)
                if (response['ok'] === 'ok')
                    return response;
            throw new ExchangeError (this.id + ' ' + this.json (response));
        } else if ('error' in response) {
            if (response['error'])
                throw new ExchangeError (this.id + ' ' + this.json (response));
        }
        return response;
    }
};
<commit_msg>add lot size to cex.io<commit_after>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError, InvalidOrder } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class cex extends Exchange {

    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'cex',
            'name': 'CEX.IO',
            'countries': [ 'GB', 'EU', 'CY', 'RU' ],
            'rateLimit': 1500,
            'hasCORS': true,
            'hasFetchTickers': true,
            'hasFetchOHLCV': true,
            'hasFetchOpenOrders': true,
            'timeframes': {
                '1m': '1m',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766442-8ddc33b0-5ed8-11e7-8b98-f786aef0f3c9.jpg',
                'api': 'https://cex.io/api',
                'www': 'https://cex.io',
                'doc': 'https://cex.io/cex-api',
                'fees': [
                    'https://cex.io/fee-schedule',
                    'https://cex.io/limits-commissions',
                ],
            },
            'requiredCredentials': {
                'apiKey': true,
                'secret': true,
                'uid': true,
            },
            'api': {
                'public': {
                    'get': [
                        'currency_limits/',
                        'last_price/{pair}/',
                        'last_prices/{currencies}/',
                        'ohlcv/hd/{yyyymmdd}/{pair}',
                        'order_book/{pair}/',
                        'ticker/{pair}/',
                        'tickers/{currencies}/',
                        'trade_history/{pair}/',
                    ],
                    'post': [
                        'convert/{pair}',
                        'price_stats/{pair}',
                    ],
                },
                'private': {
                    'post': [
                        'active_orders_status/',
                        'archived_orders/{pair}/',
                        'balance/',
                        'cancel_order/',
                        'cancel_orders/{pair}/',
                        'cancel_replace_order/{pair}/',
                        'close_position/{pair}/',
                        'get_address/',
                        'get_myfee/',
                        'get_order/',
                        'get_order_tx/',
                        'open_orders/{pair}/',
                        'open_orders/',
                        'open_position/{pair}/',
                        'open_positions/{pair}/',
                        'place_order/{pair}/',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.16 / 100,
                    'taker': 0.25 / 100,
                },
                'funding': {
                    'withdraw': {
                        // 'USD': undefined,
                        // 'EUR': undefined,
                        // 'RUB': undefined,
                        // 'GBP': undefined,
                        'BTC': 0.001,
                        'ETH': 0.01,
                        'BCH': 0.001,
                        'DASH': 0.01,
                        'BTG': 0.001,
                        'ZEC': 0.001,
                        'XRP': 0.02,
                        'XLM': undefined,
                    },
                    'deposit': {
                        // 'USD': amount => amount * 0.035 + 0.25,
                        // 'EUR': amount => amount * 0.035 + 0.24,
                        // 'RUB': amount => amount * 0.05 + 15.57,
                        // 'GBP': amount => amount * 0.035 + 0.2,
                        'BTC': 0.0,
                        'ETH': 0.0,
                        'BCH': 0.0,
                        'DASH': 0.0,
                        'BTG': 0.0,
                        'ZEC': 0.0,
                        'XRP': 0.0,
                        'XLM': 0.0,
                    },
                },
            },
        });
    }

    async fetchMarkets () {
        let markets = await this.publicGetCurrencyLimits ();
        let result = [];
        for (let p = 0; p < markets['data']['pairs'].length; p++) {
            let market = markets['data']['pairs'][p];
            let id = market['symbol1'] + '/' + market['symbol2'];
            let symbol = id;
            let [ base, quote ] = symbol.split ('/');
            result.push ({
                'id': id,
                'info': market,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'lot': market['minLotSize'],
                'precision': {
                    'price': this.precisionFromString (market['minPrice']),
                    'amount': -1 * Math.log10 (market['minLotSize']),
                },
                'limits': {
                    'amount': {
                        'min': market['minLotSize'],
                        'max': market['maxLotSize'],
                    },
                    'price': {
                        'min': parseFloat (market['minPrice']),
                        'max': parseFloat (market['maxPrice']),
                    },
                    'cost': {
                        'min': market['minLotSizeS2'],
                        'max': undefined,
                    },
                },
            });
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostBalance ();
        let result = { 'info': response };
        let ommited = [ 'username', 'timestamp' ];
        let balances = this.omit (response, ommited);
        let currencies = Object.keys (balances);
        for (let i = 0; i < currencies.length; i++) {
            let currency = currencies[i];
            if (currency in balances) {
                let account = {
                    'free': this.safeFloat (balances[currency], 'available', 0.0),
                    'used': this.safeFloat (balances[currency], 'orders', 0.0),
                    'total': 0.0,
                };
                account['total'] = this.sum (account['free'], account['used']);
                result[currency] = account;
            }
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetOrderBookPair (this.extend ({
            'pair': this.marketId (symbol),
        }, params));
        let timestamp = orderbook['timestamp'] * 1000;
        return this.parseOrderBook (orderbook, timestamp);
    }

    parseOHLCV (ohlcv, market = undefined, timeframe = '1m', since = undefined, limit = undefined) {
        return [
            ohlcv[0] * 1000,
            ohlcv[1],
            ohlcv[2],
            ohlcv[3],
            ohlcv[4],
            ohlcv[5],
        ];
    }

    async fetchOHLCV (symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        if (!since)
            since = this.milliseconds () - 86400000; // yesterday
        let ymd = this.Ymd (since);
        ymd = ymd.split ('-');
        ymd = ymd.join ('');
        let request = {
            'pair': market['id'],
            'yyyymmdd': ymd,
        };
        let response = await this.publicGetOhlcvHdYyyymmddPair (this.extend (request, params));
        let key = 'data' + this.timeframes[timeframe];
        let ohlcvs = JSON.parse (response[key]);
        return this.parseOHLCVs (ohlcvs, market, timeframe, since, limit);
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = undefined;
        let iso8601 = undefined;
        if ('timestamp' in ticker) {
            timestamp = parseInt (ticker['timestamp']) * 1000;
            iso8601 = this.iso8601 (timestamp);
        }
        let volume = this.safeFloat (ticker, 'volume');
        let high = this.safeFloat (ticker, 'high');
        let low = this.safeFloat (ticker, 'low');
        let bid = this.safeFloat (ticker, 'bid');
        let ask = this.safeFloat (ticker, 'ask');
        let last = this.safeFloat (ticker, 'last');
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': iso8601,
            'high': high,
            'low': low,
            'bid': bid,
            'ask': ask,
            'vwap': undefined,
            'open': undefined,
            'close': undefined,
            'first': undefined,
            'last': last,
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': volume,
            'quoteVolume': undefined,
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let currencies = Object.keys (this.currencies);
        let response = await this.publicGetTickersCurrencies (this.extend ({
            'currencies': currencies.join ('/'),
        }, params));
        let tickers = response['data'];
        let result = {};
        for (let t = 0; t < tickers.length; t++) {
            let ticker = tickers[t];
            let symbol = ticker['pair'].replace (':', '/');
            let market = this.markets[symbol];
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let ticker = await this.publicGetTickerPair (this.extend ({
            'pair': market['id'],
        }, params));
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market = undefined) {
        let timestamp = parseInt (trade['date']) * 1000;
        return {
            'info': trade,
            'id': trade['tid'],
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': trade['type'],
            'price': parseFloat (trade['price']),
            'amount': parseFloat (trade['amount']),
        };
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetTradeHistoryPair (this.extend ({
            'pair': market['id'],
        }, params));
        return this.parseTrades (response, market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        let order = {
            'pair': this.marketId (symbol),
            'type': side,
            'amount': amount,
        };
        if (type === 'limit') {
            order['price'] = price;
        } else {
            // for market buy CEX.io requires the amount of quote currency to spend
            if (side === 'buy') {
                if (!price) {
                    throw new InvalidOrder ('For market buy orders ' + this.id + " requires the amount of quote currency to spend, to calculate proper costs call createOrder (symbol, 'market', 'buy', amount, price)");
                }
                order['amount'] = amount * price;
            }
            order['order_type'] = type;
        }
        let response = await this.privatePostPlaceOrderPair (this.extend (order, params));
        return {
            'info': response,
            'id': response['id'],
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privatePostCancelOrder ({ 'id': id });
    }

    parseOrder (order, market = undefined) {
        let timestamp = parseInt (order['time']);
        let symbol = undefined;
        if (!market) {
            let symbol = order['symbol1'] + '/' + order['symbol2'];
            if (symbol in this.markets)
                market = this.market (symbol);
        }
        let status = order['status'];
        if (status === 'a') {
            status = 'open'; // the unified status
        } else if (status === 'cd') {
            status = 'canceled';
        } else if (status === 'c') {
            status = 'canceled';
        } else if (status === 'd') {
            status = 'closed';
        }
        let price = this.safeFloat (order, 'price');
        let amount = this.safeFloat (order, 'amount');
        let remaining = this.safeFloat (order, 'pending');
        if (!remaining)
            remaining = this.safeFloat (order, 'remains');
        let filled = amount - remaining;
        let fee = undefined;
        let cost = undefined;
        if (market) {
            symbol = market['symbol'];
            cost = this.safeFloat (order, 'ta:' + market['quote']);
            let baseFee = 'fa:' + market['base'];
            let quoteFee = 'fa:' + market['quote'];
            let feeRate = this.safeFloat (order, 'tradingFeeMaker');
            if (!feeRate)
                feeRate = this.safeFloat (order, 'tradingFeeTaker', feeRate);
            if (feeRate)
                feeRate /= 100.0; // convert to mathematically-correct percentage coefficients: 1.0 = 100%
            if (baseFee in order) {
                fee = {
                    'currency': market['base'],
                    'rate': feeRate,
                    'cost': this.safeFloat (order, baseFee),
                };
            } else if (quoteFee in order) {
                fee = {
                    'currency': market['quote'],
                    'rate': feeRate,
                    'cost': this.safeFloat (order, quoteFee),
                };
            }
        }
        if (!cost)
            cost = price * filled;
        return {
            'id': order['id'],
            'datetime': this.iso8601 (timestamp),
            'timestamp': timestamp,
            'status': status,
            'symbol': symbol,
            'type': undefined,
            'side': order['type'],
            'price': price,
            'cost': cost,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'trades': undefined,
            'fee': fee,
            'info': order,
        };
    }

    async fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let request = {};
        let method = 'privatePostOpenOrders';
        let market = undefined;
        if (symbol) {
            market = this.market (symbol);
            request['pair'] = market['id'];
            method += 'Pair';
        }
        let orders = await this[method] (this.extend (request, params));
        for (let i = 0; i < orders.length; i++) {
            orders[i] = this.extend (orders[i], { 'status': 'open' });
        }
        return this.parseOrders (orders, market, since, limit);
    }

    async fetchOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.privatePostGetOrder (this.extend ({
            'id': id.toString (),
        }, params));
        return this.parseOrder (response);
    }

    nonce () {
        return this.milliseconds ();
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'public') {
            if (Object.keys (query).length)
                url += '?' + this.urlencode (query);
        } else {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let auth = nonce + this.uid + this.apiKey;
            let signature = this.hmac (this.encode (auth), this.encode (this.secret));
            body = this.urlencode (this.extend ({
                'key': this.apiKey,
                'signature': signature.toUpperCase (),
                'nonce': nonce,
            }, query));
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            };
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if (!response) {
            throw new ExchangeError (this.id + ' returned ' + this.json (response));
        } else if (response === true) {
            return response;
        } else if ('e' in response) {
            if ('ok' in response)
                if (response['ok'] === 'ok')
                    return response;
            throw new ExchangeError (this.id + ' ' + this.json (response));
        } else if ('error' in response) {
            if (response['error'])
                throw new ExchangeError (this.id + ' ' + this.json (response));
        }
        return response;
    }
};
<|endoftext|>


<commit_before>// Recieve message from content script
// If response.showPageActionIcon === true,
// We've got possible songs on page
// Create a new Tab object
function onMessage(e, sender, responseCallback){
    console.log('onMessage', e, sender);
    var type = e.type;
    switch(type){
        case 'scanDone':
            if(e.response.showPageActionIcon === true){
                var tab = new Tab(sender, e.response, true);
            }
        break;
        case 'openTab':
            chrome.tabs.create(
                {
                    'url': e.url
                }
            );
        break;
        case 'deepScan':
            chrome.storage.local.get("tab" + sender.tab.id, function(savedTab){
                var props = savedTab["tab" + sender.tab.id];
                var tab = new Tab(props.sender, props.response, false);
                tab.deepScan();
            });
        break;
        case 'toggleMinimize':
            chrome.tabs.sendMessage(sender.tab.id,
                {
                    "type": "toggleMinimize"
                }
            );
        break;
        case 'minimizeEnd':
            chrome.tabs.sendMessage(sender.tab.id,
                {
                    "type": "minimizeEnd"
                }
            );
        break;
        case 'maximizeEnd':
            chrome.tabs.sendMessage(sender.tab.id,
                {
                    "type": "maximizeEnd"
                }
            );
        break;
        case 'getDataUrl':
            responseCallback(dataUrlObj[sender.tab.id]);
            delete dataUrlObj[sender.tab.id];
        break;
        case 'tumblrLike':
            var tumblr = new Tumblr(sender.tab);
            tumblr.like(e.id, e.reblogKey);
        break;
        case 'soundcloudFavorite':
            var soundcloud = new Soundcloud(sender.tab);
            soundcloud.favorite(e.id);
        break;
        case 'soundcloudResolveThenFavorite':
            var soundcloud = new Soundcloud(sender.tab);
            soundcloud.resolveThenFavorite(e.url);
        break;
        case 'getId3':
            id3(e.url, function(err, tags) {
                if(!err){
                    responseCallback(tags);
                }
                else{
                    responseCallback(null);
                }
            });
            return true;
        break;
        case 'nowPlaying':
            var lastfm = new Lastfm(sender.tab);
            lastfm.nowPlaying(e.song);
        break;
        case 'songHalf':
            var lastfm = new Lastfm(sender.tab);
            lastfm.scrobble(e.song);
        break;
        case 'rdioSave':
            var rdio = new Rdio(sender.tab);
            rdio.save(e.title, e.artist);
        break;
        default:
        break
    }
}
chrome.runtime.onMessage.addListener(this.onMessage);

// Page action was clicked
// get a ref to tab and do a deep scan for songs
function onPageActionClicked(sender){
    chrome.storage.local.get("tab" + sender.id, function(savedTab){
        var props = savedTab["tab" + sender.id];
        var tab = new Tab(props.sender, props.response, false);
        tab.onPageActionClicked();
    });
}
chrome.pageAction.onClicked.addListener(this.onPageActionClicked);

// tab was removed
// remove it from storage
function onTabRemoved(tabId){
    chrome.storage.local.remove("tab" + tabId);
}
chrome.tabs.onRemoved.addListener(this.onTabRemoved);

// listen for keyboard commands
chrome.commands.onCommand.addListener(function(command) {
    console.log('Command:', command);
    if(command === 'minimize-player'){
        chrome.tabs.getSelected(null, function(tab){
            chrome.tabs.sendMessage(tab.id,
                {
                    "type": "toggleMinimize"
                }
            );
        })
    }
});<commit_msg>was giving services wrong tab object. now its the right one<commit_after>// Recieve message from content script
// If response.showPageActionIcon === true,
// We've got possible songs on page
// Create a new Tab object
function onMessage(e, sender, responseCallback){
    console.log('onMessage', e, sender);
    var type = e.type;
    switch(type){
        case 'scanDone':
            if(e.response.showPageActionIcon === true){
                var tab = new Tab(sender, e.response, true);
            }
        break;
        case 'openTab':
            chrome.tabs.create(
                {
                    'url': e.url
                }
            );
        break;
        case 'deepScan':
            chrome.storage.local.get("tab" + sender.tab.id, function(savedTab){
                var props = savedTab["tab" + sender.tab.id];
                var tab = new Tab(props.sender, props.response, false);
                tab.deepScan();
            });
        break;
        case 'toggleMinimize':
            chrome.tabs.sendMessage(sender.tab.id,
                {
                    "type": "toggleMinimize"
                }
            );
        break;
        case 'minimizeEnd':
            chrome.tabs.sendMessage(sender.tab.id,
                {
                    "type": "minimizeEnd"
                }
            );
        break;
        case 'maximizeEnd':
            chrome.tabs.sendMessage(sender.tab.id,
                {
                    "type": "maximizeEnd"
                }
            );
        break;
        case 'getDataUrl':
            responseCallback(dataUrlObj[sender.tab.id]);
            delete dataUrlObj[sender.tab.id];
        break;
        case 'tumblrLike':
            var tab = new Tab(sender, null, false);
            var tumblr = new Tumblr(tab);
            tumblr.like(e.id, e.reblogKey);
        break;
        case 'soundcloudFavorite':
            var tab = new Tab(sender, null, false);
            var soundcloud = new Soundcloud(tab);
            soundcloud.favorite(e.id);
        break;
        case 'soundcloudResolveThenFavorite':
            var tab = new Tab(sender, null, false);
            var soundcloud = new Soundcloud(tab);
            soundcloud.resolveThenFavorite(e.url);
        break;
        case 'getId3':
            id3(e.url, function(err, tags) {
                if(!err){
                    responseCallback(tags);
                }
                else{
                    responseCallback(null);
                }
            });
            return true;
        break;
        case 'nowPlaying':
            var tab = new Tab(sender, null, false);
            var lastfm = new Lastfm(tab);
            lastfm.nowPlaying(e.song);
        break;
        case 'songHalf':
            var tab = new Tab(sender, null, false);
            var lastfm = new Lastfm(tab);
            lastfm.scrobble(e.song);
        break;
        case 'rdioSave':
            var tab = new Tab(sender, null, false);
            var rdio = new Rdio(tab);
            rdio.save(e.title, e.artist);
        break;
        default:
        break
    }
}
chrome.runtime.onMessage.addListener(this.onMessage);

// Page action was clicked
// get a ref to tab and do a deep scan for songs
function onPageActionClicked(sender){
    chrome.storage.local.get("tab" + sender.id, function(savedTab){
        var props = savedTab["tab" + sender.id];
        var tab = new Tab(props.sender, props.response, false);
        tab.onPageActionClicked();
    });
}
chrome.pageAction.onClicked.addListener(this.onPageActionClicked);

// tab was removed
// remove it from storage
function onTabRemoved(tabId){
    chrome.storage.local.remove("tab" + tabId);
}
chrome.tabs.onRemoved.addListener(this.onTabRemoved);

// listen for keyboard commands
chrome.commands.onCommand.addListener(function(command) {
    console.log('Command:', command);
    if(command === 'minimize-player'){
        chrome.tabs.getSelected(null, function(tab){
            chrome.tabs.sendMessage(tab.id,
                {
                    "type": "toggleMinimize"
                }
            );
        })
    }
});<|endoftext|>


<commit_before>'use strict';

var express = require('express'),
    path = require('path'),
    config = require('./config');

/**
 * Express configuration
 */
module.exports = function(app) {
  app.configure('development', function(){
    app.use(require('connect-livereload')());

    // Disable caching of scripts for easier testing
    app.use(function noCache(req, res, next) {
      if (req.url.indexOf('/scripts/') === 0) {
        res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
        res.header('Pragma', 'no-cache');
        res.header('Expires', 0);
      }
      next();
    });
    app.use(express.compress());
    app.use(express.static(path.join(config.root, '.tmp')));
    app.use(express.static(path.join(config.root, 'app')));
    app.use(express.errorHandler());
    app.set('views', config.root + '/app/views');
  });

  app.configure('production', function(){
    app.use(express.favicon(path.join(config.root, 'public', 'favicon.ico')));
    app.use(express.compress());
    app.use(express.static(path.join(config.root, 'public')));
    app.set('views', config.root + '/views');
  });

  app.configure(function(){
    app.engine('html', require('ejs').renderFile);
    app.set('view engine', 'html');
    app.use(express.logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded());
    app.use(express.methodOverride());
    // Router needs to be last
    app.use(app.router);
  });
};

<commit_msg>fix bug with connect-livereload injection<commit_after>'use strict';

var express = require('express'),
    path = require('path'),
    config = require('./config');

/**
 * Express configuration
 */
module.exports = function(app) {
  app.configure('development', function(){
    app.use(require('connect-livereload')({
      // livereload is incompatible with the current method
      // of streaming data from the API for some maps.
      // see https://github.com/mscdex/busboy/issues/79#issuecomment-108684031
      ignore: [/api/],
    }));

    // Disable caching of scripts for easier testing
    app.use(function noCache(req, res, next) {
      if (req.url.indexOf('/scripts/') === 0) {
        res.header('Cache-Control', 'no-cache, no-store, must-revalidate');
        res.header('Pragma', 'no-cache');
        res.header('Expires', 0);
      }
      next();
    });
    app.use(express.compress());
    app.use(express.static(path.join(config.root, '.tmp')));
    app.use(express.static(path.join(config.root, 'app')));
    app.use(express.errorHandler());
    app.set('views', config.root + '/app/views');
  });

  app.configure('production', function(){
    app.use(express.favicon(path.join(config.root, 'public', 'favicon.ico')));
    app.use(express.compress());
    app.use(express.static(path.join(config.root, 'public')));
    app.set('views', config.root + '/views');
  });

  app.configure(function(){
    app.engine('html', require('ejs').renderFile);
    app.set('view engine', 'html');
    app.use(express.logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded());
    app.use(express.methodOverride());
    // Router needs to be last
    app.use(app.router);
  });
};

<|endoftext|>


<commit_before>// Copyright 2016-2018, University of Colorado Boulder

/**
 * base class for light bulbs in the model
 *
 * @author John Blanco
 * @author Andrew Adare
 */
define( function( require ) {
  'use strict';

  // modules
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkPathMover = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyChunkPathMover' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var EnergyUser = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyUser' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Property = require( 'AXON/Property' );
  var Range = require( 'DOT/Range' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );

  var OFFSET_TO_LEFT_SIDE_OF_WIRE = new Vector2( -0.04, -0.04 );
  var OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND = new Vector2( -0.02, -0.04 );
  var OFFSET_TO_FIRST_WIRE_CURVE_POINT = new Vector2( -0.01, -0.0375 );
  var OFFSET_TO_SECOND_WIRE_CURVE_POINT = new Vector2( -0.001, -0.025 );
  var OFFSET_TO_THIRD_WIRE_CURVE_POINT = new Vector2( -0.0005, -0.0175 );
  var OFFSET_TO_BOTTOM_OF_CONNECTOR = new Vector2( 0, -0.01 );
  var OFFSET_TO_RADIATE_POINT = new Vector2( 0, 0.066 );

  var RADIATED_ENERGY_CHUNK_MAX_DISTANCE = 0.5;
  var THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT = new Range( 2, 2.5 );
  var ENERGY_TO_FULLY_LIGHT = EFACConstants.MAX_ENERGY_PRODUCTION_RATE;
  var LIGHT_CHUNK_LIT_BULB_RADIUS = 0.1; // In meters.
  var LIGHT_CHANGE_RATE = 0.5; // In proportion per second.

  /**
   * @param {Image} iconImage
   * @param {boolean} hasFilament
   * @param {Property.<boolean>} energyChunksVisibleProperty
   * @constructor
   */
  function LightBulb( iconImage, hasFilament, energyChunksVisibleProperty ) {

    EnergyUser.call( this, iconImage );

    // @public (read-only) {NumberProperty}
    this.litProportionProperty = new Property( 0 );

    // @private
    this.hasFilament = hasFilament;
    this.energyChunksVisibleProperty = energyChunksVisibleProperty;

    // @private {number} - fewer thermal energy chunks are radiated for bulbs without a filament
    this.proportionOfThermalChunksRadiated = hasFilament ? 0.35 : 0.2;

    // @private - movers and flags that control how the energy chunks move through the light bulb
    this.electricalEnergyChunkMovers = [];
    this.filamentEnergyChunkMovers = [];
    this.radiatedEnergyChunkMovers = [];
    this.goRightNextTime = true;
  }

  energyFormsAndChanges.register( 'LightBulb', LightBulb );

  return inherit( EnergyUser, LightBulb, {

    /**
     * @param  {number} dt - time step, in seconds
     * @param  {Energy} incomingEnergy
     * @public
     * @override
     */
    step: function( dt, incomingEnergy ) {
      var self = this;
      if ( this.activeProperty.value ) {

        // handle any incoming energy chunks
        if ( this.incomingEnergyChunks.length > 0 ) {

          var incomingChunks = _.clone( this.incomingEnergyChunks );

          incomingChunks.forEach( function( incomingChunk ) {

            if ( incomingChunk.energyTypeProperty.get() === EnergyType.ELECTRICAL ) {

              // add the energy chunk to the list of those under management
              self.energyChunkList.push( incomingChunk );

              // add a "mover" that will move this energy chunk through the wire to the bulb
              self.electricalEnergyChunkMovers.push(
                new EnergyChunkPathMover(
                  incomingChunk,
                  self.createElectricalEnergyChunkPath( self.positionProperty.value ),
                  EFACConstants.ENERGY_CHUNK_VELOCITY )
              );
            }

            // yy design, this shouldn't happen, so warn if it does
            else {
              assert && assert(
                false,
                'Encountered energy chunk with unexpected type: ' + self.incomingEnergyChunk.energyTypeProperty.get()
              );
            }
          } );

          self.incomingEnergyChunks.length = 0;
        }

        // move all of the energy chunks
        this.moveElectricalEnergyChunks( dt );
        this.moveFilamentEnergyChunks( dt );
        this.moveRadiatedEnergyChunks( dt );

        // set how lit the bulb is
        if ( this.energyChunksVisibleProperty.get() ) {

          // energy chunks are visible, so the lit proportion is dependent upon whether light energy chunks are present
          var lightChunksInLitRadius = 0;

          this.radiatedEnergyChunkMovers.forEach( function( mover ) {
            var distance = mover.energyChunk.positionProperty.value.distance( self.positionProperty.value.plus( OFFSET_TO_RADIATE_POINT ) );
            if ( distance < LIGHT_CHUNK_LIT_BULB_RADIUS ) {
              lightChunksInLitRadius++;
            }
          } );

          if ( lightChunksInLitRadius > 0 ) {

            // light is on
            this.litProportionProperty.set( Math.min( 1, this.litProportionProperty.get() + LIGHT_CHANGE_RATE * dt ) );
          } else {

            // light is off
            this.litProportionProperty.set( Math.max( 0, this.litProportionProperty.get() - LIGHT_CHANGE_RATE * dt ) );
          }
        }

        // energy chunks not currently visible
        else {
          if ( this.activeProperty.value && incomingEnergy.type === EnergyType.ELECTRICAL ) {
            this.litProportionProperty.set( Util.clamp( incomingEnergy.amount / ( ENERGY_TO_FULLY_LIGHT * dt ), 0, 1 ) );
          } else {
            this.litProportionProperty.set( 0.0 );
          }
        }
      }
    },

    /**
     * @param  {number} dt - time step, in seconds
     * @private
     */
    moveRadiatedEnergyChunks: function( dt ) {

      // iterate over a copy to mutate original without problems
      var movers = _.clone( this.radiatedEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        // remove the chunk and its mover
        if ( mover.pathFullyTraversed ) {
          self.energyChunkList.remove( mover.energyChunk );
          _.pull( self.radiatedEnergyChunkMovers, mover );
        }
      } );
    },

    /**
     * @param  {number} dt - time step, in seconds
     * @private
     */
    moveFilamentEnergyChunks: function( dt ) {

      // iterate over a copy to mutate original without problems
      var movers = _.clone( this.filamentEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        // cause this energy chunk to be radiated from the bulb
        if ( mover.pathFullyTraversed ) {
          _.pull( self.filamentEnergyChunkMovers, mover );
          self.radiateEnergyChunk( mover.energyChunk );
        }
      } );
    },

    /**
     * @param  {number} dt - time step, in seconds
     * @private
     */
    moveElectricalEnergyChunks: function( dt ) {

      // iterate over a copy to mutate original without problems
      var movers = _.clone( this.electricalEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        if ( mover.pathFullyTraversed ) {
          _.pull( self.electricalEnergyChunkMovers, mover );

          // turn this energy chunk into thermal energy on the filament
          if ( self.hasFilament ) {
            mover.energyChunk.energyTypeProperty.set( EnergyType.THERMAL );
            var path = self.createThermalEnergyChunkPath( mover.energyChunk.positionProperty.value );
            var speed = self.getTotalPathLength( mover.energyChunk.positionProperty.value, path ) /
              self.generateThermalChunkTimeOnFilament();

            self.filamentEnergyChunkMovers.push( new EnergyChunkPathMover( mover.energyChunk, path, speed ) );
          } else {

            // there is no filament, so just radiate the chunk
            self.radiateEnergyChunk( mover.energyChunk );
          }
        }
      } );
    },

    /**
     * @param  {Energy} incomingEnergy
     * @public
     * @override
     */
    preLoadEnergyChunks: function( incomingEnergy ) {

      this.clearEnergyChunks();

      if ( incomingEnergy.amount < EFACConstants.MAX_ENERGY_PRODUCTION_RATE / 10 ||
        incomingEnergy.type !== EnergyType.ELECTRICAL ) {

        // no energy chunk pre-loading needed
        return;
      }

      var dt = 1 / EFACConstants.FRAMES_PER_SECOND;
      var energySinceLastChunk = EFACConstants.ENERGY_PER_CHUNK * 0.99;

      // simulate energy chunks moving through the system
      var preLoadComplete = false;
      while ( !preLoadComplete ) {
        this.energySinceLastChunk += incomingEnergy.amount * dt;

        // determine if time to add a new chunk
        if ( this.energySinceLastChunk >= EFACConstants.ENERGY_PER_CHUNK ) {
          var newEnergyChunk = new EnergyChunk(
            EnergyType.ELECTRICAL,
            this.positionProperty.value.plus( OFFSET_TO_LEFT_SIDE_OF_WIRE ),
            this.energyChunksVisibleProperty
          );

          this.energyChunkList.push( newEnergyChunk );

          // add a "mover" that will move this energy chunk through the wire to the heating element
          this.electricalEnergyChunkMovers.push( new EnergyChunkPathMover(
            newEnergyChunk,
            this.createElectricalEnergyChunkPath( this.positionProperty.value ),
            EFACConstants.ENERGY_CHUNK_VELOCITY
          ) );

          // update energy since last chunk
          energySinceLastChunk = energySinceLastChunk - EFACConstants.ENERGY_PER_CHUNK;
        }

        this.moveElectricalEnergyChunks( dt );
        this.moveFilamentEnergyChunks( dt );

        if ( this.radiatedEnergyChunkMovers.length > 1 ) {

          // a couple of chunks are radiating, which completes the pre-load
          preLoadComplete = true;
        }
      }
    },

    /**
     * @param  {EnergyChunk} energyChunk
     * @private
     */
    radiateEnergyChunk: function( energyChunk ) {
      if ( phet.joist.random.nextDouble() > this.proportionOfThermalChunksRadiated ) {
        energyChunk.energyTypeProperty.set( EnergyType.LIGHT );
      } else {
        energyChunk.energyTypeProperty.set( EnergyType.THERMAL );
      }

      // path of radiated light chunks
      var path = [];

      path.push( this.positionProperty.value
        .plus( OFFSET_TO_RADIATE_POINT )
        .plus( new Vector2( 0, RADIATED_ENERGY_CHUNK_MAX_DISTANCE )
          .rotated( ( phet.joist.random.nextDouble() - 0.5 ) * ( Math.PI / 2 ) )
        )
      );

      this.radiatedEnergyChunkMovers.push( new EnergyChunkPathMover(
        energyChunk,
        path,
        EFACConstants.ENERGY_CHUNK_VELOCITY )
      );
    },

    /**
     * @param  {Vector2} startingPoint
     * @returns {Vector2[]}
     * @private
     */
    createThermalEnergyChunkPath: function( startingPoint ) {
      var path = [];
      var filamentWidth = 0.03;
      var x = (0.5 + phet.joist.random.nextDouble() / 2) * filamentWidth / 2 * (this.goRightNextTime ? 1 : -1);

      path.push( startingPoint.plus( new Vector2( x, 0 ) ) );
      this.goRightNextTime = !this.goRightNextTime;

      return path;
    },

    /**
     * @param  {Vector2} center
     * @returns {Vector2[]}
     * @private
     */
    createElectricalEnergyChunkPath: function( center ) {
      var path = [];

      path.push( center.plus( OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND ) );
      path.push( center.plus( OFFSET_TO_FIRST_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_SECOND_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_THIRD_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_BOTTOM_OF_CONNECTOR ) );
      path.push( center.plus( OFFSET_TO_RADIATE_POINT ) );

      return path;
    },

    /**
     * @returns {number} time
     * @private
     */
    generateThermalChunkTimeOnFilament: function() {
      return THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT.min +
             phet.joist.random.nextDouble() * THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT.getLength();
    },

    /**
     * @param {Vector2} startingLocation
     * @param {Vector2[]} pathPoints
     * @returns {number}
     * @private
     */
    getTotalPathLength: function( startingLocation, pathPoints ) {
      if ( pathPoints.length === 0 ) {
        return 0;
      }

      var pathLength = startingLocation.distance( pathPoints[ 0 ] );
      for ( var i = 0; i < pathPoints.length - 1; i++ ) {
        pathLength += pathPoints[ i ].distance( pathPoints[ i + 1 ] );
      }

      return pathLength;
    },

    /**
     * deactivate the light bulb
     * @public
     * @override
     */
    deactivate: function() {
      EnergyUser.prototype.deactivate.call( this );
      this.litProportionProperty.set( 0 );
    },

    /**
     * @public
     * @override
     */
    clearEnergyChunks: function() {
      EnergyUser.prototype.clearEnergyChunks.call( this );
      this.electricalEnergyChunkMovers.length = 0;
      this.filamentEnergyChunkMovers.length = 0;
      this.radiatedEnergyChunkMovers.length = 0;
      this.incomingEnergyChunks.length = 0;
    },

    /**
     * restore the initial state
     * @public
     */
    reset: function() {
      this.litProportionProperty.reset();
    }
  } );
} );

<commit_msg>Update light bulb chunk path, see https://github.com/phetsims/energy-forms-and-changes/issues/91<commit_after>// Copyright 2016-2018, University of Colorado Boulder

/**
 * base class for light bulbs in the model
 *
 * @author John Blanco
 * @author Andrew Adare
 */
define( function( require ) {
  'use strict';

  // modules
  var EFACConstants = require( 'ENERGY_FORMS_AND_CHANGES/common/EFACConstants' );
  var EnergyChunk = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyChunk' );
  var EnergyChunkPathMover = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyChunkPathMover' );
  var energyFormsAndChanges = require( 'ENERGY_FORMS_AND_CHANGES/energyFormsAndChanges' );
  var EnergyType = require( 'ENERGY_FORMS_AND_CHANGES/common/model/EnergyType' );
  var EnergyUser = require( 'ENERGY_FORMS_AND_CHANGES/energy-systems/model/EnergyUser' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Property = require( 'AXON/Property' );
  var Range = require( 'DOT/Range' );
  var Util = require( 'DOT/Util' );
  var Vector2 = require( 'DOT/Vector2' );

  var OFFSET_TO_LEFT_SIDE_OF_WIRE = new Vector2( -0.04, -0.041 );
  var OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND = new Vector2( -0.02, -0.041 );
  var OFFSET_TO_FIRST_WIRE_CURVE_POINT = new Vector2( -0.01, -0.0375 );
  var OFFSET_TO_SECOND_WIRE_CURVE_POINT = new Vector2( -0.001, -0.025 );
  var OFFSET_TO_THIRD_WIRE_CURVE_POINT = new Vector2( -0.0003, -0.0175 );
  var OFFSET_TO_BOTTOM_OF_CONNECTOR = new Vector2( 0.0002, -0.01 );
  var OFFSET_TO_RADIATE_POINT = new Vector2( 0.0002, 0.066 );

  var RADIATED_ENERGY_CHUNK_MAX_DISTANCE = 0.5;
  var THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT = new Range( 2, 2.5 );
  var ENERGY_TO_FULLY_LIGHT = EFACConstants.MAX_ENERGY_PRODUCTION_RATE;
  var LIGHT_CHUNK_LIT_BULB_RADIUS = 0.1; // In meters.
  var LIGHT_CHANGE_RATE = 0.5; // In proportion per second.

  /**
   * @param {Image} iconImage
   * @param {boolean} hasFilament
   * @param {Property.<boolean>} energyChunksVisibleProperty
   * @constructor
   */
  function LightBulb( iconImage, hasFilament, energyChunksVisibleProperty ) {

    EnergyUser.call( this, iconImage );

    // @public (read-only) {NumberProperty}
    this.litProportionProperty = new Property( 0 );

    // @private
    this.hasFilament = hasFilament;
    this.energyChunksVisibleProperty = energyChunksVisibleProperty;

    // @private {number} - fewer thermal energy chunks are radiated for bulbs without a filament
    this.proportionOfThermalChunksRadiated = hasFilament ? 0.35 : 0.2;

    // @private - movers and flags that control how the energy chunks move through the light bulb
    this.electricalEnergyChunkMovers = [];
    this.filamentEnergyChunkMovers = [];
    this.radiatedEnergyChunkMovers = [];
    this.goRightNextTime = true;
  }

  energyFormsAndChanges.register( 'LightBulb', LightBulb );

  return inherit( EnergyUser, LightBulb, {

    /**
     * @param  {number} dt - time step, in seconds
     * @param  {Energy} incomingEnergy
     * @public
     * @override
     */
    step: function( dt, incomingEnergy ) {
      var self = this;
      if ( this.activeProperty.value ) {

        // handle any incoming energy chunks
        if ( this.incomingEnergyChunks.length > 0 ) {

          var incomingChunks = _.clone( this.incomingEnergyChunks );

          incomingChunks.forEach( function( incomingChunk ) {

            if ( incomingChunk.energyTypeProperty.get() === EnergyType.ELECTRICAL ) {

              // add the energy chunk to the list of those under management
              self.energyChunkList.push( incomingChunk );

              // add a "mover" that will move this energy chunk through the wire to the bulb
              self.electricalEnergyChunkMovers.push(
                new EnergyChunkPathMover(
                  incomingChunk,
                  self.createElectricalEnergyChunkPath( self.positionProperty.value ),
                  EFACConstants.ENERGY_CHUNK_VELOCITY )
              );
            }

            // yy design, this shouldn't happen, so warn if it does
            else {
              assert && assert(
                false,
                'Encountered energy chunk with unexpected type: ' + self.incomingEnergyChunk.energyTypeProperty.get()
              );
            }
          } );

          self.incomingEnergyChunks.length = 0;
        }

        // move all of the energy chunks
        this.moveElectricalEnergyChunks( dt );
        this.moveFilamentEnergyChunks( dt );
        this.moveRadiatedEnergyChunks( dt );

        // set how lit the bulb is
        if ( this.energyChunksVisibleProperty.get() ) {

          // energy chunks are visible, so the lit proportion is dependent upon whether light energy chunks are present
          var lightChunksInLitRadius = 0;

          this.radiatedEnergyChunkMovers.forEach( function( mover ) {
            var distance = mover.energyChunk.positionProperty.value.distance( self.positionProperty.value.plus( OFFSET_TO_RADIATE_POINT ) );
            if ( distance < LIGHT_CHUNK_LIT_BULB_RADIUS ) {
              lightChunksInLitRadius++;
            }
          } );

          if ( lightChunksInLitRadius > 0 ) {

            // light is on
            this.litProportionProperty.set( Math.min( 1, this.litProportionProperty.get() + LIGHT_CHANGE_RATE * dt ) );
          } else {

            // light is off
            this.litProportionProperty.set( Math.max( 0, this.litProportionProperty.get() - LIGHT_CHANGE_RATE * dt ) );
          }
        }

        // energy chunks not currently visible
        else {
          if ( this.activeProperty.value && incomingEnergy.type === EnergyType.ELECTRICAL ) {
            this.litProportionProperty.set( Util.clamp( incomingEnergy.amount / ( ENERGY_TO_FULLY_LIGHT * dt ), 0, 1 ) );
          } else {
            this.litProportionProperty.set( 0.0 );
          }
        }
      }
    },

    /**
     * @param  {number} dt - time step, in seconds
     * @private
     */
    moveRadiatedEnergyChunks: function( dt ) {

      // iterate over a copy to mutate original without problems
      var movers = _.clone( this.radiatedEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        // remove the chunk and its mover
        if ( mover.pathFullyTraversed ) {
          self.energyChunkList.remove( mover.energyChunk );
          _.pull( self.radiatedEnergyChunkMovers, mover );
        }
      } );
    },

    /**
     * @param  {number} dt - time step, in seconds
     * @private
     */
    moveFilamentEnergyChunks: function( dt ) {

      // iterate over a copy to mutate original without problems
      var movers = _.clone( this.filamentEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        // cause this energy chunk to be radiated from the bulb
        if ( mover.pathFullyTraversed ) {
          _.pull( self.filamentEnergyChunkMovers, mover );
          self.radiateEnergyChunk( mover.energyChunk );
        }
      } );
    },

    /**
     * @param  {number} dt - time step, in seconds
     * @private
     */
    moveElectricalEnergyChunks: function( dt ) {

      // iterate over a copy to mutate original without problems
      var movers = _.clone( this.electricalEnergyChunkMovers );

      var self = this;

      movers.forEach( function( mover ) {
        mover.moveAlongPath( dt );

        if ( mover.pathFullyTraversed ) {
          _.pull( self.electricalEnergyChunkMovers, mover );

          // turn this energy chunk into thermal energy on the filament
          if ( self.hasFilament ) {
            mover.energyChunk.energyTypeProperty.set( EnergyType.THERMAL );
            var path = self.createThermalEnergyChunkPath( mover.energyChunk.positionProperty.value );
            var speed = self.getTotalPathLength( mover.energyChunk.positionProperty.value, path ) /
              self.generateThermalChunkTimeOnFilament();

            self.filamentEnergyChunkMovers.push( new EnergyChunkPathMover( mover.energyChunk, path, speed ) );
          } else {

            // there is no filament, so just radiate the chunk
            self.radiateEnergyChunk( mover.energyChunk );
          }
        }
      } );
    },

    /**
     * @param  {Energy} incomingEnergy
     * @public
     * @override
     */
    preLoadEnergyChunks: function( incomingEnergy ) {

      this.clearEnergyChunks();

      if ( incomingEnergy.amount < EFACConstants.MAX_ENERGY_PRODUCTION_RATE / 10 ||
        incomingEnergy.type !== EnergyType.ELECTRICAL ) {

        // no energy chunk pre-loading needed
        return;
      }

      var dt = 1 / EFACConstants.FRAMES_PER_SECOND;
      var energySinceLastChunk = EFACConstants.ENERGY_PER_CHUNK * 0.99;

      // simulate energy chunks moving through the system
      var preLoadComplete = false;
      while ( !preLoadComplete ) {
        this.energySinceLastChunk += incomingEnergy.amount * dt;

        // determine if time to add a new chunk
        if ( this.energySinceLastChunk >= EFACConstants.ENERGY_PER_CHUNK ) {
          var newEnergyChunk = new EnergyChunk(
            EnergyType.ELECTRICAL,
            this.positionProperty.value.plus( OFFSET_TO_LEFT_SIDE_OF_WIRE ),
            this.energyChunksVisibleProperty
          );

          this.energyChunkList.push( newEnergyChunk );

          // add a "mover" that will move this energy chunk through the wire to the heating element
          this.electricalEnergyChunkMovers.push( new EnergyChunkPathMover(
            newEnergyChunk,
            this.createElectricalEnergyChunkPath( this.positionProperty.value ),
            EFACConstants.ENERGY_CHUNK_VELOCITY
          ) );

          // update energy since last chunk
          energySinceLastChunk = energySinceLastChunk - EFACConstants.ENERGY_PER_CHUNK;
        }

        this.moveElectricalEnergyChunks( dt );
        this.moveFilamentEnergyChunks( dt );

        if ( this.radiatedEnergyChunkMovers.length > 1 ) {

          // a couple of chunks are radiating, which completes the pre-load
          preLoadComplete = true;
        }
      }
    },

    /**
     * @param  {EnergyChunk} energyChunk
     * @private
     */
    radiateEnergyChunk: function( energyChunk ) {
      if ( phet.joist.random.nextDouble() > this.proportionOfThermalChunksRadiated ) {
        energyChunk.energyTypeProperty.set( EnergyType.LIGHT );
      } else {
        energyChunk.energyTypeProperty.set( EnergyType.THERMAL );
      }

      // path of radiated light chunks
      var path = [];

      path.push( this.positionProperty.value
        .plus( OFFSET_TO_RADIATE_POINT )
        .plus( new Vector2( 0, RADIATED_ENERGY_CHUNK_MAX_DISTANCE )
          .rotated( ( phet.joist.random.nextDouble() - 0.5 ) * ( Math.PI / 2 ) )
        )
      );

      this.radiatedEnergyChunkMovers.push( new EnergyChunkPathMover(
        energyChunk,
        path,
        EFACConstants.ENERGY_CHUNK_VELOCITY )
      );
    },

    /**
     * @param  {Vector2} startingPoint
     * @returns {Vector2[]}
     * @private
     */
    createThermalEnergyChunkPath: function( startingPoint ) {
      var path = [];
      var filamentWidth = 0.03;
      var x = (0.5 + phet.joist.random.nextDouble() / 2) * filamentWidth / 2 * (this.goRightNextTime ? 1 : -1);

      path.push( startingPoint.plus( new Vector2( x, 0 ) ) );
      this.goRightNextTime = !this.goRightNextTime;

      return path;
    },

    /**
     * @param  {Vector2} center
     * @returns {Vector2[]}
     * @private
     */
    createElectricalEnergyChunkPath: function( center ) {
      var path = [];

      path.push( center.plus( OFFSET_TO_LEFT_SIDE_OF_WIRE_BEND ) );
      path.push( center.plus( OFFSET_TO_FIRST_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_SECOND_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_THIRD_WIRE_CURVE_POINT ) );
      path.push( center.plus( OFFSET_TO_BOTTOM_OF_CONNECTOR ) );
      path.push( center.plus( OFFSET_TO_RADIATE_POINT ) );

      return path;
    },

    /**
     * @returns {number} time
     * @private
     */
    generateThermalChunkTimeOnFilament: function() {
      return THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT.min +
             phet.joist.random.nextDouble() * THERMAL_ENERGY_CHUNK_TIME_ON_FILAMENT.getLength();
    },

    /**
     * @param {Vector2} startingLocation
     * @param {Vector2[]} pathPoints
     * @returns {number}
     * @private
     */
    getTotalPathLength: function( startingLocation, pathPoints ) {
      if ( pathPoints.length === 0 ) {
        return 0;
      }

      var pathLength = startingLocation.distance( pathPoints[ 0 ] );
      for ( var i = 0; i < pathPoints.length - 1; i++ ) {
        pathLength += pathPoints[ i ].distance( pathPoints[ i + 1 ] );
      }

      return pathLength;
    },

    /**
     * deactivate the light bulb
     * @public
     * @override
     */
    deactivate: function() {
      EnergyUser.prototype.deactivate.call( this );
      this.litProportionProperty.set( 0 );
    },

    /**
     * @public
     * @override
     */
    clearEnergyChunks: function() {
      EnergyUser.prototype.clearEnergyChunks.call( this );
      this.electricalEnergyChunkMovers.length = 0;
      this.filamentEnergyChunkMovers.length = 0;
      this.radiatedEnergyChunkMovers.length = 0;
      this.incomingEnergyChunks.length = 0;
    },

    /**
     * restore the initial state
     * @public
     */
    reset: function() {
      this.litProportionProperty.reset();
    }
  } );
} );

<|endoftext|>


<commit_before><commit_msg>waiting before sending NAK at beginning (when pump may be slow to respond correctly?)<commit_after><|endoftext|>