/*
 * Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
 * Copyright (C) 2016 Furrtek
 *
 * This file is part of PortaPack.
 *
 * 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, 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; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street,
 * Boston, MA 02110-1301, USA.
 */

#include <ch.h>

#include "demofont.hpp"
#include "ymdata.hpp"

#include "cpld_update.hpp"
#include "portapack.hpp"
#include "audio.hpp"
#include "event_m0.hpp"

#include "ui_about.hpp"
#include "touch.hpp"
#include "sine_table.hpp"

#include "portapack_shared_memory.hpp"
#include "portapack_persistent_memory.hpp"
#include "lpc43xx_cpp.hpp"

#include <math.h>
#include <cstring>

using namespace lpc43xx;
using namespace portapack;

namespace ui {

void AboutView::on_show() {
    transmitter_model.set_target_frequency(1337000000);  // TODO: Change
    transmitter_model.set_baseband_configuration({
        .mode = 0,
        .sampling_rate = 1536000,
        .decimation_factor = 1,
    });
    transmitter_model.set_rf_amp(true);
    transmitter_model.set_lna(40);
    transmitter_model.set_vga(40);
    transmitter_model.enable();

    baseband::set_audiotx_data(32, 50, false, 0);

    // audio::headphone::set_volume(volume_t::decibel(0 - 99) + audio::headphone::volume_range().max);
}

void AboutView::render_video() {
    uint8_t p, r, luma, chroma, cy;
    ui::Color cc;
    char ch;
    float s;

    // Send framebuffer to LCD. Gotta go fast !
    display.render_box({30, 112}, {180, 72}, framebuffer);

    // Clear framebuffer to black
    memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));

    // Drum hit palette animation
    if (drum > 1) drum--;

    // Render copper bars from Y buffer
    for (p = 0; p < 72; p++) {
        luma = copperbuffer[p] & 0x0F;  // 0 is transparent
        if (luma) {
            chroma = copperbuffer[p] >> 4;
            cc = ui::Color(std::min((coppercolor[chroma][0] / luma) * drum, 255), std::min((coppercolor[chroma][1] / luma) * drum, 255), std::min((coppercolor[chroma][2] / luma) * drum, 255));
            for (r = 0; r < 180; r++)
                framebuffer[(p * 180) + r] = cc;
        }
    }

    // Scroll in/out state machine
    if (anim_state == 0) {
        // Scroll in
        if (ofy < 8) {
            ofy++;
            anim_state = 0;
        } else {
            anim_state = 1;
        }
        if (ofx < (int16_t)(180 - (strlen(credits[credits_index].name) * 16) - 8)) {
            ofx += 8;
            anim_state = 0;
        }
    } else if (anim_state == 1) {
        // Just wait
        if (credits_timer == (30 * 3)) {
            credits_timer = 0;
            anim_state = 2;
        } else {
            credits_timer++;
        }
    } else {
        // Scroll out
        if (credits[credits_index].change == true) {
            if (ofy > -24) {
                ofy--;
                anim_state = 2;
            } else {
                anim_state = 0;
            }
        } else {
            anim_state = 0;
        }
        if (ofx < 180) {
            ofx += 8;
            anim_state = 2;
        }

        // Switch to next text
        if (anim_state == 0) {
            if (credits_index == 9)
                credits_index = 0;
            else
                credits_index++;
            ofx = -(strlen(credits[credits_index].name) * 16) - 16;
        }
    }

    // Sine text ("role")
    p = 0;
    while ((ch = credits[credits_index].role[p])) {
        draw_demoglyph({(ui::Coord)(8 + (p * 16)), (ui::Coord)(ofy + (sine_table_f32[((p * 16) + (phase >> 5)) & 0xFF] * 8))}, ch, paletteA);
        p++;
    }

    // Scroll text (name)
    p = 0;
    while ((ch = credits[credits_index].name[p])) {
        draw_demoglyph({(ui::Coord)(ofx + (p * 16)), 56}, ch, paletteB);
        p++;
    }

    // Clear bars Y buffer
    memset(copperbuffer, 0, 72);

    // Render bars to Y buffer
    for (p = 0; p < 5; p++) {
        cy = copperbars[p];
        for (r = 0; r < 16; r++)
            copperbuffer[cy + r] = copperluma[r] + (p << 4);
    }

    // Animate bars positions
    for (p = 0; p < 5; p++) {
        s = sine_table_f32[((p * 32) + (phase / 24)) & 0xFF];
        s += sine_table_f32[((p * 16) + (phase / 35)) & 0xFF];
        copperbars[p] = 28 + (uint8_t)(s * 14);
    }

    phase += 128;
}

void AboutView::draw_demoglyph(ui::Point p, char ch, ui::Color* pal) {
    uint8_t x, y, c, cl, cr;
    uint16_t che;
    int16_t lbx, il;

    // Map ASCII to font bitmap
    if ((ch >= 32) && (ch < 96))
        che = char_map[ch - 32];
    else
        che = 0xFF;

    if (che < 0xFF) {
        che = (che * 128) + 48;  // Start in bitmap

        il = (180 * p.y) + p.x;  // Start il framebuffer

        for (y = 0; y < 16; y++) {
            if (p.y + y >= 72) break;  // Over bottom of framebuffer, abort
            if (p.y + y >= 0) {
                for (x = 0; x < 8; x++) {
                    c = demofont_bin[x + (y * 8) + che];  // Split byte in 2 4BPP pixels
                    cl = c >> 4;
                    cr = c & 0x0F;
                    lbx = p.x + (x * 2);
                    if (cl && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cl];
                    lbx++;
                    il++;
                    if (cr && (lbx < 180) && (lbx >= 0)) framebuffer[il] = pal[cr];
                    il++;
                }
                il += 180 - 16;
            } else {
                il += 180;
            }
        }
    }
}

void AboutView::render_audio() {
    uint8_t i, ymdata;
    uint16_t ym_render_cnt;

    // This is heavily inspired by MAME's ay8910.cpp and the YM2149's datasheet

    // Render 1024 music samples
    for (ym_render_cnt = 0; ym_render_cnt < 1024; ym_render_cnt++) {
        // Update registers at 48000/960 = 50Hz
        if (ym_sample_cnt == 0) {
            // "Decompress" on the fly and update YM registers
            for (i = 0; i < 14; i++) {
                if (!ym_regs[i].cnt) {
                    // New run
                    ymdata = ymdata_bin[ym_regs[i].ptr++];
                    ym_regs[i].cnt = ymdata & 0x7F;
                    if (ymdata & 0x80) {
                        ym_regs[i].same = true;
                        ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
                    } else {
                        ym_regs[i].same = false;
                    }
                    // Detect drum on channel B
                    if (i == 3)
                        if (ym_regs[3].value > 2) drum = 4;
                }
                if (ym_regs[i].same == false) {
                    ym_regs[i].value = ymdata_bin[ym_regs[i].ptr++];
                    if (i == 13) {
                        // Update envelope attributes
                        ym_env_att = (ym_regs[13].value & 4) ? 0x1F : 0x00;
                        if (!(ym_regs[13].value & 8)) {
                            ym_env_hold = 1;
                            ym_env_alt = ym_env_att;
                        } else {
                            ym_env_hold = ym_regs[13].value & 1;
                            ym_env_alt = ym_regs[13].value & 2;
                        }
                        // Reset envelope counter
                        ym_env_step = 0x1F;
                        ym_env_holding = 0;
                        ym_env_vol = (ym_env_step ^ ym_env_att);
                    }
                }
                ym_regs[i].cnt--;
            }
            ym_frame++;
        }

        // Square wave oscillators
        // 2457600/16/48000 = 3.2, but 4 sounds better than 3...
        for (i = 0; i < 3; i++) {
            ym_osc_cnt[i] += 4;
            if (ym_osc_cnt[i] >= (ym_regs[i * 2].value | ((ym_regs[(i * 2) + 1].value & 0x0f) << 8))) {
                ym_osc_cnt[i] = 0;
                ym_osc_out[i] ^= 1;
            }
        }

        // Noise generator
        ym_noise_cnt += 4;
        if (ym_noise_cnt >= ((ym_regs[6].value & 0x1F) * 2)) {
            ym_noise_cnt = 0;
            ym_rng ^= (((ym_rng & 1) ^ ((ym_rng >> 3) & 1)) << 17);
            ym_rng >>= 1;
        }

        // Mix tones and noise
        for (i = 0; i < 3; i++)
            ym_ch[i] = (ym_osc_out[i] | ((ym_regs[7].value >> i) & 1)) & ((ym_rng & 1) | ((ym_regs[7].value >> (i + 3)) & 1));

        // Envelope generator
        if (!ym_env_holding) {
            ym_env_cnt += 8;
            if (ym_env_cnt >= (ym_regs[11].value | (ym_regs[12].value << 8))) {
                ym_env_cnt = 0;
                ym_env_step--;
                if (ym_env_step < 0) {
                    if (ym_env_hold) {
                        if (ym_env_alt)
                            ym_env_att ^= 0x1F;
                        ym_env_holding = 1;
                        ym_env_step = 0;
                    } else {
                        if (ym_env_alt && (ym_env_step & 0x20))
                            ym_env_att ^= 0x1F;
                        ym_env_step &= 0x1F;
                    }
                }
            }
        }
        ym_env_vol = (ym_env_step ^ ym_env_att);

        ym_out = 0;
        for (i = 0; i < 3; i++) {
            if (ym_regs[i + 8].value & 0x10) {
                // Envelope mode
                ym_out += (ym_ch[i] ? ym_env_vol : 0);
            } else {
                // Fixed mode
                ym_out += (ym_ch[i] ? (ym_regs[i + 8].value & 0x0F) : 0);
            }
        }

        ym_buffer[ym_render_cnt] = (ym_out * 2) - 45;

        if (ym_sample_cnt < 960) {
            ym_sample_cnt++;
        } else {
            ym_sample_cnt = 0;
        }

        // Loop
        if (ym_frame == ym_frames) ym_init();
    }
}

void AboutView::update() {
    if (framebuffer) {
        // Update 1 out of 2 frames, 60Hz is very laggy
        if (refresh_cnt & 1) render_video();
        refresh_cnt++;
    }

    // Slowly increase volume to avoid jumpscare
    if (headphone_vol < (70 << 2)) {
        audio::headphone::set_volume(volume_t::decibel((headphone_vol / 4) - 99) + audio::headphone::volume_range().max);
        headphone_vol++;
    }
}

void AboutView::ym_init() {
    uint8_t reg;

    for (reg = 0; reg < 14; reg++) {
        ym_regs[reg].cnt = 0;
        // Pick up start pointers for each YM registers RLE blocks
        ym_regs[reg].ptr = ((uint16_t)(ymdata_bin[(reg * 2) + 3]) << 8) + ymdata_bin[(reg * 2) + 2];
        ym_regs[reg].same = false;  // Useless ?
        ym_regs[reg].value = 0;     // Useless ?
    }

    ym_frame = 0;
}

AboutView::AboutView(
    NavigationView& nav) {
    uint8_t p, c;

    baseband::run_image(portapack::spi_flash::image_tag_audio_tx);

    add_children({{
        &text_title,
        &text_firmware,
        &text_cpld_hackrf,
        &text_cpld_hackrf_status,
        &button_ok,
    }});

    if (cpld_hackrf_verify_eeprom()) {
        text_cpld_hackrf_status.set(" OK");
    } else {
        text_cpld_hackrf_status.set("BAD");
    }

    // Politely ask for about 26kB
    framebuffer = (ui::Color*)chHeapAlloc(0x0, 180 * 72 * sizeof(ui::Color));

    if (framebuffer) {
        memset(framebuffer, 0, 180 * 72 * sizeof(ui::Color));

        // Copy original font palette
        c = 0;
        for (p = 0; p < 48; p += 3)
            paletteA[c++] = ui::Color(demofont_bin[p], demofont_bin[p + 1], demofont_bin[p + 2]);

        // Increase red in another one
        c = 0;
        for (p = 0; p < 48; p += 3)
            paletteB[c++] = ui::Color(std::min(demofont_bin[p] + 64, 255), demofont_bin[p + 1], demofont_bin[p + 2]);
    }

    // Init YM synth
    ym_frames = ((uint16_t)(ymdata_bin[1]) << 8) + ymdata_bin[0];
    ym_init();

    button_ok.on_select = [this, &nav](Button&) {
        if (framebuffer) chHeapFree(framebuffer);  // Do NOT forget this
        nav.pop();
    };
}

AboutView::~AboutView() {
    transmitter_model.disable();
    baseband::shutdown();
}

void AboutView::focus() {
    button_ok.focus();
}

} /* namespace ui */