bool RgbdSensor_nws_yarp::threadInit()
{
    // Get interface from attached device if any.
    return true;
}

/*
 */

#include "RgbdSensor_nws_yarp.h"

#include <yarp/os/LogComponent.h>
#include <yarp/os/LogStream.h>

#include <yarp/proto/framegrabber/CameraVocabs.h>

#include <cstdio>
#include <cstring>
#include <sstream>

using namespace RGBDImpl;
using namespace yarp::sig;
using namespace yarp::dev;
using namespace yarp::os;

YARP_LOG_COMPONENT(RGBDSENSORNWSYARP, "yarp.devices.RgbdSensor_nws_yarp")

#define RGBD_INTERFACE_PROTOCOL_VERSION_MAJOR 1
#define RGBD_INTERFACE_PROTOCOL_VERSION_MINOR 0

RGBDSensorParser::RGBDSensorParser() :
        iRGBDSensor(nullptr)
{
}

bool RGBDSensorParser::configure(IRGBDSensor *interface)
{
    bool ret;
    iRGBDSensor = interface;
    ret  = rgbParser.configure(interface);
    ret &= depthParser.configure(interface);
    return ret;
}

bool RGBDSensorParser::configure(IRgbVisualParams *rgbInterface, IDepthVisualParams* depthInterface)
{
    bool ret = rgbParser.configure(rgbInterface);
    ret &= depthParser.configure(depthInterface);
    return ret;
}

bool RGBDSensorParser::configure(IFrameGrabberControls *_fgCtrl)
{
    return fgCtrlParsers.configure(_fgCtrl);
}

bool RGBDSensorParser::respond(const Bottle& cmd, Bottle& response)
{
    bool ret = false;
    int interfaceType = cmd.get(0).asVocab32();

    response.clear();
    switch(interfaceType)
    {
        case VOCAB_RGB_VISUAL_PARAMS:
        {
            // forwarding to the proper parser.
            ret = rgbParser.respond(cmd, response);
        }
        break;

        case VOCAB_DEPTH_VISUAL_PARAMS:
        {
            // forwarding to the proper parser.
            ret = depthParser.respond(cmd, response);
        }
        break;

        case VOCAB_FRAMEGRABBER_CONTROL:
        {
            // forwarding to the proper parser.
            ret = fgCtrlParsers.respond(cmd, response);
        }
        break;

        case VOCAB_RGBD_SENSOR:
        {
            switch (cmd.get(1).asVocab32())
            {
                case VOCAB_GET:
                {
                    switch(cmd.get(2).asVocab32())
                    {
                        case VOCAB_EXTRINSIC_PARAM:
                        {
                            yarp::sig::Matrix params;
                            ret = iRGBDSensor->getExtrinsicParam(params);
                            if(ret)
                            {
                                yarp::os::Bottle params_b;
                                response.addVocab32(VOCAB_RGBD_SENSOR);
                                response.addVocab32(VOCAB_EXTRINSIC_PARAM);
                                response.addVocab32(VOCAB_IS);
                                ret &= Property::copyPortable(params, params_b);  // will it really work??
                                response.append(params_b);
                            } else {
                                response.addVocab32(VOCAB_FAILED);
                            }
                        }
                        break;

                        case VOCAB_ERROR_MSG:
                        {
                            response.addVocab32(VOCAB_RGBD_SENSOR);
                            response.addVocab32(VOCAB_ERROR_MSG);
                            response.addVocab32(VOCAB_IS);
                            response.addString(iRGBDSensor->getLastErrorMsg());
                            ret = true;
                        }
                        break;

                        case VOCAB_RGBD_PROTOCOL_VERSION:
                        {
                            response.addVocab32(VOCAB_RGBD_SENSOR);
                            response.addVocab32(VOCAB_RGBD_PROTOCOL_VERSION);
                            response.addVocab32(VOCAB_IS);
                            response.addInt32(RGBD_INTERFACE_PROTOCOL_VERSION_MAJOR);
                            response.addInt32(RGBD_INTERFACE_PROTOCOL_VERSION_MINOR);
                        }
                        break;

                        case VOCAB_STATUS:
                        {
                            response.addVocab32(VOCAB_RGBD_SENSOR);
                            response.addVocab32(VOCAB_STATUS);
                            response.addVocab32(VOCAB_IS);
                            response.addInt32(iRGBDSensor->getSensorStatus());
                        }
                        break;

                        default:
                        {
                            yCError(RGBDSENSORNWSYARP) << "Interface parser received an unknown GET command. Command is " << cmd.toString();
                            response.addVocab32(VOCAB_FAILED);
                        }
                        break;
                    }
                }
                break;

                case VOCAB_SET:
                {
                    yCError(RGBDSENSORNWSYARP) << "Interface parser received an unknown SET command. Command is " << cmd.toString();
                    response.addVocab32(VOCAB_FAILED);
                }
                break;
            }
        }
        break;

        default:
        {
            yCError(RGBDSENSORNWSYARP) << "Received a command for a wrong interface " << yarp::os::Vocab32::decode(interfaceType);
            return DeviceResponder::respond(cmd,response);
        }
        break;
    }
    return ret;
}

RgbdSensor_nws_yarp::RgbdSensor_nws_yarp() :
    PeriodicThread(DEFAULT_THREAD_PERIOD),
    period(DEFAULT_THREAD_PERIOD),
    sensor_p(nullptr),
    fgCtrl(nullptr),
    sensorStatus(IRGBDSensor::RGBD_SENSOR_NOT_READY),
    verbose(4),
    isSubdeviceOwned(false),
    subDeviceOwned(nullptr)
{}

RgbdSensor_nws_yarp::~RgbdSensor_nws_yarp()
{
    close();
    sensor_p = nullptr;
    fgCtrl = nullptr;
}

/** Device driver interface */

bool RgbdSensor_nws_yarp::open(yarp::os::Searchable &config)
{
    m_conf.fromString(config.toString());
    if (verbose >= 5) {
        yCTrace(RGBDSENSORNWSYARP) << "\nParameters are: \n"
                                   << config.toString();
    }

    if(!fromConfig(config))
    {
        yCError(RGBDSENSORNWSYARP) << "Failed to open, check previous log for error messages.";
        return false;
    }

    if(!initialize_YARP(config))
    {
        yCError(RGBDSENSORNWSYARP) << sensorId << "Error initializing YARP ports";
        return false;
    }

    // check if we need to create subdevice or if they are
    // passed later on through attachAll()
    if(isSubdeviceOwned)
    {
        if(! openAndAttachSubDevice(config))
        {
            yCError(RGBDSENSORNWSYARP, "Error while opening subdevice");
            return false;
        }
    }
    else
    {
        if (!openDeferredAttach(config)) {
            return false;
        }
    }

    return true;
}

bool RgbdSensor_nws_yarp::fromConfig(yarp::os::Searchable &config)
{
    if (!config.check("period", "refresh period of the broadcasted values in s"))
    {
        if (verbose >= 3) {
            yCInfo(RGBDSENSORNWSYARP) << "Using default 'period' parameter of " << DEFAULT_THREAD_PERIOD << "s";
        }
    } else {
        period = config.find("period").asFloat64();
    }

    std::string rootName;
    rootName = config.check("name",Value("/"), "starting '/' if needed.").asString();

    if (!config.check("name", "Prefix name of the ports opened by the RGBD wrapper.")) {
        yCError(RGBDSENSORNWSYARP) << "Missing 'name' parameter. Check you configuration file; it must be like:";
        yCError(RGBDSENSORNWSYARP) << "   name:         Prefix name of the ports opened by the RGBD wrapper, e.g. /robotName/RGBD";
        return false;
    }

    rootName = config.find("name").asString();
    rpcPort_Name  = rootName + "/rpc:i";
    colorFrame_StreamingPort_Name = rootName + "/rgbImage:o";
    depthFrame_StreamingPort_Name = rootName + "/depthImage:o";

    if(config.check("subdevice")) {
        isSubdeviceOwned=true;
    } else {
        isSubdeviceOwned=false;
    }

    return true;
}

bool RgbdSensor_nws_yarp::openDeferredAttach(Searchable& prop)
{
    // I dunno what to do here now...
    isSubdeviceOwned = false;
    return true;
}

bool RgbdSensor_nws_yarp::openAndAttachSubDevice(Searchable& prop)
{
    Property p;
    subDeviceOwned = new PolyDriver;
    p.fromString(prop.toString());

    p.setMonitor(prop.getMonitor(), "subdevice"); // pass on any monitoring
    p.unput("device");
    p.put("device",prop.find("subdevice").asString());  // subdevice was already checked before

    // if errors occurred during open, quit here.
    yCDebug(RGBDSENSORNWSYARP, "Opening IRGBDSensor subdevice");
    subDeviceOwned->open(p);

    if (!subDeviceOwned->isValid())
    {
        yCError(RGBDSENSORNWSYARP, "Opening IRGBDSensor subdevice... FAILED");
        return false;
    }
    isSubdeviceOwned = true;
    if(!attach(subDeviceOwned)) {
        return false;
    }

    return true;
}

bool RgbdSensor_nws_yarp::close()
{
    yCTrace(RGBDSENSORNWSYARP, "Close");
    detach();

    // close subdevice if it was created inside the open (--subdevice option)
    if(isSubdeviceOwned)
    {
        if(subDeviceOwned)
        {
            delete subDeviceOwned;
            subDeviceOwned=nullptr;
        }
        sensor_p = nullptr;
        fgCtrl = nullptr;
        isSubdeviceOwned = false;
    }

    // Closing port

    rpcPort.interrupt();
    colorFrame_StreamingPort.interrupt();
    depthFrame_StreamingPort.interrupt();

    rpcPort.close();
    colorFrame_StreamingPort.close();
    depthFrame_StreamingPort.close();

    return true;
}

/* Helper functions */

bool RgbdSensor_nws_yarp::initialize_YARP(yarp::os::Searchable &params)
{
    // Open ports
    bool bRet;
    bRet = true;
    if(!rpcPort.open(rpcPort_Name))
    {
        yCError(RGBDSENSORNWSYARP) << "Unable to open rpc Port" << rpcPort_Name.c_str();
        bRet = false;
    }
    rpcPort.setReader(rgbdParser);

    if(!colorFrame_StreamingPort.open(colorFrame_StreamingPort_Name))
    {
        yCError(RGBDSENSORNWSYARP) << "Unable to open color streaming Port" << colorFrame_StreamingPort_Name.c_str();
        bRet = false;
    }
    if(!depthFrame_StreamingPort.open(depthFrame_StreamingPort_Name))
    {
        yCError(RGBDSENSORNWSYARP) << "Unable to open depth streaming Port" << depthFrame_StreamingPort_Name.c_str();
        bRet = false;
    }

    return bRet;
}

/**
  * WrapperSingle interface
  */

bool RgbdSensor_nws_yarp::detach()
{
    if (yarp::os::PeriodicThread::isRunning()) {
        yarp::os::PeriodicThread::stop();
    }

    //check if we already instantiated a subdevice previously
    if (isSubdeviceOwned) {
        return false;
    }

    sensor_p = nullptr;
    return true;
}

bool RgbdSensor_nws_yarp::attach(PolyDriver* poly)
{
    if(poly)
    {
        poly->view(sensor_p);
        poly->view(fgCtrl);
    }

    if(sensor_p == nullptr)
    {
        yCError(RGBDSENSORNWSYARP) << "Attached device has no valid IRGBDSensor interface.";
        return false;
    }

    if(!rgbdParser.configure(sensor_p))
    {
        yCError(RGBDSENSORNWSYARP) << "Error configuring IRGBD interface";
        return false;
    }

    if(fgCtrl != nullptr)
    {
        if (!rgbdParser.configure(fgCtrl)) {
            yCError(RGBDSENSORNWSYARP) << "Error configuring interfaces for parsers";
            return false;
        }
    }
    else
    {
        yCWarning(RGBDSENSORNWSYARP) << "Attached device has no valid IFrameGrabberControls interface.";
    }

    PeriodicThread::setPeriod(period);
    return PeriodicThread::start();
}

/* IRateThread interface */

void RgbdSensor_nws_yarp::threadRelease()
{
    // Detach() calls stop() which in turns calls this functions, therefore no calls to detach here!
}

bool RgbdSensor_nws_yarp::setCamInfo(const std::string& frame_id, const UInt& seq, const SensorType& sensorType)
{
    double phyF = 0.0;
    double fx = 0.0;
    double fy = 0.0;
    double cx = 0.0;
    double cy = 0.0;
    double k1 = 0.0;
    double k2 = 0.0;
    double t1 = 0.0;
    double t2 = 0.0;
    double k3 = 0.0;
    double stamp = 0.0;

    std::string                  distModel, currentSensor;
    UInt                    i;
    Property                camData;
    std::vector<param<double> >  parVector;
    param<double>*          par;
    bool                    ok;

    currentSensor = sensorType == COLOR_SENSOR ? "Rgb" : "Depth";
    ok            = sensorType == COLOR_SENSOR ? sensor_p->getRgbIntrinsicParam(camData) : sensor_p->getDepthIntrinsicParam(camData);

    if (!ok)
    {
        yCError(RGBDSENSORNWSYARP) << "Unable to get intrinsic param from" << currentSensor << "sensor!";
        return false;
    }

    if(!camData.check("distortionModel"))
    {
        yCWarning(RGBDSENSORNWSYARP) << "Missing distortion model";
        return false;
    }

    distModel = camData.find("distortionModel").asString();
    if (distModel != "plumb_bob")
    {
        yCError(RGBDSENSORNWSYARP) << "Distortion model not supported";
        return false;
    }

    parVector.emplace_back(phyF,"physFocalLength");
    parVector.emplace_back(fx,"focalLengthX");
    parVector.emplace_back(fy,"focalLengthY");
    parVector.emplace_back(cx,"principalPointX");
    parVector.emplace_back(cy,"principalPointY");
    parVector.emplace_back(k1,"k1");
    parVector.emplace_back(k2,"k2");
    parVector.emplace_back(t1,"t1");
    parVector.emplace_back(t2,"t2");
    parVector.emplace_back(k3,"k3");
    parVector.emplace_back(stamp,"stamp");
    for(i = 0; i < parVector.size(); i++)
    {
        par = &parVector[i];

        if(!camData.check(par->parname))
        {
            yCWarning(RGBDSENSORNWSYARP) << "Driver has not the param:" << par->parname;
            return false;
        }
        *par->var = camData.find(par->parname).asFloat64();
    }

    return true;
}

bool RgbdSensor_nws_yarp::writeData()
{

    //             colorImage.resize(hDim, vDim);  // Has this to be done each time? If size is the same what it does?
    if (!sensor_p->getImages(colorImage, depthImage, &colorStamp, &depthStamp))
    {
        return false;
    }

    static Stamp oldColorStamp = Stamp(0, 0);
    static Stamp oldDepthStamp = Stamp(0, 0);
    bool rgb_data_ok = true;
    bool depth_data_ok = true;

    if (((colorStamp.getTime() - oldColorStamp.getTime()) > 0) == false)
    {
        rgb_data_ok=false;
    }
    else { oldColorStamp = colorStamp; }

    if (((depthStamp.getTime() - oldDepthStamp.getTime()) > 0) == false)
    {
        depth_data_ok=false;
    }
    else { oldDepthStamp = depthStamp; }

    // TBD: We should check here somehow if the timestamp was correctly updated and, if not, update it ourselves.
    if (rgb_data_ok)
    {
        FlexImage& yColorImage = colorFrame_StreamingPort.prepare();
        yColorImage.setPixelCode(colorImage.getPixelCode());
        yColorImage.setQuantum(colorImage.getQuantum());
        yColorImage.setExternal(colorImage.getRawImage(), colorImage.width(), colorImage.height());
        colorFrame_StreamingPort.setEnvelope(colorStamp);
        colorFrame_StreamingPort.write();
    }
    if (depth_data_ok)
    {
        ImageOf<PixelFloat>& yDepthImage = depthFrame_StreamingPort.prepare();
        yDepthImage.setQuantum(depthImage.getQuantum());
        yDepthImage.setExternal(depthImage.getRawImage(), depthImage.width(), depthImage.height());
        depthFrame_StreamingPort.setEnvelope(depthStamp);
        depthFrame_StreamingPort.write();
    }

    return true;
}

void RgbdSensor_nws_yarp::run()
{
    if (sensor_p!=nullptr)
    {
        static int i = 0;
        sensorStatus = sensor_p->getSensorStatus();
        switch (sensorStatus)
        {
            case(IRGBDSensor::RGBD_SENSOR_OK_IN_USE) :
            {
                if (!writeData()) {
                    yCError(RGBDSENSORNWSYARP, "Image not captured.. check hardware configuration");
                }
                i = 0;
            }
            break;
            case(IRGBDSensor::RGBD_SENSOR_NOT_READY):
            {
                if(i < 1000) {
                    if((i % 30) == 0) {
                        yCInfo(RGBDSENSORNWSYARP) << "Device not ready, waiting...";
                    }
                } else {
                    yCWarning(RGBDSENSORNWSYARP) << "Device is taking too long to start..";
                }
                i++;
            }
            break;
            default:
            {
                if (verbose >= 1) {  // better not to print it every cycle anyway, too noisy
                    yCError(RGBDSENSORNWSYARP, "%s: Sensor returned error", sensorId.c_str());
                }
            }
        }
    }
    else
    {
        if(verbose >= 6) {
            yCError(RGBDSENSORNWSYARP, "%s: Sensor interface is not valid", sensorId.c_str());
        }
    }
}


void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    gl::Viewport({ 0, 0 }, width, height);
    scr_width = width;
    scr_height = height;
}

#include <UGL/UGL>
#include <UGM/UGM>

#include <GLFW/glfw3.h>

#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>

#include "../../tool/Camera.h"
#include "../../tool/SimpleLoader.h"

#include <iostream>

using namespace Ubpa;

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);
gl::Texture2D loadTexture(char const* path);

// settings
unsigned int scr_width = 800;
unsigned int scr_height = 600;
bool have_shadow = false;

// camera
Camera camera(pointf3(0.0f, 0.0f, 3.0f));
float lastX = scr_width / 2.0f;
float lastY = scr_height / 2.0f;
bool firstMouse = true;

// timing
float deltaTime = 0.0f;	// time between current frame and last frame
float lastFrame = 0.0f;

int main()
{
    // glfw: initialize and configure
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
#endif

    // glfw window creation
    GLFWwindow* window = glfwCreateWindow(scr_width, scr_height, "HW8 - shadow", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // tell GLFW to capture our mouse
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // glad: load all OpenGL function pointers
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // configure global opengl state
    gl::Enable(gl::Capability::DepthTest);

    // build and compile our shader zprogram
    gl::Shader p3t2n3_vs(gl::ShaderType::VertexShader, "../data/shaders/p3t2n3.vert");
    gl::Shader p3_vs(gl::ShaderType::VertexShader, "../data/shaders/p3.vert");

    gl::Shader light_shadow_fs(gl::ShaderType::FragmentShader, "../data/shaders/light_shadow.frag");
    gl::Shader empty_fs(gl::ShaderType::FragmentShader, "../data/shaders/empty.frag");

    gl::Program light_shadow_program(&p3t2n3_vs, &light_shadow_fs);
    gl::Program shadow_program(&p3_vs, &empty_fs);

    rgbf ambient{ 0.2f,0.2f,0.2f };
    light_shadow_program.SetTex("albedo_texture", 0);
    light_shadow_program.SetTex("shadowmap", 1);
    light_shadow_program.SetVecf3("point_light_pos", { 0,5,0 });
    light_shadow_program.SetVecf3("point_light_radiance", { 100,100,100 });
    light_shadow_program.SetVecf3("ambient_irradiance", ambient);
    light_shadow_program.SetFloat("roughness", 0.5f );
    light_shadow_program.SetFloat("metalness", 0.f);

    // load model
    auto spot = SimpleLoader::LoadObj("../data/models/spot_triangulated_good.obj");
    // world space positions of our cubes
    pointf3 instancePositions[] = {
        pointf3(0.0f,  0.0f,  0.0f),
        pointf3(2.0f,  5.0f, -15.0f),
        pointf3(-1.5f, -2.2f, -2.5f),
        pointf3(-3.8f, -2.0f, -12.3f),
        pointf3(2.4f, -0.4f, -3.5f),
        pointf3(-1.7f,  3.0f, -7.5f),
        pointf3(1.3f, -2.0f, -2.5f),
        pointf3(1.5f,  2.0f, -2.5f),
        pointf3(1.5f,  0.2f, -1.5f),
        pointf3(-1.3f,  1.0f, -1.5f)
    };

    // load and create a texture
    gl::Texture2D spot_albedo = loadTexture("../data/textures/spot_albedo.png");

    // shadow buffer
    const size_t SHADOW_TEXTURE_SIZE = 1024;
    gl::Texture2D shadowmap;
    shadowmap.SetImage(0, gl::PixelDataInternalFormat::DepthComponent, SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE,
        gl::PixelDataFormat::DepthComponent, gl::PixelDataType::Float, 0);
    shadowmap.SetWrapFilter(gl::WrapMode::ClampToBorder, gl::WrapMode::ClampToBorder,
        gl::MinFilter::Nearest, gl::MagFilter::Nearest);
    rgbaf borderColor{ 1.f,1.f,1.f,1.f };
    gl::TexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor.data());
    gl::FrameBuffer shadowFB;
    shadowFB.Attach(gl::FramebufferAttachment::DepthAttachment, &shadowmap);

    // render loop
    while (!glfwWindowShouldClose(window))
    {
        // per-frame time logic
        float currentFrame = static_cast<float>(glfwGetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // input
        processInput(window);

        // render

        shadowFB.Bind();
        gl::Viewport({ 0,0 }, SHADOW_TEXTURE_SIZE, SHADOW_TEXTURE_SIZE);
        gl::Clear(gl::BufferSelectBit::DepthBufferBit);

        // TODO: HW8 - 2_Shadow | generate shadow map

        gl::FrameBuffer::BindReset(); // default framebuffer
        gl::Viewport({ 0,0 }, scr_width, scr_height);
        gl::ClearColor({ ambient, 1.0f });
        gl::Clear(gl::BufferSelectBit::ColorBufferBit | gl::BufferSelectBit::DepthBufferBit);

        light_shadow_program.SetVecf3("camera_pos", camera.Position);

        // bind textures on corresponding texture units
        light_shadow_program.Active(0, &spot_albedo);
        light_shadow_program.Active(1, &shadowmap);

        // pass projection matrix to shader (note that in this case it could change every frame)
        transformf projection = transformf::perspective(to_radian(camera.Zoom), (float)scr_width / (float)scr_height, 0.1f, 100.f);
        light_shadow_program.SetMatf4("projection", projection);

        // camera/view transformation
        light_shadow_program.SetMatf4("view", camera.GetViewMatrix());

        // TODO: HW8 - 2_Shadow | set uniforms about shadow
        light_shadow_program.SetBool("have_shadow", have_shadow);

        for (unsigned int i = 0; i < 10; i++)
        {
            // calculate the model matrix for each object and pass it to shader before drawing
            float angle = 20.0f * i + 10.f * (float)glfwGetTime();
            transformf model(instancePositions[i], quatf{ vecf3(1.0f, 0.3f, 0.5f), to_radian(angle) });
            light_shadow_program.SetMatf4("model", model);
            spot->va->Draw(&light_shadow_program);
        }

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    delete spot;

    // glfw: terminate, clearing all previously allocated GLFW resources.
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        camera.ProcessKeyboard(Camera::Movement::FORWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        camera.ProcessKeyboard(Camera::Movement::BACKWARD, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        camera.ProcessKeyboard(Camera::Movement::LEFT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        camera.ProcessKeyboard(Camera::Movement::RIGHT, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_Q) == GLFW_PRESS)
        camera.ProcessKeyboard(Camera::Movement::UP, deltaTime);
    if (glfwGetKey(window, GLFW_KEY_E) == GLFW_PRESS)
        camera.ProcessKeyboard(Camera::Movement::DOWN, deltaTime);

    if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS)
        have_shadow = !have_shadow;
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes

// glfw: whenever the mouse moves, this callback is called
void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = static_cast<float>(xpos);
        lastY = static_cast<float>(ypos);
        firstMouse = false;
    }

    float xoffset = static_cast<float>(xpos) - lastX;
    float yoffset = lastY - static_cast<float>(ypos); // reversed since y-coordinates go from bottom to top

    lastX = static_cast<float>(xpos);
    lastY = static_cast<float>(ypos);

    camera.ProcessMouseMovement(static_cast<float>(xoffset), static_cast<float>(yoffset));
}

// glfw: whenever the mouse scroll wheel scrolls, this callback is called
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    camera.ProcessMouseScroll(static_cast<float>(yoffset));
}

gl::Texture2D loadTexture(char const* path)
{
    gl::Texture2D tex;
    tex.SetWrapFilter(gl::WrapMode::Repeat, gl::WrapMode::Repeat, gl::MinFilter::Linear, gl::MagFilter::Linear);
    // load image, create texture and generate mipmaps
    int width, height, nrChannels;
    stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.
    unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0);
    gl::PixelDataFormat c2f[4] = {
        gl::PixelDataFormat::Red,
        gl::PixelDataFormat::Rg,
        gl::PixelDataFormat::Rgb,
        gl::PixelDataFormat::Rgba
    };
    gl::PixelDataInternalFormat c2if[4] = {
        gl::PixelDataInternalFormat::Red,
        gl::PixelDataInternalFormat::Rg,
        gl::PixelDataInternalFormat::Rgb,
        gl::PixelDataInternalFormat::Rgba
    };
    if (data)
    {
        tex.SetImage(0, c2if[nrChannels - 1], width, height, c2f[nrChannels - 1], gl::PixelDataType::UnsignedByte, data);
        tex.GenerateMipmap();
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);

    return tex;
}


u8 o2_4in1_device::io_read(offs_t offset)
{
	if (m_control & 2)
		return m_rom[m_bank << 8 | offset];
	else
		return 0xff;
}

// copyright-holders:hap
/******************************************************************************

Videopac 31/40 cartridge emulation

4KB ROM (program ROM is fixed, data ROM is 'bankswitched' via P11)

Used in:
- #31: Musician
- #40: 4 in 1 Row

******************************************************************************/

#include "emu.h"
#include "4in1.h"

DEFINE_DEVICE_TYPE(O2_ROM_4IN1, o2_4in1_device, "o2_4in1", "Videopac 40 Cartridge")

o2_4in1_device::o2_4in1_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) :
	device_t(mconfig, O2_ROM_4IN1, tag, owner, clock),
	device_o2_cart_interface(mconfig, *this)
{ }

void o2_4in1_device::device_start()
{
	save_item(NAME(m_control));
	save_item(NAME(m_bank));
}

void o2_4in1_device::cart_init()
{
	if (m_rom_size != 0x1000)
		fatalerror("o2_4in1_device: ROM size must be 4KB\n");
}

//  mapper specific handlers


void
bdlbb::swap(BlobBuffer& a, BlobBuffer& b)
{
    a.swap(b);
}

#include <bdlbb_blob.h>

#include <bsls_ident.h>
BSLS_IDENT_RCSID(bdlbb_blob_cpp, "$Id$ $CSID$")

#include <bdlb_print.h>

#include <bslalg_swaputil.h>

#include <bsls_assert.h>
#include <bsls_performancehint.h>
#include <bsls_review.h>

#include <bsl_algorithm.h>
#include <bsl_climits.h>    // INT_MAX
#include <bsl_iomanip.h>
#include <bsl_ostream.h>

#if defined(BSLS_PLATFORM_CMP_MSVC) && defined(min)
#undef min
#endif

namespace BloombergLP {
namespace {

typedef bsl::vector<bdlbb::BlobBuffer>::iterator       BlobBufferIterator;
typedef bsl::vector<bdlbb::BlobBuffer>::const_iterator BlobBufferConstIterator;

class InvalidBlobBufferFactory : private bdlbb::BlobBufferFactory {
    // NOT IMPLEMENTED
    InvalidBlobBufferFactory(const InvalidBlobBufferFactory&);
    InvalidBlobBufferFactory& operator=(const InvalidBlobBufferFactory&);

  private:
    // PRIVATE CREATORS
    InvalidBlobBufferFactory();
        // Default construct an 'InvalidBlobBufferFactory' object.

    ~InvalidBlobBufferFactory();

  public:
    // CLASS METHODS
    static bdlbb::BlobBufferFactory *factory(
                                   bdlbb::BlobBufferFactory *basicFactory = 0);
        // Return a pointer to a 'BlobBufferFactory' object.  Optionally
        // specify 'basicFactory' that should be returned if it is non-null.
        // If 'basicFactory' is not specified or is null return the singleton
        // object managed by this 'class'.

    static InvalidBlobBufferFactory& singleton();
        // Return a reference providing modifiable access to the
        // 'InvalidBlobBufferFactory' singleton object managed by this 'class'.

    // MANIPULATORS
    virtual void allocate(bdlbb::BlobBuffer *);
        // Allocate a blob buffer from this blob buffer factory, and load it
        // into the specified 'buffer'.
};

// PRIVATE CREATORS
inline
InvalidBlobBufferFactory::InvalidBlobBufferFactory()
{
}

inline
InvalidBlobBufferFactory::~InvalidBlobBufferFactory()
{
}

// CLASS METHODS
inline
bdlbb::BlobBufferFactory *InvalidBlobBufferFactory::factory(
                                        bdlbb::BlobBufferFactory *basicFactory)
{
    return basicFactory ? basicFactory : &singleton();
}

InvalidBlobBufferFactory& InvalidBlobBufferFactory::singleton()
{
    static InvalidBlobBufferFactory instance;
    return instance;
}

// MANIPULATORS
void InvalidBlobBufferFactory::allocate(bdlbb::BlobBuffer *)
{
    BSLS_ASSERT_OPT(!"Invalid Blob Buffer Factory Used!");
}

}  // close unnamed namespace

namespace bdlbb {

// MANIPULATORS
BlobBuffer& BlobBuffer::operator=(const BlobBuffer& rhs)
{
    d_buffer = rhs.d_buffer;
    d_size   = rhs.d_size;
    return *this;
}

BlobBuffer& BlobBuffer::operator=(bslmf::MovableRef<BlobBuffer> rhs)
{
    BlobBuffer& lvalue = rhs;
    d_buffer = MoveUtil::move(lvalue.d_buffer);
    d_size   = MoveUtil::move(lvalue.d_size);

    lvalue.d_size = 0;

    return *this;
}

void BlobBuffer::reset(const bsl::shared_ptr<char>& buffer, int size)
{
    BSLS_ASSERT(0 <= size);

    d_buffer = buffer;
    d_size   = size;
}

void BlobBuffer::reset()
{
    d_buffer.reset();
    d_size = 0;
}

void BlobBuffer::swap(BlobBuffer& other)
{
    bslalg::SwapUtil::swap(&this->d_buffer, &other.d_buffer);
    bslalg::SwapUtil::swap(&this->d_size, &other.d_size);
}

// ACCESSORS
bsl::ostream& BlobBuffer::print(bsl::ostream& stream, int, int) const
{
    bdlb::Print::hexDump(stream, d_buffer.get(), d_size);
    return stream << bsl::flush;
}
}  // close package namespace

// FREE OPERATORS
bsl::ostream& bdlbb::operator<<(bsl::ostream& stream, const BlobBuffer& buffer)
{
    return buffer.print(stream, 0, -1);
}

// FREE FUNCTIONS

namespace bdlbb {

// CREATORS
BlobBufferFactory::~BlobBufferFactory()
{
}

// PRIVATE ACCESSORS
int Blob::assertInvariants() const
{
    BSLS_ASSERT(0 != d_bufferFactory_p);
    BSLS_ASSERT(0 <= d_totalSize);
    BSLS_ASSERT(0 <= d_dataLength);
    BSLS_ASSERT(d_dataLength <= d_totalSize);
    BSLS_ASSERT(-1 <= d_dataIndex);
    BSLS_ASSERT(0 <= d_preDataIndexLength);
    BSLS_ASSERT(d_preDataIndexLength <= d_dataLength);

    int       preDataLength = 0;
    int       index         = 0;
    const int NUM_BUFFERS   = numBuffers();

    for (; index < d_dataIndex; ++index) {
        preDataLength += buffer(index).size();
    }

    BSLS_ASSERT(preDataLength == d_preDataIndexLength);

    int totalSize = preDataLength;
    for (int i = index; i < NUM_BUFFERS; ++i) {
        totalSize += buffer(i).size();
    }

    BSLS_ASSERT(totalSize == d_totalSize);

    if (-1 == d_dataIndex) {
       BSLS_ASSERT(0 == d_dataLength);
       BSLS_ASSERT(0 == d_preDataIndexLength);
    }

    if (0 < d_dataLength) {
        BSLS_ASSERT(NUM_BUFFERS != index                              );
        BSLS_ASSERT(d_dataIndex <  static_cast<int>(d_buffers.size()) );
        BSLS_ASSERT(0           <= d_dataLength - d_preDataIndexLength);
    }
    else {
        BSLS_ASSERT(0 == d_preDataIndexLength);
    }

    return 0;
}

// PRIVATE MANIPULATORS
void Blob::slowSetLength(int length)
{
    BSLS_ASSERT(0 <= length);

    if (0 == length) {
        d_dataIndex          = -1;
        d_dataLength         =  0;
        d_preDataIndexLength =  0;
        return;                                                       // RETURN
    }

    while (length > d_totalSize) {
        BlobBuffer buf;
        d_bufferFactory_p->allocate(&buf);
        appendBuffer(buf);
    }

    if (length > d_dataLength) {
        // If there is a last data buffer, add its remaining capacity.

        int currentBufferSize = 0;
        if (-1 != d_dataIndex) {
            currentBufferSize = d_buffers[d_dataIndex].size();
            d_dataLength = d_preDataIndexLength + currentBufferSize;
            BSLS_ASSERT(d_dataLength < length);
        }
        int left = length - d_dataLength;

        // Add space from additional capacity buffers until the blob is the
        // requested length.

        do {
            d_preDataIndexLength += currentBufferSize;
            ++d_dataIndex;
            currentBufferSize = d_buffers[d_dataIndex].size();
            d_dataLength += bsl::min(left, currentBufferSize);
            left -= currentBufferSize;
        } while (left > 0);
        return;                                                       // RETURN
    }

    // We are decreasing the length.

    BSLS_ASSERT(d_preDataIndexLength >= length);

    // Empty the last data buffer.

    int left     = d_preDataIndexLength - length;
    d_dataLength = d_preDataIndexLength;
    --d_dataIndex;

    // Empty the necessary amount of data buffers to get the required length.

    while (d_dataIndex >= 0 &&
           left > 0 &&
           left >= d_buffers[d_dataIndex].size()) {
        d_preDataIndexLength -= d_buffers[d_dataIndex].size();
        const int currentBufferSize = d_buffers[d_dataIndex].size();
        d_dataLength -= currentBufferSize;
        left -= currentBufferSize;
        --d_dataIndex;
    }

    // Remove the reminder.

    if (left > 0) {
        d_dataLength -= left;
    }

    if (d_dataIndex >= 0) {
        d_preDataIndexLength -= d_buffers[d_dataIndex].size();
    }
}

// CREATORS
Blob::Blob(bslma::Allocator *basicAllocator)
: d_buffers(basicAllocator)
, d_totalSize(0)
, d_dataLength(0)
, d_dataIndex(-1)
, d_preDataIndexLength(0)
, d_bufferFactory_p(InvalidBlobBufferFactory::factory(0))
{
    BSLS_ASSERT_SAFE(0 == assertInvariants());
}

Blob::Blob(BlobBufferFactory *factory, bslma::Allocator *basicAllocator)
: d_buffers(basicAllocator)
, d_totalSize(0)
, d_dataLength(0)
, d_dataIndex(-1)
, d_preDataIndexLength(0)
, d_bufferFactory_p(InvalidBlobBufferFactory::factory(factory))
{
    BSLS_ASSERT_SAFE(0 == assertInvariants());
}

Blob::Blob(const BlobBuffer  *buffers,
           int                numBuffers,
           BlobBufferFactory *factory,
           bslma::Allocator  *basicAllocator)
: d_buffers(buffers, buffers + numBuffers, basicAllocator)
, d_totalSize(0)
, d_dataLength(0)
, d_dataIndex(-1)
, d_preDataIndexLength(0)
, d_bufferFactory_p(InvalidBlobBufferFactory::factory(factory))
{
    for (BlobBufferConstIterator it = d_buffers.begin(); it != d_buffers.end();
         ++it) {
        BSLS_ASSERT(0 <= it->size());
        d_totalSize += it->size();
    }
    BSLS_ASSERT_SAFE(0 == assertInvariants());
}

Blob::Blob(const Blob&        original,
           BlobBufferFactory *factory,
           bslma::Allocator  *basicAllocator)
: d_buffers(original.d_buffers, basicAllocator)
, d_totalSize(original.d_totalSize)
, d_dataLength(original.d_dataLength)
, d_dataIndex(original.d_dataIndex)
, d_preDataIndexLength(original.d_preDataIndexLength)
, d_bufferFactory_p(InvalidBlobBufferFactory::factory(factory))
{
    BSLS_ASSERT_SAFE(0 == assertInvariants());
}

Blob::Blob(const Blob& original, bslma::Allocator *basicAllocator)
: d_buffers(original.d_buffers, basicAllocator)
, d_totalSize(original.d_totalSize)
, d_dataLength(original.d_dataLength)
, d_dataIndex(original.d_dataIndex)
, d_preDataIndexLength(original.d_preDataIndexLength)
, d_bufferFactory_p(InvalidBlobBufferFactory::factory(0))
{
    BSLS_ASSERT_SAFE(0 == assertInvariants());
}

Blob::Blob(bslmf::MovableRef<Blob> original) BSLS_KEYWORD_NOEXCEPT
: d_buffers(MoveUtil::move(MoveUtil::access(original).d_buffers))
, d_totalSize(MoveUtil::move(MoveUtil::access(original).d_totalSize))
, d_dataLength(MoveUtil::move(MoveUtil::access(original).d_dataLength))
, d_dataIndex(MoveUtil::move(MoveUtil::access(original).d_dataIndex))
, d_preDataIndexLength(
            MoveUtil::move(MoveUtil::access(original).d_preDataIndexLength))
, d_bufferFactory_p(
            MoveUtil::move(MoveUtil::access(original).d_bufferFactory_p))
{
    Blob& lvalue = original;
    lvalue.removeAll();
}

Blob::Blob(bslmf::MovableRef<Blob> original, bslma::Allocator *basicAllocator)
: d_buffers(MoveUtil::move(MoveUtil::access(original).d_buffers),
            basicAllocator)
, d_totalSize(MoveUtil::move(MoveUtil::access(original).d_totalSize))
, d_dataLength(MoveUtil::move(MoveUtil::access(original).d_dataLength))
, d_dataIndex(MoveUtil::move(MoveUtil::access(original).d_dataIndex))
, d_preDataIndexLength(
            MoveUtil::move(MoveUtil::access(original).d_preDataIndexLength))
, d_bufferFactory_p(
            MoveUtil::move(MoveUtil::access(original).d_bufferFactory_p))
{
    Blob& lvalue = original;
    lvalue.removeAll();
}

Blob::~Blob()
{
    BSLS_ASSERT_SAFE(0 == assertInvariants());
}

// MANIPULATORS
Blob& Blob::operator=(const Blob& rhs)
{
    d_buffers.reserve(rhs.numBuffers());

    d_buffers            = rhs.d_buffers;
    d_totalSize          = rhs.d_totalSize;
    d_dataLength         = rhs.d_dataLength;
    d_dataIndex          = rhs.d_dataIndex;
    d_preDataIndexLength = rhs.d_preDataIndexLength;

    return *this;
}

Blob& Blob::operator=(bslmf::MovableRef<Blob> rhs)
{
    Blob& lvalue = rhs;

    d_buffers            = MoveUtil::move(lvalue.d_buffers);
    d_totalSize          = MoveUtil::move(lvalue.d_totalSize);
    d_dataLength         = MoveUtil::move(lvalue.d_dataLength);
    d_dataIndex          = MoveUtil::move(lvalue.d_dataIndex);
    d_preDataIndexLength = MoveUtil::move(lvalue.d_preDataIndexLength);
    d_bufferFactory_p    = MoveUtil::move(lvalue.d_bufferFactory_p);

    lvalue.removeAll();

    return *this;
}

void Blob::appendBuffer(const BlobBuffer& buffer)
{
    BSLS_ASSERT(d_totalSize <= INT_MAX - buffer.size());
    BSLS_ASSERT(numBuffers() < INT_MAX);

    d_buffers.push_back(buffer);
    d_totalSize += buffer.size();
}

void Blob::appendDataBuffer(const BlobBuffer& buffer)
{

    BSLS_ASSERT(numBuffers() < INT_MAX);

    const int bufferSize    = buffer.size();
    const int oldDataLength = d_dataLength;

    if (d_totalSize == d_dataLength || 0 == d_dataLength) {
        // Fast path.  At the start, we had some data buffers in the blob and
        // all non-zero sized buffers were full or there was no data, but
        // empty buffers could be present.

        BSLS_ASSERT(d_totalSize <= INT_MAX - bufferSize);

        d_buffers.insert(d_buffers.begin() + numDataBuffers(), buffer);

        d_preDataIndexLength = oldDataLength;
        ++d_dataIndex;
        d_totalSize += bufferSize;
        d_dataLength += bufferSize;
    }
    else {
        // Complicated case -- at the start, buffer(s) with data were present,
        // trimming 'prevBuf' might or might not be necessary, empty space was
        // present on the end, whole empty buffer(s) might or might not have
        // been present on the end.
        // The last data buffer can be trimmed during appending new buffer.
        // Therefore, the potentially allowed size of the added buffer should
        // be adjusted accordingly.

        const int TRIMMED_SIZE =
            d_buffers[d_dataIndex].size() - lastDataBufferLength();

        BSLS_ASSERT(d_totalSize - TRIMMED_SIZE <= INT_MAX - bufferSize);
        BSLS_ASSERT(d_dataLength > 0);
        BSLS_ASSERT(d_dataLength < d_totalSize);
        BSLS_ASSERT((unsigned)d_dataIndex < d_buffers.size());
        BSLS_ASSERT(oldDataLength >= d_preDataIndexLength);

        BlobBuffer&    prevBuf        = d_buffers[d_dataIndex];
        const unsigned newPrevBufSize = oldDataLength - d_preDataIndexLength;

        BSLS_ASSERT(TRIMMED_SIZE <= static_cast<int>(prevBuf.size()));

        prevBuf.setSize(newPrevBufSize);
        d_totalSize -= TRIMMED_SIZE;

        d_buffers.insert(d_buffers.begin() + d_dataIndex + 1, buffer);
        ++d_dataIndex;
        d_preDataIndexLength = oldDataLength;
        d_totalSize += bufferSize;
        d_dataLength += bufferSize;
    }
}

void Blob::insertBuffer(int index, const BlobBuffer& buffer)
{
    BSLS_ASSERT(0 <= index);
    BSLS_ASSERT(index <= static_cast<int>(d_buffers.size()));

    const int bufferSize = buffer.size();
    BSLS_ASSERT(d_totalSize <= INT_MAX - bufferSize);
    BSLS_ASSERT(numBuffers() < INT_MAX);

    d_buffers.insert(d_buffers.begin() + index, buffer);
    d_totalSize += bufferSize;
    if (0 != d_dataLength && index <= d_dataIndex) {
        // Newly-inserted buffer is a data buffer.

        d_dataLength += bufferSize;
        d_preDataIndexLength += bufferSize;
        ++d_dataIndex;
    }
}

void Blob::prependDataBuffer(const BlobBuffer& buffer)
{
    const int bufferSize = buffer.size();

    BSLS_ASSERT(d_totalSize <= INT_MAX - bufferSize);
    BSLS_ASSERT(numBuffers() < INT_MAX);

    d_buffers.insert(d_buffers.begin(), buffer);

    ++d_dataIndex;
    if (0 != d_dataIndex) {
        d_preDataIndexLength += bufferSize;
    }
    d_totalSize += bufferSize;
    d_dataLength += bufferSize;
}

void Blob::removeAll()
{
    d_buffers.clear();
    d_totalSize          =  0;
    d_dataLength         =  0;
    d_dataIndex          = -1;
    d_preDataIndexLength =  0;
}

void Blob::removeBuffer(int index)
{
    BSLS_ASSERT(0 <= index);
    BSLS_ASSERT(index < static_cast<int>(d_buffers.size()));

    d_totalSize -= d_buffers[index].size();
    if (d_dataIndex == index) {
        d_dataLength = d_preDataIndexLength;

        if (d_preDataIndexLength != 0) {
            d_preDataIndexLength -= d_buffers[index - 1].size();
        }

        --d_dataIndex;
    }
    else if (d_dataIndex > index) {
        d_preDataIndexLength -= d_buffers[index].size();
        d_dataLength -= d_buffers[index].size();
        --d_dataIndex;
    }
    d_buffers.erase(d_buffers.begin() + index);
}

void Blob::removeBuffers(int index, int numBuffers)
{
    BSLS_ASSERT(0 <= index);
    BSLS_ASSERT(0 <= numBuffers);
    BSLS_ASSERT(index + numBuffers <= this->numBuffers());

    // As we iterate through the buffers the values of the class data members
    // will change constantly making it difficult to iterate correctly.  Make a
    // local copy of the variables so we can iterate through the blob and
    // update the data members at the end.

    int dataIndex          = d_dataIndex;
    int dataLength         = d_dataLength;
    int totalSize          = d_totalSize;
    int preDataIndexLength = d_preDataIndexLength;

    for (int i = 0; i < numBuffers; ++i) {
        const int currIdx = index + i;
        const int bufSize = d_buffers[currIdx].size();

        totalSize -= bufSize;
        if (currIdx < d_dataIndex) {
            preDataIndexLength -= bufSize;
            dataLength -= bufSize;
            --dataIndex;
        }
        else if (currIdx == d_dataIndex) {
            dataLength = preDataIndexLength;

            if (preDataIndexLength != 0) {
                BSLS_ASSERT(0 != dataIndex);

                preDataIndexLength -= d_buffers[dataIndex - 1].size();
            }

            --dataIndex;
        }
    }

    d_buffers.erase(d_buffers.begin() + index,
                    d_buffers.begin() + index + numBuffers);

    d_dataIndex          = dataIndex;
    d_dataLength         = dataLength;
    d_totalSize          = totalSize;
    d_preDataIndexLength = preDataIndexLength;
}

void Blob::removeUnusedBuffers()
{
    if (numDataBuffers() < numBuffers()) {
        d_totalSize = d_dataLength > 0 ? d_preDataIndexLength +
                                             d_buffers[d_dataIndex].size()
                                       : 0;

        d_buffers.erase(d_buffers.begin() + numDataBuffers(), d_buffers.end());
    }
}

void Blob::replaceDataBuffer(int index, const BlobBuffer& buffer)
{
    BSLS_ASSERT(0 <= index);
    BSLS_ASSERT(index < numDataBuffers());
    BSLS_ASSERT(d_totalSize <= INT_MAX - buffer.size());

    int sizeDiff = buffer.size() - d_buffers[index].size();

    d_totalSize += sizeDiff;
    if (d_dataIndex == index) {
        d_dataLength = d_preDataIndexLength + buffer.size();
    }
    else if (d_dataIndex > index) {
        d_preDataIndexLength += sizeDiff;
        d_dataLength += sizeDiff;
    }

    d_buffers[index] = buffer;
}

void Blob::setLength(int length)
{
    BSLS_ASSERT(0 <= length);

    if (d_dataLength == length) {
        return;                                                       // RETURN
    }

    if (d_totalSize &&
        -1 != d_dataIndex &&
        d_preDataIndexLength + d_buffers[d_dataIndex].size() >= length &&
        d_preDataIndexLength < length) {
        // We are not crossing any buffer boundaries.

        d_dataLength = length;
        return;                                                       // RETURN
    }

    return slowSetLength(length);
}

void Blob::swap(Blob& other)
{
    BSLS_ASSERT(this->allocator() == other.allocator());

    bslalg::SwapUtil::swap(&this->d_buffers, &other.d_buffers);
    bslalg::SwapUtil::swap(&this->d_totalSize, &other.d_totalSize);
    bslalg::SwapUtil::swap(&this->d_dataLength, &other.d_dataLength);
    bslalg::SwapUtil::swap(&this->d_dataIndex, &other.d_dataIndex);
    bslalg::SwapUtil::swap(&this->d_preDataIndexLength,
                           &other.d_preDataIndexLength);
    bslalg::SwapUtil::swap(&this->d_bufferFactory_p, &other.d_bufferFactory_p);
}

void Blob::swapBufferRaw(int index, BlobBuffer *srcBuffer)
{
    BSLS_ASSERT(0 <= index);
    BSLS_ASSERT(index < numBuffers());
    BSLS_ASSERT(srcBuffer->size() == buffer(index).size());

    d_buffers[index].buffer().swap(srcBuffer->buffer());
}

void Blob::trimLastDataBuffer()
{
    if (0 != d_dataLength &&
        d_dataLength - d_preDataIndexLength < d_buffers[d_dataIndex].size()) {
        d_totalSize -= d_buffers[d_dataIndex].size();
        d_buffers[d_dataIndex].setSize(d_dataLength - d_preDataIndexLength);
        d_totalSize += d_dataLength - d_preDataIndexLength;
    }
}

void Blob::moveBuffers(Blob *srcBlob)
{
    d_buffers.resize(srcBlob->d_buffers.size());

    BlobBufferIterator dstIter = d_buffers.begin();
    BlobBufferIterator srcIter = srcBlob->d_buffers.begin();
    for (; srcIter != srcBlob->d_buffers.end(); ++srcIter, ++dstIter) {
        dstIter->buffer().swap(srcIter->buffer());
        dstIter->setSize(srcIter->size());
    }
    d_totalSize          = srcBlob->d_totalSize;
    d_dataLength         = srcBlob->d_dataLength;
    d_dataIndex          = srcBlob->d_dataIndex;
    d_preDataIndexLength = srcBlob->d_preDataIndexLength;
    srcBlob->removeAll();
}

void Blob::moveDataBuffers(Blob *srcBlob)
{
    if (0 == srcBlob->length()) {
        d_dataIndex          = -1;
        d_dataLength         =  0;
        d_preDataIndexLength =  0;
        return;                                                       // RETURN
    }

    const int numSrcDataBuffers = srcBlob->numDataBuffers();

    d_buffers.resize(numSrcDataBuffers);

    BlobBufferIterator dstIter = d_buffers.begin();
    BlobBufferIterator srcIter = srcBlob->d_buffers.begin();

    for (int i = 0; i < numSrcDataBuffers; ++i, ++srcIter, ++dstIter) {
        dstIter->buffer().swap(srcIter->buffer());
        dstIter->setSize(srcIter->size());
    }

    d_dataIndex          = srcBlob->d_dataIndex;
    d_dataLength         = srcBlob->d_dataLength;
    d_preDataIndexLength = srcBlob->d_preDataIndexLength;
    d_totalSize = d_preDataIndexLength + d_buffers[d_dataIndex].size();

    srcBlob->d_buffers.erase(srcBlob->d_buffers.begin(),
                             srcBlob->d_buffers.begin() + numSrcDataBuffers);

    srcBlob->d_dataIndex          = -1;
    srcBlob->d_dataLength         =  0;
    srcBlob->d_preDataIndexLength =  0;
    srcBlob->d_totalSize -= d_totalSize;
}

void Blob::moveAndAppendDataBuffers(Blob *srcBlob)
{
    if (0 == srcBlob->length()) {
        return;                                                       // RETURN
    }

    trimLastDataBuffer();  // Note that this call may update 'd_totalSize'.

    BSLS_ASSERT(d_totalSize <= INT_MAX - srcBlob->totalSize());
    BSLS_ASSERT(numBuffers() <= INT_MAX - srcBlob->numBuffers());

    const int numSrcDataBuffers = srcBlob->numDataBuffers();
    const int numDstDataBuffers = numDataBuffers();

    reserveBufferCapacity(static_cast<int>(d_buffers.size()) +
                          numSrcDataBuffers);

    BlobBufferIterator dstIter = d_buffers.begin() + numDstDataBuffers;
    BlobBufferIterator srcIter = srcBlob->d_buffers.begin();

    d_buffers.insert(dstIter, numSrcDataBuffers, BlobBuffer());

    for (int i = 0; i < numSrcDataBuffers; ++i, ++srcIter, ++dstIter) {
        dstIter->buffer().swap(srcIter->buffer());
        dstIter->setSize(srcIter->size());
    }

    const int totalSizeAdded =
        srcBlob->d_preDataIndexLength +
        srcBlob->d_buffers[numSrcDataBuffers - 1].size();

    d_dataIndex = 0 == length() ? numSrcDataBuffers - 1
                                : d_dataIndex + numSrcDataBuffers;
    d_preDataIndexLength = d_dataLength + srcBlob->d_preDataIndexLength;
    d_dataLength += srcBlob->d_dataLength;
    d_totalSize += totalSizeAdded;

    srcBlob->d_buffers.erase(srcBlob->d_buffers.begin(),
                             srcBlob->d_buffers.begin() + numSrcDataBuffers);

    srcBlob->d_dataIndex          = -1;
    srcBlob->d_dataLength         =  0;
    srcBlob->d_preDataIndexLength =  0;
    srcBlob->d_totalSize -= totalSizeAdded;
}
}  // close package namespace

// FREE OPERATORS
bool bdlbb::operator==(const Blob& lhs, const Blob& rhs)
{
    return lhs.d_buffers == rhs.d_buffers &&
           lhs.d_totalSize == rhs.d_totalSize &&
           lhs.d_dataLength == rhs.d_dataLength &&
           lhs.d_dataIndex == rhs.d_dataIndex &&
           lhs.d_preDataIndexLength == rhs.d_preDataIndexLength;
}

bool bdlbb::operator!=(const Blob& lhs, const Blob& rhs)
{
    return lhs.d_buffers != rhs.d_buffers ||
           lhs.d_totalSize != rhs.d_totalSize ||
           lhs.d_dataLength != rhs.d_dataLength ||
           lhs.d_dataIndex != rhs.d_dataIndex ||
           lhs.d_preDataIndexLength != rhs.d_preDataIndexLength;
}

// FREE FUNCTIONS
void
bdlbb::swap(Blob& a, Blob& b)
{
    if (a.allocator() == b.allocator()) {
        a.swap(b);
    }
    else {
        typedef bslmf::MovableRefUtil MoveUtil;
        Blob x(MoveUtil::move(a), a.allocator());
        a = b;
        b = x;
    }
}

}  // close enterprise namespace


//  This example shows how to create a simple lexer recognizing a couple of
//  different tokens aimed at a simple language and how to use this lexer with
//  a grammar. It shows how to associate attributes to tokens and how to access the
//  token attributes from inside the grammar.
//  Additionally, this example demonstrates, how to define a token set usable
//  as the skip parser during parsing, allowing to define several tokens to be
//  The main purpose of this example is to show how inheritance can be used to
//  overload parts of a base grammar and add token definitions to a base lexer.
//  Further, it shows how you can use the 'omit' attribute type specifier
//  for token definitions to force the token to have no attribute (expose an
//  This example recognizes a very simple programming language having
//  assignment statements and if and while control structures. Look at the file
//  example5.input for an example.

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/lex_lexertl.hpp>
#include <boost/phoenix/operator.hpp>

#include <iostream>
#include <fstream>
#include <string>

#include "example.hpp"

using namespace boost::spirit;
using boost::phoenix::val;

//  Token definition base, defines all tokens for the base grammar below
template <typename Lexer>
struct example5_base_tokens : lex::lexer<Lexer>
{
protected:
    // this lexer is supposed to be used as a base type only

public:
    void init_token_definitions()
    {
        // define the tokens to match
        identifier = "[a-zA-Z_][a-zA-Z0-9_]*";
        constant = "[0-9]+";
        if_ = "if";
        while_ = "while";

        // associate the tokens and the token set with the lexer
        this->self += lex::token_def<>('(') | ')' | '{' | '}' | '=' | ';' | constant;
        this->self += if_ | while_ | identifier;

        // define the whitespace to ignore (spaces, tabs, newlines and C-style
        this->self("WS")
            =   lex::token_def<>("[ \\t\\n]+")
            |   "\\/\\*[^*]*\\*+([^/*][^*]*\\*+)*\\/"
            ;
    }

    // these tokens have no attribute
    lex::token_def<lex::omit> if_, while_;

    // The following two tokens have an associated attribute type, 'identifier'
    // carries a string (the identifier name) and 'constant' carries the
    // matched integer value.
    // Note: any token attribute type explicitly specified in a token_def<>
    //       declaration needs to be listed during token type definition as
    //       well (see the typedef for the token_type below).
    // The conversion of the matched input to an instance of this type occurs
    // once (on first access), which makes token attributes as efficient as
    // possible. Moreover, token instances are constructed once by the lexer
    // library. From this point on tokens are passed by reference only,
    // avoiding them being copied around.
    lex::token_def<std::string> identifier;
    lex::token_def<unsigned int> constant;
};

//  Grammar definition base, defines a basic language
template <typename Iterator, typename Lexer>
struct example5_base_grammar
  : qi::grammar<Iterator, qi::in_state_skipper<Lexer> >
{
    template <typename TokenDef>
    example5_base_grammar(TokenDef const& tok)
      : example5_base_grammar::base_type(program)
    {
        using boost::spirit::_val;

        program
            =  +block
            ;

        block
            =   '{' >> *statement >> '}'
            ;

        statement
            =   assignment
            |   if_stmt
            |   while_stmt
            ;

        assignment
            =   (tok.identifier >> '=' >> expression >> ';')
                [
                    std::cout << val("assignment statement to: ") << _1 << "\n"
                ]
            ;

        if_stmt
            =   (tok.if_ >> '(' >> expression >> ')' >> block)
                [
                    std::cout << val("if expression: ") << _1 << "\n"
                ]
            ;

        while_stmt
            =   (tok.while_ >> '(' >> expression >> ')' >> block)
                [
                    std::cout << val("while expression: ") << _1 << "\n"
                ]
            ;

        //  since expression has a variant return type accommodating for
        //  std::string and unsigned integer, both possible values may be
        //  returned to the calling rule
        expression
            =   tok.identifier [ _val = _1 ]
            |   tok.constant   [ _val = _1 ]
            ;
    }

    typedef qi::in_state_skipper<Lexer> skipper_type;

    qi::rule<Iterator, skipper_type> program, block, statement;
    qi::rule<Iterator, skipper_type> assignment, if_stmt;
    qi::rule<Iterator, skipper_type> while_stmt;

    //  the expression is the only rule having a return value
    typedef boost::variant<unsigned int, std::string> expression_type;
    qi::rule<Iterator, expression_type(), skipper_type>  expression;
};

//  Token definition for derived lexer, defines additional tokens
template <typename Lexer>
struct example5_tokens : example5_base_tokens<Lexer>
{
    typedef example5_base_tokens<Lexer> base_type;

    example5_tokens()
    {
        // define the additional token to match
        else_ = "else";

        // associate the new token with the lexer, note we add 'else' before
        // anything else to add it to the token set before the identifier
        // token, otherwise "else" would be matched as an identifier
        this->self = else_;

        // now add the token definitions from the base class
        this->base_type::init_token_definitions();
    }

    // this token has no attribute
    lex::token_def<lex::omit> else_;
};

//  Derived grammar definition, defines a language extension
template <typename Iterator, typename Lexer>
struct example5_grammar : example5_base_grammar<Iterator, Lexer>
{
    template <typename TokenDef>
    example5_grammar(TokenDef const& tok)
      : example5_base_grammar<Iterator, Lexer>(tok)
    {
        // we alter the if_stmt only
        this->if_stmt
            =   this->if_stmt.copy() >> -(tok.else_ >> this->block)
            ;
    }
};

int main()
{
    // iterator type used to expose the underlying input stream
    typedef std::string::iterator base_iterator_type;

    // This is the lexer token type to use. The second template parameter lists
    // all attribute types used for token_def's during token definition (see
    // example5_base_tokens<> above). Here we use the predefined lexertl token
    // type, but any compatible token type may be used instead.
    // If you don't list any token attribute types in the following declaration
    // (or just use the default token type: lexertl_token<base_iterator_type>)
    // it will compile and work just fine, just a bit less efficient. This is
    // because the token attribute will be generated from the matched input
    // sequence every time it is requested. But as soon as you specify at
    // least one token attribute type you'll have to list all attribute types
    // used for token_def<> declarations in the token definition class above,
    // otherwise compilation errors will occur.
    typedef lex::lexertl::token<
        base_iterator_type, boost::mpl::vector<unsigned int, std::string>
    > token_type;

    // Here we use the lexertl based lexer engine.
    typedef lex::lexertl::lexer<token_type> lexer_type;

    // This is the token definition type (derived from the given lexer type).
    typedef example5_tokens<lexer_type> example5_tokens;

    // this is the iterator type exposed by the lexer
    typedef example5_tokens::iterator_type iterator_type;

    // this is the type of the grammar to parse
    typedef example5_grammar<iterator_type, example5_tokens::lexer_def> example5_grammar;

    // now we use the types defined above to create the lexer and grammar
    // object instances needed to invoke the parsing process
    example5_tokens tokens;                         // Our lexer
    example5_grammar calc(tokens);                  // Our parser

    std::string str (read_from_file("example5.input"));

    // At this point we generate the iterator pair used to expose the
    // tokenized input stream.
    std::string::iterator it = str.begin();
    iterator_type iter = tokens.begin(it, str.end());
    iterator_type end = tokens.end();

    // Parsing is done based on the token stream, not the character
    // stream read from the input.
    // Note how we use the lexer defined above as the skip parser. It must
    // be explicitly wrapped inside a state directive, switching the lexer
    // state for the duration of skipping whitespace.
    std::string ws("WS");
    bool r = qi::phrase_parse(iter, end, calc, qi::in_state(ws)[tokens.self]);

    if (r && iter == end)
    {
        std::cout << "-------------------------\n";
        std::cout << "Parsing succeeded\n";
        std::cout << "-------------------------\n";
    }
    else
    {
        std::cout << "-------------------------\n";
        std::cout << "Parsing failed\n";
        std::cout << "-------------------------\n";
    }

    std::cout << "Bye... :-) \n\n";
    return 0;
}


inline void bfs() {
        for (int i = 0; i < maxn; ++i) {
            dep[i] = -1; gap[i] = 0;
        }
        q.push(t);
        dep[t] = 0, gap[0] = 1;
        while (!q.empty()) {
            int u = q.front();
            q.pop();
            for (int i = head[u]; i >= 0; i = edges[i].next) {
                int v = edges[i].to;
                if (dep[v] != -1)continue;
                q.push(v);
                dep[v] = dep[u] + 1, gap[dep[v]]++;
            }
        }
    }

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
struct Maxflow_ISAP{
    static const int INF = 0x3f3f3f3f;
    static const int maxn = 2500;//点的大小
    static const int maxm = 1010000;//边
    struct edge {
        int next, to, w;
    }edges[maxm << 1];//双向边
    int head[maxn], cnt;
    inline void addedge(int u, int v, int w) {
        edges[cnt].next = head[u];
        edges[cnt].to = v;
        edges[cnt].w = w;
        head[u] = cnt++;
        edges[cnt].next = head[v];
        edges[cnt].to = u;
        edges[cnt].w = 0;
        head[v] = cnt++;
    }
    int n;
    int s, t;
    int maxflow;
    int dep[maxn], gap[maxn], cur[maxn];
    queue<int> q;
    inline void init(int n) {
        this->n = n;
        memset(head, -1, sizeof(head));
        while(q.size())q.pop();
        cnt = 0;
        memset(dep, 0, sizeof(dep));
        memset(gap, 0, sizeof(gap));
        memset(cur, 0, sizeof(cur));
        maxflow = 0;
    }

    //gap[i]指深度为i的点的数量
    //cur为当前弧优化

    inline int dfs(int u, int flow) {
        if (u == t) {
            maxflow += flow;
            return flow;
        }
        int occured = 0;
        for (int& i = cur[u]; i >= 0; i = edges[i].next) {
            int v = edges[i].to;
            if (edges[i].w && dep[v] + 1 == dep[u]) {
                int d = dfs(v, min(flow - occured, edges[i].w));
                if (d > 0) {
                    edges[i].w -= d; edges[i ^ 1].w += d;
                    occured += d;
                }
                if (occured == flow)return occured;
            }
        }
        //前面和dinic一样
        //后面表示所有点都流过了而且还有剩余
        --gap[dep[u]];
        if (!gap[dep[u]])dep[s] = n + 1;
        dep[u]++;
        gap[dep[u]]++;
        return occured;
    }

    inline int ISAP(int S, int T) {
        this->s = S, this->t = T;
        bfs();
        while (dep[s] < n) {
            memcpy(cur, head, sizeof(head));
            dfs(s, INF);
        }
        return maxflow;
    }
}solver;
int n, m, s, t;
int a[1010], b[1010];
inline void write(int x) {
    if (x < 0)putchar_unlocked('-'), x = -x;
    if (x > 9)write(x / 10);
    putchar_unlocked(x % 10 + 48);
}
inline int read() {
    int k = 0, f = 1;
    char c = getchar_unlocked();
    while (c < '0' || c>'9') {
        if (c == '-')f = -1;
        c = getchar_unlocked();
    }
    while (c >= '0' && c <= '9') {
        k = (k << 1) + (k << 3) + c - 48;
        c = getchar_unlocked();
    }
    return k * f;
}
int main() {
    while(scanf("%d%d", &n, &m) != EOF) {
        solver.init(n + m + 2);
        s = n + m + 1, t = s + 1;
        for(int i = 1; i <= n; ++i) a[i] = read(), solver.addedge(s, i, 1);
        for(int i = 1; i <= m; ++i) {
            b[i] = read();
            solver.addedge(n + i, t, 1);
            for (int j = 1; j <= n; ++j)
                if(!(b[i] % a[j]))
                    solver.addedge(j, n + i, 1);
        }
        write(solver.ISAP(s, t)), putchar_unlocked('\n');
    }
}


void TranspositionTable::clear() {
  memset(entries, 0, size * 4 * sizeof(TTEntry));
}

/*
  Glaurung, a UCI chess playing engine.

*/

#include <cassert>
#include <cmath>
#include <cstring>

#include "tt.h"

TranspositionTable::TranspositionTable(unsigned mbSize) {
  size = 0;
  generation = 0;
  writes = 0;
  entries = 0;
  this->set_size(mbSize);
}

TranspositionTable::~TranspositionTable() {
  delete [] entries;
}

/// TranspositionTable::set_size sets the size of the transposition table,
/// measured in megabytes.

void TranspositionTable::set_size(unsigned mbSize) {
  unsigned newSize;

  assert(mbSize >= 4 && mbSize <= 1024);

  for(newSize = 1024; newSize * 4 * (sizeof(TTEntry)) <= (mbSize << 20);
      newSize *= 2);
  newSize /= 2;

  if(newSize != size) {
    size = newSize;
    delete [] entries;
    entries = new TTEntry[size * 4];
    if(entries == NULL) {
      std::cerr << "Failed to allocate " << mbSize
                << " MB for transposition table."
                << std::endl;
      exit(EXIT_FAILURE);
    }
    this->clear();
  }
}

/// TranspositionTable::clear overwrites the entire transposition table
/// with zeroes.  It is called whenever the table is resized, or when the
/// user asks the program to clear the table (from the UCI interface).
/// Perhaps we should also clear it when the "ucinewgame" command is recieved?

/// TranspositionTable::store writes a new entry containing a position,
/// a value, a value type, a search depth, and a best move to the
/// transposition table.  The transposition table is organized in clusters
/// of four TTEntry objects, and when a new entry is written, it replaces
/// the least valuable of the four entries in a cluster.  A TTEntry t1 is
/// considered to be more valuable than a TTEntry t2 if t1 is from the
/// current search and t2 is from a previous search, or if the depth of t1
/// is bigger than the depth of t2.

void TranspositionTable::store(const Position &pos, Value v, Depth d,
                               Move m, ValueType type) {
  TTEntry *tte, *replace;

  tte = replace = entries + int(pos.get_key() & (size - 1)) * 4;
  for(int i = 0; i < 4; i++) {
    if((tte+i)->key() == pos.get_key()) {
      if(m == MOVE_NONE)
        m = (tte+i)->move();
      *(tte+i) = TTEntry(pos.get_key(), v, type, d, m, generation);
      return;
    }
    if(replace->generation() == generation) {
      if((tte+i)->generation() != generation ||
         (tte+i)->depth() < replace->depth())
        replace = tte+i;
    }
    else if((tte+i)->generation() != generation &&
            (tte+i)->depth() < replace->depth())
      replace = tte+i;
  }
  *replace = TTEntry(pos.get_key(), v, type, d, m, generation);
  writes++;
}

/// TranspositionTable::retrieve looks up the current position in the
/// transposition table, and extracts the value, value type, depth and
/// best move if the position is found.  The return value is true if
/// the position is found, and false if it isn't.

bool TranspositionTable::retrieve(const Position &pos, Value *value,
                                  Depth *d, Move *move,
                                  ValueType *type) const {
  TTEntry *tte;
  bool found = false;

  tte = entries + int(pos.get_key() & (size - 1)) * 4;
  for(int i = 0; i < 4 && !found ; i++)
    if((tte+i)->key() == pos.get_key()) {
      tte = tte + i;
      found = true;
    }
  if(!found) {
    *move = MOVE_NONE;
    return false;
  }

  *value = tte->value();
  *type = tte->type();
  *d = tte->depth();
  *move = tte->move();

  return true;
}

/// TranspositionTable::new_search() is called at the beginning of every new
/// search.  It increments the "generation" variable, which is used to
/// distinguish transposition table entries from previous searches from
/// entries from the current search.

void TranspositionTable::new_search() {
  generation++;
  writes = 0;
}

/// TranspositionTable::insert_pv() is called at the end of a search
/// iteration, and inserts the PV back into the PV.  This makes sure the
/// old PV moves are searched first, even if the old TT entries have been

void TranspositionTable::insert_pv(const Position &pos, Move pv[]) {
  UndoInfo u;
  Position p(pos);

  for(int i = 0; pv[i] != MOVE_NONE; i++) {
    this->store(p, VALUE_NONE, Depth(0), pv[i], VALUE_TYPE_NONE);
    p.do_move(pv[i], u);
  }
}

/// TranspositionTable::full() returns the permill of all transposition table
/// entries which have received at least one write during the current search.
/// It is used to display the "info hashfull ..." information in UCI.

int TranspositionTable::full() {
  double N = double(size) * 4.0;
  return int(1000 * (1 - exp(writes * log(1.0 - 1.0/N))));
}

TTEntry::TTEntry() {
}

TTEntry::TTEntry(Key k, Value v, ValueType t, Depth d, Move m,
                 int generation) {
  key_ = k;
  data = (m & 0x7FFFF) | (t << 20) | (generation << 23);
  value_ = v;
  depth_ = int16_t(d);
}

/// Functions for extracting data from TTEntry objects.

Key TTEntry::key() const {
  return key_;
}

Depth TTEntry::depth() const {
  return Depth(depth_);
}

Move TTEntry::move() const {
  return Move(data & 0x7FFFF);
}

Value TTEntry::value() const {
  return Value(value_);
}

ValueType TTEntry::type() const {
  return ValueType((data >> 20) & 3);
}

int TTEntry::generation() const {
  return (data >> 23);
}


#include "Internal.hpp"
#include <LuminoEngine/Reflection/VMProperty.hpp>
#include <LuminoEngine/UI/UICommand.hpp>
#include <LuminoEngine/UI/Layout/UILayoutPanel.hpp>
#include <LuminoEngine/UI/Controls/UIListBox.hpp>

namespace ln {

// UIListItemsControl

LN_OBJECT_IMPLEMENT(UIListItemsControl, UIControl) {}

UIListItemsControl::UIListItemsControl()
	: m_itemsHostLayout(nullptr)
	, m_selectedItems()
	, m_selectionMoveMode(UIListSelectionMoveMode::Cyclic)
	, m_submitMode(UIListSubmitMode::Single)
{
}

bool UIListItemsControl::init()
{
	if (!UIControl::init()) return false;

	// TODO: 負荷軽減のため、デフォルトでは作成しない。その場合、StackLayout としてレイアウトする
	auto layout = makeObject<UIStackLayout>();
	layout->setOrientation(UILayoutOrientation::Vertical);
	setItemsLayoutPanel(layout);

	return true;
}

void UIListItemsControl::selectItem(UIListItem* item)
{
	if (LN_REQUIRE(m_logicalChildren->contains(item))) return;
	selectItemExclusive(item);
}

UIListItem* UIListItemsControl::selectedItem() const
{
	if (m_selectedItems.isEmpty()) return nullptr;
	return m_selectedItems.front();
}

void UIListItemsControl::setItemsLayoutPanel(UILayoutPanel* layout)
{
	if (LN_REQUIRE(layout)) return;

	if (m_itemsHostLayout) {
		for (auto& item : *m_logicalChildren) {
			m_itemsHostLayout->removeVisualChild(item);
		}
		removeVisualChild(m_itemsHostLayout);
	}

	m_itemsHostLayout = layout;

	if (m_itemsHostLayout) {
		addVisualChild(m_itemsHostLayout);

		for (const auto& item : *m_logicalChildren) {
			m_itemsHostLayout->addVisualChild(item);
		}
	}
}

UILayoutPanel* UIListItemsControl::itemsLayoutPanel() const
{
	return m_itemsHostLayout;
}

void UIListItemsControl::setSubmitMode(UIListSubmitMode value)
{
	m_submitMode = value;
}

UIListSubmitMode UIListItemsControl::submitMode() const
{
	return m_submitMode;
}

Ref<EventConnection> UIListItemsControl::connectOnSubmit(Ref<UIGeneralEventHandler> handler)
{
	return m_onSubmit.connect(handler);
}

void UIListItemsControl::addListItem(UIListItem* item)
{
	if (LN_REQUIRE(item)) return;
	if (LN_REQUIRE(!item->logicalParent())) return;

	item->m_ownerListControl = this;

	m_logicalChildren->add(item);
	item->setLogicalParent(this);

	if (m_itemsHostLayout) {
		m_itemsHostLayout->addVisualChild(item);
	}
}

void UIListItemsControl::removeListItem(UIListItem* item)
{
	if (LN_REQUIRE(item)) return;
	if (LN_REQUIRE(item->m_ownerListControl == this)) return;

	m_selectedItems.remove(item);

	item->m_ownerListControl = nullptr;

	m_logicalChildren->remove(item);
	item->setLogicalParent(nullptr);

	if (m_itemsHostLayout) {
		m_itemsHostLayout->removeVisualChild(item);
	}
}

void UIListItemsControl::removeAllItems()
{
	if (!m_logicalChildren->isEmpty()) {
		for (const auto& item : *m_logicalChildren) {
			static_pointer_cast<UIListItem>(item)->m_ownerListControl = nullptr;
			item->setLogicalParent(nullptr);
			m_itemsHostLayout->removeVisualChild(item);
		}
		m_logicalChildren->clear();
	}
}

void UIListItemsControl::submitItem(UIListItem* item)
{
	m_onSubmit.raise(UIEventArgs::create(item, UIEvents::Submitted));
}

void UIListItemsControl::onRoutedEvent(UIEventArgs* e)
{
	UIControl::onRoutedEvent(e);

	if (e->type() == UIEvents::KeyDownEvent) {

		if (!m_logicalChildren->isEmpty()) {
			auto* k = static_cast<UIKeyEventArgs*>(e);
			Vector2 dir;
			if (k->getKey() == Keys::Left)
				dir.x = -1;
			else if (k->getKey() == Keys::Up)
				dir.y = -1;
			else if (k->getKey() == Keys::Right)
				dir.x = 1;
			else if (k->getKey() == Keys::Down)
				dir.y = 1;

			if (dir != Vector2::Zero) {
				if (m_selectedItems.isEmpty()) {
					selectItemExclusive(static_pointer_cast<UIListItem>(m_logicalChildren->at(0)));
				}
				else {

					UIElement* selected = m_selectedItems.front();
					UIElement* next = nullptr;
					float distance = std::numeric_limits<float>::max();
					for (const auto& item : *m_logicalChildren) {
						auto diff = item->localPosition() - selected->localPosition();
						auto f = diff * dir;
						if (f.x >= 1.0f || f.y >= 1.0f) {
							float len = diff.lengthSquared();
							if (len < distance) {
								distance = len;
								next = item;
							}
						}
					}

					if (!next && m_selectionMoveMode == UIListSelectionMoveMode::Cyclic) {
						dir *= -1.0f;
						distance = 0.0f;
						for (const auto& item : *m_logicalChildren) {
							auto diff = item->localPosition() - selected->localPosition();
							auto f = diff * dir;
							if (f.x >= 1.0f || f.y >= 1.0f) {
								float len = diff.lengthSquared();
								if (len > distance) {
									distance = len;
									next = item;
								}
							}
						}
					}

					if (next) {
						selectItemExclusive(static_cast<UIListItem*>(next));
					}
				}

				e->handled = true;
			}
		}

		return;
	}
	else if (e->type() == UIEvents::ExecuteCommandEvent) {
		if (static_cast<UICommandEventArgs*>(e)->command() == UICommonInputCommands::submit()) {
			if (UIListItem* item = selectedItem()) {
				submitItem(item);
				item->onSubmit();
			}
		}
	}

}

Size UIListItemsControl::measureOverride(UILayoutContext* layoutContext, const Size& constraint)
{
	if (m_itemsHostLayout) {
		m_itemsHostLayout->measureLayout(layoutContext, constraint);
		return layoutContext->makeDesiredSize(this, m_itemsHostLayout->desiredSize());
	}
	else {
		LN_NOTIMPLEMENTED();
		return UIControl::measureOverride(layoutContext, constraint);
	}
}

Size UIListItemsControl::arrangeOverride(UILayoutContext* layoutContext, const Rect& finalArea)
{
	const auto finalSize = finalArea.getSize();
	Rect contentRect = layoutContext->makeContentRect(this, finalSize);

	if (m_itemsHostLayout) {
		m_itemsHostLayout->arrangeLayout(layoutContext, contentRect);
		return finalSize;
	}
	else {
		LN_NOTIMPLEMENTED();
		return UIControl::arrangeOverride(layoutContext, finalArea);
	}
}

void UIListItemsControl::notifyItemClicked(UIListItem* item, int clickCount)
{
	if (submitMode() == UIListSubmitMode::Single) {
		submitItem(item);
		item->onSubmit();
	}
	else {
		selectItemExclusive(item);
	}
}

void UIListItemsControl::selectItemExclusive(UIListItem* item)
{
	if (LN_REQUIRE(item)) return;

	for (const auto& item : m_selectedItems) {
		item->setSelectedInternal(false);
	}
	m_selectedItems.clear();

	m_selectedItems.add(item);
	item->setSelectedInternal(true);
}

void UIListItemsControl::BuilderDetails::apply(UIListItemsControl* p) const
{
	UIControl::BuilderDetails::apply(p);
	if (onSubmit) p->connectOnSubmit(onSubmit);
}

// UIListBox

Ref<UIListBox> UIListBox::create()
{
    return makeObject<UIListBox>();
}

UIListBox::UIListBox()
	: m_scrollViewHelper()
{
}

bool UIListBox::init()
{
	if (!UIListItemsControl::init()) return false;

	m_scrollViewHelper = makeRef<UIScrollViewHelper>(this);
	m_scrollViewHelper->setScrollTarget(itemsLayoutPanel());
	m_scrollViewHelper->setVScrollbarVisible(true);

	return true;
}

UIListBoxItem* UIListBox::addItem(const ln::String& text, Ref<Variant> data)
{
	auto item = UIListBoxItem::create(text);
	item->setData(data);
	addChild(item);
	return item;
}

UIListBoxItem* UIListBox::addItem(UIElement* content)
{
	if (content->m_specialElementFlags.hasFlag(detail::UISpecialElementFlags::ListItem)) {
		addChild(content);
		return static_cast<UIListBoxItem*>(content);
	}
	else {
		auto item = makeObject<UIListBoxItem>(content);
		addChild(item);
		return item;
	}
}

void UIListBox::bind(ObservablePropertyBase* prop)
{
	if (prop->type()->typeClass() == TypeInfoClass::Object) {
		auto obj = prop->getValue()->getObject<Object>();
		if (auto collection = dynamic_pointer_cast<CollectionObject>(obj)) {	// TODO: TypeInfo で Collection かどうか調べたいところ
			int count = collection->getItemCount();
			for (int i = 0; i < count; i++) {
				auto item = ln::makeObject<UIListBoxItem>();
				auto data = collection->getItemAsVariant(i);
				if (auto dataObj = data->getAsObject<Object>()) {
					if (detail::ObjectHelper::isObservableProperty(dataObj)) {
						item->bind(static_pointer_cast<ObservablePropertyBase>(dataObj));
					}
					else {
						LN_NOTIMPLEMENTED();
					}
				}
				else {
					LN_NOTIMPLEMENTED();
				}

				addListItem(item);
			}
		}
	}
}

void UIListBox::onAddChild(UIElement* child)
{
	// TODO: flag で判定
	if (auto* item1 = dynamic_cast<UIListBoxItem*>(child)) {
		addListItem(item1);
	}
	else {
		auto item = ln::makeObject<UIListBoxItem>();
		item->addElement(child);
		addListItem(item);
	}
}

Size UIListBox::measureOverride(UILayoutContext* layoutContext, const Size& constraint)
{
	const Size scrollBarDesiredSize = m_scrollViewHelper->measure(layoutContext, constraint);
	const Size baseSize = UIListItemsControl::measureOverride(layoutContext, constraint);
	return Size(baseSize.width + scrollBarDesiredSize.width, baseSize.height + scrollBarDesiredSize.height);
}

Size UIListBox::arrangeOverride(UILayoutContext* layoutContext, const Rect& finalArea)
{
	const Size finalSize = finalArea.getSize();
	const Rect clientArea = m_scrollViewHelper->calculateClientArea(finalSize);

	UIListItemsControl::arrangeOverride(layoutContext, clientArea);

	m_scrollViewHelper->arrange(layoutContext, finalSize);

	return finalSize;
}

void UIListBox::onRoutedEvent(UIEventArgs* e)
{
	UIListItemsControl::onRoutedEvent(e);
	m_scrollViewHelper->handleRoutedEvent(e);
}

void UIListBox::BuilderDetails::apply(UIListBox* p) const
{
	UIListItemsControl::BuilderDetails::apply(p);
}

} // namespace ln


void UShooterGameViewportClient::BeginDestroy()
{
	ReleaseSlateResources();

	Super::BeginDestroy();
}

#include "ShooterGame.h"
#include "ShooterGameViewportClient.h"
#include "SShooterConfirmationDialog.h"
#include "Widgets/Layout/SSafeZone.h"
#include "Widgets/Images/SThrobber.h"
#include "Player/ShooterLocalPlayer.h"

UShooterGameViewportClient::UShooterGameViewportClient(const FObjectInitializer& ObjectInitializer)
	: Super(ObjectInitializer)
{
	SetSuppressTransitionMessage(true);
}

void UShooterGameViewportClient::NotifyPlayerAdded(int32 PlayerIndex, ULocalPlayer* AddedPlayer)
{
	Super::NotifyPlayerAdded(PlayerIndex, AddedPlayer);

 	UShooterLocalPlayer* const ShooterLP = Cast<UShooterLocalPlayer>(AddedPlayer);
 	if (ShooterLP)
 	{
 		ShooterLP->LoadPersistentUser();
 	}
}

void UShooterGameViewportClient::AddViewportWidgetContent( TSharedRef<class SWidget> ViewportContent, const int32 ZOrder )
{
	UE_LOG( LogPlayerManagement, Log, TEXT( "UShooterGameViewportClient::AddViewportWidgetContent: %p" ), &ViewportContent.Get() );

	if ( ( DialogWidget.IsValid() || LoadingScreenWidget.IsValid() ) && ViewportContent != DialogWidget && ViewportContent != LoadingScreenWidget )
	{
		// Add to hidden list, and don't show until we hide the dialog widget
		HiddenViewportContentStack.AddUnique( ViewportContent );
		return;
	}

	if ( ViewportContentStack.Contains( ViewportContent ) )
	{
		return;
	}

	ViewportContentStack.AddUnique( ViewportContent );

	Super::AddViewportWidgetContent( ViewportContent, 0 );
}

void UShooterGameViewportClient::RemoveViewportWidgetContent( TSharedRef<class SWidget> ViewportContent )
{
	UE_LOG( LogPlayerManagement, Log, TEXT( "UShooterGameViewportClient::RemoveViewportWidgetContent: %p" ), &ViewportContent.Get()  );

	ViewportContentStack.Remove( ViewportContent );
	HiddenViewportContentStack.Remove( ViewportContent );

	Super::RemoveViewportWidgetContent( ViewportContent );
}

void UShooterGameViewportClient::HideExistingWidgets()
{
	check( HiddenViewportContentStack.Num() == 0 );

	TArray<TSharedRef<class SWidget>> CopyOfViewportContentStack = ViewportContentStack;

	for ( int32 i = ViewportContentStack.Num() - 1; i >= 0; i-- )
	{
		RemoveViewportWidgetContent( ViewportContentStack[i] );
	}

	HiddenViewportContentStack = CopyOfViewportContentStack;
}

void UShooterGameViewportClient::ShowExistingWidgets()
{
	// We shouldn't have any visible widgets at this point
	check( ViewportContentStack.Num() == 0 );

	// Unhide all of the previously hidden widgets
	for ( int32 i = 0; i < HiddenViewportContentStack.Num(); i++ )
	{
		AddViewportWidgetContent( HiddenViewportContentStack[i] );
	}

	check( ViewportContentStack.Num() == HiddenViewportContentStack.Num() );

	// Done with these
	HiddenViewportContentStack.Empty();
}

void UShooterGameViewportClient::ShowDialog(TWeakObjectPtr<ULocalPlayer> PlayerOwner, EShooterDialogType::Type DialogType, const FText& Message, const FText& Confirm, const FText& Cancel, const FOnClicked& OnConfirm, const FOnClicked& OnCancel)
{
	UE_LOG( LogPlayerManagement, Log, TEXT( "UShooterGameViewportClient::ShowDialog..." ) );

	if ( DialogWidget.IsValid() )
	{
		return;	// Already showing a dialog box
	}

	// Hide all existing widgets
	if ( !LoadingScreenWidget.IsValid() )
	{
		HideExistingWidgets();
	}

	DialogWidget = SNew( SShooterConfirmationDialog )
		.PlayerOwner(PlayerOwner)
		.DialogType(DialogType)
		.MessageText(Message)
		.ConfirmText(Confirm)
		.CancelText(Cancel)
		.OnConfirmClicked(OnConfirm)
		.OnCancelClicked(OnCancel);

	if ( LoadingScreenWidget.IsValid() )
	{
		// Can't show dialog while loading screen is visible
		HiddenViewportContentStack.Add( DialogWidget.ToSharedRef() );
	}
	else
	{
		AddViewportWidgetContent( DialogWidget.ToSharedRef() );

		// Remember what widget currently has focus
		OldFocusWidget = FSlateApplication::Get().GetKeyboardFocusedWidget();

		// Force focus to the dialog widget
		FSlateApplication::Get().SetKeyboardFocus( DialogWidget, EFocusCause::SetDirectly );
	}
}

void UShooterGameViewportClient::HideDialog()
{
	UE_LOG( LogPlayerManagement, Log, TEXT( "UShooterGameViewportClient::HideDialog. DialogWidget: %p, OldFocusWidget: %p" ), DialogWidget.Get(), OldFocusWidget.Get() );

	if ( DialogWidget.IsValid() )
	{
		const bool bRestoreOldFocus = OldFocusWidget.IsValid() && FSlateApplication::Get().GetKeyboardFocusedWidget() == DialogWidget;

		// Hide the dialog widget
		RemoveViewportWidgetContent( DialogWidget.ToSharedRef() );

		// Destroy the dialog widget
		DialogWidget = NULL;

		if ( !LoadingScreenWidget.IsValid() )
		{
			ShowExistingWidgets();
		}

		// Restore focus to last widget (but only if the dialog currently has focus still)
		if ( bRestoreOldFocus )
		{
			FSlateApplication::Get().SetKeyboardFocus( OldFocusWidget, EFocusCause::SetDirectly );
		}

		OldFocusWidget = NULL;
	}
}

void UShooterGameViewportClient::ShowLoadingScreen()
{
	if ( LoadingScreenWidget.IsValid() )
	{
		return;
	}

	if ( DialogWidget.IsValid() )
	{
		// Hide the dialog widget (loading screen takes priority)
		check( !HiddenViewportContentStack.Contains( DialogWidget.ToSharedRef() ) );
		check( ViewportContentStack.Contains( DialogWidget.ToSharedRef() ) );
		RemoveViewportWidgetContent( DialogWidget.ToSharedRef() );
		HiddenViewportContentStack.Add( DialogWidget.ToSharedRef() );
	}
	else
	{
		// Hide all existing widgets
		HideExistingWidgets();
	}

	LoadingScreenWidget = SNew( SShooterLoadingScreen );

	AddViewportWidgetContent( LoadingScreenWidget.ToSharedRef() );
}

void UShooterGameViewportClient::HideLoadingScreen()
{
	if ( !LoadingScreenWidget.IsValid() )
	{
		return;
	}

	RemoveViewportWidgetContent( LoadingScreenWidget.ToSharedRef() );

	LoadingScreenWidget = NULL;

	// Show the dialog widget if we need to
	if ( DialogWidget.IsValid() )
	{
		check( HiddenViewportContentStack.Contains( DialogWidget.ToSharedRef() ) );
		check( !ViewportContentStack.Contains( DialogWidget.ToSharedRef() ) );
		HiddenViewportContentStack.Remove( DialogWidget.ToSharedRef() );
		AddViewportWidgetContent( DialogWidget.ToSharedRef() );
	}
	else
	{
		ShowExistingWidgets();
	}
}

EShooterDialogType::Type UShooterGameViewportClient::GetDialogType() const
{
	return (DialogWidget.IsValid() ? DialogWidget->DialogType : EShooterDialogType::None);
}

TWeakObjectPtr<ULocalPlayer> UShooterGameViewportClient::GetDialogOwner() const
{
	return (DialogWidget.IsValid() ? DialogWidget->PlayerOwner : nullptr);
}

void UShooterGameViewportClient::Tick(float DeltaSeconds)
{
	if ( DialogWidget.IsValid() && !LoadingScreenWidget.IsValid() )
	{
		// Make sure the dialog widget always has focus
		if ( FSlateApplication::Get().GetKeyboardFocusedWidget() != DialogWidget )
		{
			// Remember which widget had focus before we override it
			OldFocusWidget = FSlateApplication::Get().GetKeyboardFocusedWidget();

			// Force focus back to dialog
			FSlateApplication::Get().SetKeyboardFocus( DialogWidget, EFocusCause::SetDirectly );
		}
	}
}

#if WITH_EDITOR
void UShooterGameViewportClient::DrawTransition(UCanvas* Canvas)
{
	if (GetOuterUEngine() != NULL)
	{
		ETransitionType Type = GetOuterUEngine()->TransitionType;
		switch (Type)
		{
		case ETransitionType::Connecting:
			DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "ConnectingMessage", "CONNECTING").ToString());
			break;
		case ETransitionType::WaitingToConnect:
			DrawTransitionMessage(Canvas, NSLOCTEXT("GameViewportClient", "Waitingtoconnect", "Waiting to connect...").ToString());
			break;
		}
	}
}
#endif //WITH_EDITOR

void UShooterGameViewportClient::DetachViewportClient()
{
	Super::DetachViewportClient();

	ReleaseSlateResources();
}

void UShooterGameViewportClient::ReleaseSlateResources()
{
	OldFocusWidget.Reset();
	LoadingScreenWidget.Reset();
	ViewportContentStack.Empty();
	HiddenViewportContentStack.Empty();
}

void SShooterLoadingScreen::Construct(const FArguments& InArgs)
{
	static const FName LoadingScreenName(TEXT("/Game/UI/Menu/LoadingScreen.LoadingScreen"));

	//since we are not using game styles here, just load one image
	LoadingScreenBrush = MakeShareable( new FShooterGameLoadingScreenBrush( LoadingScreenName, FVector2D(1920,1080) ) );

	ChildSlot
	[
		SNew(SOverlay)
		+SOverlay::Slot()
		.HAlign(HAlign_Fill)
		.VAlign(VAlign_Fill)
		[
			SNew(SImage)
			.Image(LoadingScreenBrush.Get())
		]
		+SOverlay::Slot()
		.HAlign(HAlign_Fill)
		.VAlign(VAlign_Fill)
		[
			SNew(SSafeZone)
			.VAlign(VAlign_Bottom)
			.HAlign(HAlign_Right)
			.Padding(10.0f)
			.IsTitleSafe(true)
			[
				SNew(SThrobber)
				.Visibility(this, &SShooterLoadingScreen::GetLoadIndicatorVisibility)
			]
		]
	];
}