/*
 * This file is part of libsidplayfp, a SID player engine.
 *
 * Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
 * Copyright 2009-2014 VICE Project
 * Copyright 2007-2010 Antti Lankila
 * Copyright 2001 Simon White
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

// References below are from:
//     The MOS 6567/6569 video controller (VIC-II)
//     and its application in the Commodore 64
//     http://www.uni-mainz.de/~bauec002/VIC-Article.gz
//
// MOS 6572 info taken from http://solidstate.com.ar/wp/?p=200

#include "mos656x.h"

#include <cstring>

#include "sidendian.h"

/// Cycle # at which the VIC takes the bus in a bad line (BA goes low).
const unsigned int VICII_FETCH_CYCLE = 11;

const unsigned int VICII_SCREEN_TEXTCOLS = 40;

const MOS656X::model_data_t MOS656X::modelData[] =
{
    {262, 64, &MOS656X::clockOldNTSC},  // Old NTSC
    {263, 65, &MOS656X::clockNTSC},     // NTSC-M
    {312, 63, &MOS656X::clockPAL},      // PAL-B
    {312, 65, &MOS656X::clockNTSC},     // PAL-N
};

const char *MOS656X::credit =
{   // Optional information
    "MOS6567/6569/6572 (VIC II) Emulation:\n"
    "\tCopyright (C) 2001 Simon White\n"
    "\tCopyright (C) 2007-2010 Antti Lankila\n"
    "\tCopyright (C) 2009-2014 VICE Project\n"
    "\tCopyright (C) 2011-2016 Leandro Nini\n"
};


MOS656X::MOS656X(EventContext *context) :
    Event("VIC Raster"),
    event_context(*context),
    sprites(regs),
    badLineStateChangeEvent("Update AEC signal", *this, &MOS656X::badLineStateChange),
    rasterYIRQEdgeDetectorEvent("RasterY changed", *this, &MOS656X::rasterYIRQEdgeDetector)
{
    chip (MOS6569);
}

void MOS656X::reset()
{
    irqFlags            = 0;
    irqMask             = 0;
    yscroll             = 0;
    rasterY             = maxRasters - 1;
    lineCycle           = 0;
    areBadLinesEnabled  = false;
    isBadLine           = false;
    rasterYIRQCondition = false;
    rasterClk           = 0;
    vblanking           = false;
    lpAsserted          = false;

    memset(regs, 0, sizeof(regs));

    lp.reset();
    sprites.reset();

    event_context.cancel(*this);
    event_context.schedule(*this, 0, EVENT_CLOCK_PHI1);
}

void MOS656X::chip(model_t model)
{
    maxRasters    = modelData[model].rasterLines;
    cyclesPerLine = modelData[model].cyclesPerLine;
    clock         = modelData[model].clock;

    lp.setScreenSize(maxRasters, cyclesPerLine);

    reset();
}

uint8_t MOS656X::read(uint_least8_t addr)
{
    addr &= 0x3f;

    // Sync up timers
    sync();

    switch (addr)
    {
    case 0x11:
        // Control register 1
        return (regs[addr] & 0x7f) | ((rasterY & 0x100) >> 1);
    case 0x12:
        // Raster counter
        return rasterY & 0xFF;
    case 0x13:
        return lp.getX();
    case 0x14:
        return lp.getY();
    case 0x19:
        // Interrupt Pending Register
        return irqFlags | 0x70;
    case 0x1a:
        // Interrupt Mask Register
        return irqMask | 0xf0;
    default:
        // for addresses < $20 read from register directly, when < $2f set
        // bits of high nibble to 1, for >= $2f return $ff
        if (addr < 0x20)
            return regs[addr];
        if (addr < 0x2f)
            return regs[addr] | 0xf0;
        return 0xff;
    }
}

void MOS656X::write(uint_least8_t addr, uint8_t data)
{
    addr &= 0x3f;

    regs[addr] = data;

    // Sync up timers
    sync();

    switch (addr)
    {
    case 0x11: // Control register 1
    {
        const unsigned int oldYscroll = yscroll;
        yscroll = data & 0x7;

        // This is the funniest part... handle bad line tricks.
        const bool wasBadLinesEnabled = areBadLinesEnabled;

        if (rasterY == FIRST_DMA_LINE && lineCycle == 0)
        {
            areBadLinesEnabled = readDEN();
        }

        if (oldRasterY() == FIRST_DMA_LINE && readDEN())
        {
            areBadLinesEnabled = true;
        }

        if ((oldYscroll != yscroll || areBadLinesEnabled != wasBadLinesEnabled)
            && rasterY >= FIRST_DMA_LINE
            && rasterY <= LAST_DMA_LINE)
        {
            // Check whether bad line state has changed.
            const bool wasBadLine = (wasBadLinesEnabled && (oldYscroll == (rasterY & 7)));
            const bool nowBadLine = (areBadLinesEnabled && (yscroll == (rasterY & 7)));

            const bool oldBadLine = isBadLine;

            if (wasBadLine && !nowBadLine)
            {
                if (lineCycle < VICII_FETCH_CYCLE)
                {
                    isBadLine = false;
                }
            }
            else if (!wasBadLine && nowBadLine)
            {
                // Bad line may be generated during fetch interval
                //   (VICII_FETCH_CYCLE <= lineCycle < VICII_FETCH_CYCLE + VICII_SCREEN_TEXTCOLS + 3)
                // or outside the fetch interval but before raster ycounter is incremented
                //   (lineCycle <= VICII_FETCH_CYCLE + VICII_SCREEN_TEXTCOLS + 6)
                if (lineCycle <= VICII_FETCH_CYCLE + VICII_SCREEN_TEXTCOLS + 6)
                {
                    isBadLine = true;
                }
            }

            if (isBadLine != oldBadLine)
                event_context.schedule(badLineStateChangeEvent, 0, EVENT_CLOCK_PHI1);
        }
    }
        // fall-through

    case 0x12: // Raster counter
        // check raster Y irq condition changes at the next PHI1
        event_context.schedule(rasterYIRQEdgeDetectorEvent, 0, EVENT_CLOCK_PHI1);
        break;

    case 0x17:
        sprites.lineCrunch(data, lineCycle);
        break;

    case 0x19:
        // VIC Interrupt Flag Register
        irqFlags &= (~data & 0x0f) | 0x80;
        handleIrqState();
        break;

    case 0x1a:
        // IRQ Mask Register
        irqMask = data & 0x0f;
        handleIrqState();
        break;
    }
}

void MOS656X::handleIrqState()
{
    // signal an IRQ unless we already signaled it
    if ((irqFlags & irqMask & 0x0f) != 0)
    {
        if ((irqFlags & 0x80) == 0)
        {
            interrupt(true);
            irqFlags |= 0x80;
        }
    }
    else if ((irqFlags & 0x80) != 0)
    {
        interrupt(false);
        irqFlags &= 0x7f;
    }
}

void MOS656X::event()
{
    const event_clock_t cycles = event_context.getTime(rasterClk, event_context.phase());

    event_clock_t delay;

    if (cycles)
    {
        // Update x raster
        rasterClk += cycles;
        lineCycle += cycles;
        lineCycle %= cyclesPerLine;

        delay = (this->*clock)();
    }
    else
        delay = 1;

    event_context.schedule(*this, delay - event_context.phase(), EVENT_CLOCK_PHI1);
}

event_clock_t MOS656X::clockPAL()
{
    event_clock_t delay = 1;

    switch (lineCycle)
    {
    case 0:
        checkVblank();
        endDma<2>();
        break;

    case 1:
        vblank();
        startDma<5>();

        // No sprites before next compulsory cycle
        if (!sprites.isDma(0xf8))
           delay = 10;
        break;

    case 2:
        endDma<3>();
        break;

    case 3:
        startDma<6>();
        break;

    case 4:
        endDma<4>();
        break;

    case 5:
        startDma<7>();
        break;

    case 6:
        endDma<5>();

        delay = sprites.isDma(0xc0) ? 2 : 4;
        break;

    case 7:
        break;

    case 8:
        endDma<6>();

        delay = 2;
        break;

    case 9:
        break;

    case 10:
        endDma<7>();
        break;

    case 11:
        startBadline();

        delay = 3;
        break;

    case 12:
        delay = 2;
        break;

    case 13:
        break;

    case 14:
        sprites.updateMc();
        break;

    case 15:
        sprites.updateMcBase();

        delay = 39;
        break;

    case 54:
        sprites.checkDma(rasterY, regs);
        startDma<0>();
        break;

    case 55:
        sprites.checkDma(rasterY, regs);
        sprites.checkExp();
        startDma<0>();
        break;

    case 56:
        startDma<1>();
        break;

    case 57:
        sprites.checkDisplay();

        // No sprites before next compulsory cycle
        if (!sprites.isDma(0x1f))
            delay = 6;
        break;

    case 58:
        startDma<2>();
        break;

    case 59:
        endDma<0>();
        break;

    case 60:
        startDma<3>();
        break;

    case 61:
        endDma<1>();
        break;

    case 62:
        startDma<4>();
        break;

    default:
        delay = 54 - lineCycle;
    }

    return delay;
}

event_clock_t MOS656X::clockNTSC()
{
    event_clock_t delay = 1;

    switch (lineCycle)
    {
    case 0:
        checkVblank();
        startDma<5>();
        break;

    case 1:
        vblank();
        endDma<3>();

        // No sprites before next compulsory cycle
        if (!sprites.isDma(0xf8))
            delay = 10;
        break;

    case 2:
        startDma<6>();
        break;

    case 3:
        endDma<4>();
        break;

    case 4:
        startDma<7>();
        break;

    case 5:
        endDma<5>();

        delay = sprites.isDma(0xc0) ? 2 : 4;
        break;

    case 6:
        break;

    case 7:
        endDma<6>();

        delay = 2;
        break;

    case 8:
        break;

    case 9:
        endDma<7>();

        delay = 2;
        break;

    case 10:
        break;

    case 11:
        startBadline();

        delay = 3;
        break;

    case 12:
        delay = 2;
        break;

    case 13:
        break;

    case 14:
        sprites.updateMc();
        break;

    case 15:
        sprites.updateMcBase();

        delay = 40;
        break;

    case 55:
        sprites.checkDma(rasterY, regs);
        sprites.checkExp();
        startDma<0>();
        break;

    case 56:
        sprites.checkDma(rasterY, regs);
        startDma<0>();
        break;

    case 57:
        startDma<1>();
        break;

    case 58:
        sprites.checkDisplay();

        // No sprites before next compulsory cycle
        if (!sprites.isDma(0x1f))
            delay = 7;
        break;

    case 59:
        startDma<2>();
        break;

    case 60:
        endDma<0>();
        break;

    case 61:
        startDma<3>();
        break;

    case 62:
        endDma<1>();
        break;

    case 63:
        startDma<4>();
        break;

    case 64:
        endDma<2>();
        break;

    default:
        delay = 55 - lineCycle;
    }

    return delay;
}

event_clock_t MOS656X::clockOldNTSC()
{
    event_clock_t delay = 1;

    switch (lineCycle)
    {
    case 0:
        checkVblank();
        endDma<2>();
        break;

    case 1:
        vblank();
        startDma<5>();

        // No sprites before next compulsory cycle
        if (!sprites.isDma(0xf8))
           delay = 10;
        break;

    case 2:
        endDma<3>();
        break;

    case 3:
        startDma<6>();
        break;

    case 4:
        endDma<4>();
        break;

    case 5:
        startDma<7>();
        break;

    case 6:
        endDma<5>();

        delay = sprites.isDma(0xc0) ? 2 : 4;
        break;

    case 7:
        break;

    case 8:
        endDma<6>();

        delay = 2;
        break;

    case 9:
        break;

    case 10:
        endDma<7>();
        break;

    case 11:
        startBadline();

        delay = 3;
        break;

    case 12:
        delay = 2;
        break;

    case 13:
        break;

    case 14:
        sprites.updateMc();
        break;

    case 15:
        sprites.updateMcBase();

        delay = 40;
        break;

    case 55:
        sprites.checkDma(rasterY, regs);
        sprites.checkExp();
        startDma<0>();
        break;

    case 56:
        sprites.checkDma(rasterY, regs);
        startDma<0>();
        break;

    case 57:
        sprites.checkDisplay();
        startDma<1>();

        delay = (!sprites.isDma(0x1f)) ? 7 : 2;
        break;

    case 58:
        break;

    case 59:
        startDma<2>();
        break;

    case 60:
        endDma<0>();
        break;

    case 61:
        startDma<3>();
        break;

    case 62:
        endDma<1>();
        break;

    case 63:
        startDma<4>();
        break;

    default:
        delay = 55 - lineCycle;
    }

    return delay;
}

void MOS656X::triggerLightpen()
{
    // Synchronise simulation
    sync();

    lpAsserted = true;

    if (lp.trigger(lineCycle, rasterY))
    {
        activateIRQFlag(IRQ_LIGHTPEN);
    }
}

void MOS656X::clearLightpen()
{
    lpAsserted = false;
}
