#include "pch.h"
#include "abr.h"
#include "log.h"
#include "asset.h"

bool ABR::section_desc()
{
    auto sz = align4(ru32());
    auto n1 = ri32();             // some integer
    auto s = rwstring();          // maybe a string
    auto null = rkey_or_string();
    auto null_val = ri32();       // integer following the null

    // presets list
    auto name = rkey_or_string(); // "Brsh"
    auto list = rstring(4);       // "VlLs"
    if (auto presets = std::dynamic_pointer_cast<List>(instanciate(list)))
    {
        presets->read(*this);
        for (auto const& pr : presets->items)
        {
            if (auto desc = std::dynamic_pointer_cast<Descriptor>(pr))
                m_presets.push_back(desc);
        }
#ifdef _DEBUG
        auto out = presets->str(0, "");
        auto lines = split(out, '\n');
        for (const auto& l : lines)
        {
            LOG("%s", l.c_str());
        }
#endif // _DEBUG
    }
    snap();
    return true;
}

bool ABR::section_samp()
{
    auto section_size = ru32();
    auto start = pos();
    auto end = start + section_size;
    while (pos() < end)
    {
        auto samp_size = ru32();
        auto uid = rpascal();
        //printf("sample brush %s\n", uid.c_str());
        skip(4); // unknown bytes usually 00 01 00 00
        auto vm = std::make_shared<VMArray>();
        if (vm->read(*this))
        {
            auto img_data = vm->image(true, true);
            if (img_data.data)
            {
                // TODO: check if uid already exists in map
                m_samples[uid] = std::make_shared<Image>(img_data);
            }
        }
    }
    return true;
}

bool ABR::section_patt()
{
    auto section_size = ru32();
    auto start = pos();
    auto end = start + section_size;
    while (pos() < end)
    {
        auto patt_length = ru32(); // length of this pattern
        auto patt_version = ru32(); // = 1
        // Bitmap = 0; Grayscale = 1; Indexed = 2; RGB = 3;
        // CMYK = 4; Multichannel = 7; Duotone = 8; Lab = 9
        auto image_mode = ru32();
        if (!(image_mode == 1 || image_mode == 3))
        {
            LOG("PATT: skip image mode  %d\n", image_mode);
            skip(patt_length - 8);
            snap();
            continue;
        }
        Point point;
        point.read(*this);
        auto name = rwstring();
        auto uid = rpascal();

        // Index color table (256 * 3 RGB values): 
        // only present when image mode is indexed color
        // read(...);
        // no worries indexed is skipped anyway

        auto vm = std::make_shared<VMArray>();
        if (vm->read(*this))
        {
            int nc = std::min((int)vm->channels.size(), 3);
            if (nc != image_mode)
            {
                LOG("PATT: image_mode (%d) and number of channels (%ld) not matching\n",
                    image_mode, vm->channels.size());
            }
            auto img_data = vm->image(true, false);
            if (img_data.data)
            {
                // TODO: check if uid already exists in map
                m_patterns[uid] = std::make_shared<Image>(img_data);
            }
        }
    }
    return true;
}

std::vector<std::shared_ptr<Brush>> ABR::compute_brushes(const std::string& path)
{
    std::vector<std::shared_ptr<Brush>> ret;
    for (auto const& p : m_presets)
    {
        auto samp = p->get<Descriptor>("Brsh");
        if (samp->class_id != "sampledBrush" && samp->class_id != "computedBrush")
        {
            LOG("unsupported brush type %s", samp->class_id.c_str());
            continue;
        }
        auto b = std::make_shared<Brush>();
        b->m_name = wstr2str(p->value<String>("Nm  "));

        // default values
        b->m_tip_color = { 0, 0, 0, 1 };
        b->m_tip_flow = .90f;
        b->m_tip_opacity = 1.f;

        b->m_tip_wet = p->value<UnitFloat>("Wtdg");
        b->m_tip_noise = p->value<Boolean>("Nose") ? 1.f : 0.f;

        b->m_tip_aspect = (1.f - samp->value<UnitFloat>("Rndn") * 0.01) * 0.5f + 0.5f;
        b->m_tip_size = samp->value<UnitFloat>("Dmtr");
        b->m_tip_spacing = samp->value<UnitFloat>("Spcn") * 0.01f;

        float tip_angle = -samp->value<UnitFloat>("Angl") / 360.f;
        b->m_tip_angle = tip_angle >= 0.f ? tip_angle : tip_angle + 1.f; // [-180, 180] -> [0, 1]

        b->m_tip_flipx = samp->value<Boolean>("flipX");
        b->m_tip_flipy = samp->value<Boolean>("flipY");

        // brush sample
        if (samp->class_id == "sampledBrush")
        {
            auto tip_uid = wstr2str(samp->value<String>("sampledData"));
            b->m_brush_path = path + "/brushes/" + tip_uid + ".png";
            b->m_brush_thumb_path = path + "/brushes/thumbs/" + tip_uid + ".png";

            const auto& samp_img = m_samples[tip_uid];
            if (samp_img->height > samp_img->width)
                b->m_tip_scale = { (float)samp_img->width / (float)samp_img->height, 1.f };
            else
                b->m_tip_scale = { 1.f, (float)samp_img->height / (float)samp_img->width };
        }
        else if (samp->class_id == "computedBrush")
        {
            if (samp->value<UnitFloat>("Hrdn") > 50.f)
            {
                b->m_brush_path = "data/brushes/Round-Hard.png";
                b->m_brush_thumb_path = "data/brushes/thumbs/Round-Hard.png";
            }
            else
            {
                b->m_brush_path = "data/brushes/Round-Brush.png";
                b->m_brush_thumb_path = "data/brushes/thumbs/Round-Brush.png";
            }
        }

        // Shape dynamics
        if (p->value<Boolean>("useTipDynamics"))
        {
            // other properties 'brushProjection', 'minimumRoundness', 'tiltScale'

            if (auto jitter_size = p->get<Descriptor>("szVr"))
            {
                b->m_jitter_scale = jitter_size->value<UnitFloat>("jitter") * 0.01f;
                // TODO: p->value<UnitFloat>("minimumDiameter") * 0.001f; // minimum size
                if (jitter_size->value<Integer>("bVTy") == 2)
                    b->m_tip_size_pressure = true;
            }

            if (auto jitter_angle = p->get<Descriptor>("angleDynamics"))
            {
                auto mode = jitter_angle->value<Integer>("bVTy");
                if (mode == 0)
                {
                    b->m_jitter_angle = jitter_angle->value<UnitFloat>("jitter") * 0.01f;
                }
                else if (mode == 5)
                {
                    b->m_jitter_angle = jitter_angle->value<UnitFloat>("jitter") * 0.01f;
                    b->m_tip_angle_init = true;
                }
                else if (mode == 6)
                {
                    b->m_jitter_angle = jitter_angle->value<UnitFloat>("jitter") * 0.01f;
                    b->m_tip_angle_follow = true;
                }
            }

            if (auto roundness = p->get<Descriptor>("roundnessDynamics"))
            {
                b->m_jitter_aspect = roundness->value<UnitFloat>("jitter") * 0.01;
            }

            b->m_tip_randflipx = p->value<Boolean>("flipX");
            b->m_tip_randflipy = p->value<Boolean>("flipY");
        }

        // Transfer settings
        if (p->value<Boolean>("usePaintDynamics"))
        {
            auto jitter_opacity = p->get<Descriptor>("opVr");
            if (jitter_opacity)
            {
                b->m_jitter_opacity = jitter_opacity->value<UnitFloat>("jitter") * 0.01f;
                // TODO: jitter_opacity->value<UnitFloat>("Mnm ") * 0.01f; // minimum size
                if (jitter_opacity->value<Integer>("bVTy") == 2)
                    b->m_tip_opacity_pressure = true;
            }
            
            auto jitter_flow = p->get<Descriptor>("prVr");
            if (jitter_flow)
            {
                b->m_jitter_flow = jitter_flow->value<UnitFloat>("jitter") * 0.01f;
                // TODO: m_jitter_flow->value<UnitFloat>("Mnm ") * 0.01f; // minimum size
                if (jitter_flow->value<Integer>("bVTy") == 2)
                    b->m_tip_flow_pressure = true;
            }
            
        }

        // Color Dynamics
        if (p->value<Boolean>("useColorDynamics"))
        {
            b->m_jitter_sat = p->value<UnitFloat>("Strt") * 0.01f;
            b->m_jitter_hue = p->value<UnitFloat>("H   ") * 0.01f;
            b->m_jitter_val = p->value<UnitFloat>("Brgh") * 0.01f;
            b->m_jitter_hsv_eachsample = p->value<Boolean>("colorDynamicsPerTip");
        }

        std::vector<std::string> modes = {
            "normal",       // normal (not in Photoshop)
            "Mltp",         // multiply
            "Sbtr",         // subtract
            "Drkn",         // darken
            "Ovrl",         // overlay
            "CDdg",         // color dodge
            "CBrn",         // color burn
            "linearBurn",   // linear burn
            "hardMix",      // hard mix
            "linearHeight", // linear height
            "Hght",         // height
        };

        // pattern
        if (auto patt = p->get<Descriptor>("Txtr"))
        {
            auto patt_uid = wstr2str(patt->value<String>("Idnt"));
            b->m_pattern_path = path + "/patterns/" + patt_uid + ".png";
            b->m_pattern_thumb_path = path + "/patterns/thumbs/" + patt_uid + ".png";
            
            b->m_pattern_invert = p->value<Boolean>("InvT");
            b->m_pattern_eachsample = p->value<Boolean>("TxtC");
            b->m_pattern_depth = p->value<UnitFloat>("textureDepth") * 0.01f; // [0, 100] -> [0, 1]
            b->m_pattern_scale = p->value<UnitFloat>("textureScale") * 0.01f; // [0, 1000] -> [0, 1]
            b->m_pattern_brightness = (float)(p->value<Integer>("textureBrightness") + 150) / 300.f; // [-150, 150] -> [0, 1]

            int raw_contrast = p->value<Integer>("textureContrast"); // [-50, 0, 100] -> [0, 1]
            b->m_pattern_contrast = raw_contrast / (raw_contrast < 0 ? 100.f : 200.f) + 0.5f;

            // blending mode
            std::string blend_mode = p->value<Enum>("textureBlendMode");
            auto bm_it = std::find(modes.begin(), modes.end(), blend_mode);
            if (bm_it != modes.end())
                b->m_pattern_blend_mode = (int)std::distance(modes.begin(), bm_it);

            b->m_pattern_enabled = p->value<Boolean>("useTexture");
        }

        // dual brush
        auto db = p->get<Descriptor>("dualBrush");
        if (db && db->value<Boolean>("useDualBrush"))
        {
            auto samp = db->get<Descriptor>("Brsh");
            if (samp->class_id != "sampledBrush" && samp->class_id != "computedBrush")
            {
                LOG("unsupported brush type %s", samp->class_id.c_str());
                continue;
            }
            //b->m_name = wstr2str(p->value<String>("Nm  "));

            // default values
            //b->m_tip_color = { 0, 0, 0, 1 };
            b->m_dual_flow = .90f;
            b->m_dual_opacity = 1.f;

            b->m_dual_aspect = (1.f - samp->value<UnitFloat>("Rndn") * 0.01) * 0.5f + 0.5f;
            b->m_dual_size = samp->value<UnitFloat>("Dmtr") / b->m_tip_size;
            b->m_dual_spacing = samp->value<UnitFloat>("Spcn") * 0.01f;

            float tip_angle = -samp->value<UnitFloat>("Angl") / 360.f;
            b->m_dual_angle = tip_angle >= 0.f ? tip_angle : tip_angle + 1.f; // [-180, 180] -> [0, 1]
            
            //b->m_tip_wet = p->value<UnitFloat>("Wtdg");
            //b->m_tip_noise = (float)samp->value<UnitFloat>("Nose");
            b->m_tip_flipx = samp->value<Boolean>("flipX");
            b->m_tip_flipy = samp->value<Boolean>("flipY");
            
            b->m_dual_randflip = db->value<Boolean>("Flip");
            b->m_dual_scatter_bothaxis = db->value<Boolean>("bothAxes");

            if (db->value<Boolean>("useScatter"))
            {
                auto scatter = db->get<Descriptor>("scatterDynamics");
                b->m_dual_scatter = scatter->value<UnitFloat>("jitter") * 0.01f;
            }

            // brush sample
            if (samp->class_id == "sampledBrush")
            {
                auto tip_uid = wstr2str(samp->value<String>("sampledData"));
                b->m_dual_path = path + "/brushes/" + tip_uid + ".png";
                b->m_dual_thumb_path = path + "/brushes/thumbs/" + tip_uid + ".png";

                const auto& samp_img = m_samples[tip_uid];
                //b->m_tip_width = (float)samp_img->width / (float)samp_img->height;
            }
            else if (samp->class_id == "computedBrush")
            {
                if (samp->value<UnitFloat>("Hrdn") > 50.f)
                {
                    b->m_dual_path = "data/brushes/Round-Hard.png";
                    b->m_dual_thumb_path = "data/brushes/thumbs/Round-Hard.png";
                }
                else
                {
                    b->m_dual_path = "data/brushes/Round-Brush.png";
                    b->m_dual_thumb_path = "data/brushes/thumbs/Round-Brush.png";
                }
            }

            // blending mode
            std::string blend_mode = db->value<Enum>("BlnM");
            auto bm_it = std::find(modes.begin(), modes.end(), blend_mode);
            if (bm_it != modes.end())
                b->m_dual_blend_mode = (int)std::distance(modes.begin(), bm_it);

            b->m_dual_enabled = db->value<Boolean>("useDualBrush");
        }

        ret.push_back(b);
    }
    return ret;
}

bool ABR::open(const std::string& path)
{
    Asset asset;
    if (asset.open(path.c_str()))
    {
        init(asset.read_all(), asset.m_len, BinaryStreamReader::ByteOrder::BigEndian);
        auto version_major = ru16();
        auto version_minor = ru16();
        LOG("ABR %d.%d\n", version_major, version_minor);
        while (!eof())
        {
            if (rstring(4) != "8BIM")
                return false;
            auto t = rstring(4);
            if (t == "desc")
            {
                section_desc();
            }
            else if (t == "patt")
            {
                section_patt();
            }
            else if (t == "samp")
            {
                section_samp();
            }
            else
            {
                LOG("skip section %s\n", t.c_str());
                skip(align4(ru32()));
            }
        }
        return true;
    }
    return false;
}
