<?php

function dump(string $string)
{
    echo $string . PHP_EOL;
}

function write_data(array $data, string $filename)
{
    \file_put_contents($filename, \json_encode($data));
}

function array_clean(array $array)
{
    $output = \array_unique($array);
    \sort($output);
    return $output;
}

function ask(string $prompt)
{
    dump($prompt);
    $input = \rtrim(\fgets(STDIN));
    return $input;
}

function find_missing_associations(array $mappingItems, array $categories, array $items)
{
    $missing = [];
    foreach ($items as $item) {
        $set = \array_merge(
            $mappingItems[$item]['is'],
            $mappingItems[$item]['isnot'],
            $mappingItems[$item]['na'],
        );
        foreach ($categories as $category) {
            if (!\in_array($category, $set)) {
                $missing[] = [
                    'item' => $item,
                    'category' => $category,
                ];
            }
        }
    }
    return $missing;
}

function find_exclusions(array $exclusions, string $searchedCategory)
{
    $output = [];
    foreach ($exclusions as $exclusionSet) {
        if (\is_array($exclusionSet) && \in_array($searchedCategory, $exclusionSet)) {
            foreach ($exclusionSet as $candidate) {
                if ($candidate !== $searchedCategory) {
                    $output[] = $candidate;
                }
            }
        }
    }

    return $output;
}

function clean_item_mappings(array $itemMappings)
{
    $itemMappings['is'] = array_clean($itemMappings['is']);
    $itemMappings['isnot'] = array_clean($itemMappings['isnot']);
    $itemMappings['na'] = array_clean($itemMappings['na']);

    // remove duplicates
    $itemMappings['is'] = array_clean($itemMappings['is']);

    $tmpArray = [];
    foreach (array_clean($itemMappings['isnot']) as $itemIsNot) {
        if (!\in_array($itemIsNot, $itemMappings['is'])) {
            $tmpArray[] = $itemIsNot;
        }
    }
    $itemMappings['isnot'] = $tmpArray;

    $tmpArray = [];
    foreach (array_clean($itemMappings['na']) as $itemNa) {
        if (!\in_array($itemNa, $itemMappings['is']) && !\in_array($itemNa, $itemMappings['isnot'])) {
            $tmpArray[] = $itemNa;
        }
    }
    $itemMappings['na'] = $tmpArray;

    return $itemMappings;
}

if ($argc != 2) {
    dump('Need data json file as parameter.');
    die;
}

$jsonDataFile = $argv[1];

$data = [];
if (is_file($jsonDataFile)) {
    $data = \json_decode(\file_get_contents($jsonDataFile), true);
}

if (!is_writable($jsonDataFile)) {
    dump('Output data json file is not writable.');
    die;
}

$categories = (\array_key_exists('categories', $data) && \is_array($data['categories'])) ? $data['categories'] : [];
$items = (\array_key_exists('items', $data) && \is_array($data['items'])) ? $data['items'] : [];

// Manage categories exclusions
$exclusions = (\array_key_exists('exclusions', $data) && \is_array($data['exclusions'])) ? $data['exclusions'] : [];

// Merge categories with categories found in exclusions
foreach ($exclusions as $exclusionSet) {
    foreach ($exclusionSet as $category) {
        $categories[] = $category;
    }
}
$categories = array_clean($categories);
$items = array_clean($items);

$themes = (\array_key_exists('themes', $data) && \is_array($data['themes'])) ? $data['themes'] : [];
$resources = (\array_key_exists('resources', $data) && \is_array($data['resources'])) ? $data['resources'] : [];

$data['categories'] = $categories;
$data['items'] = $items;
$data['exclusions'] = $exclusions;
$data['themes'] = $themes;
$data['resources'] = $resources;

dump('');
dump('Found ' . \count($categories) . ' unique categories.');
dump('Found ' . \count($items) . ' unique items.');
dump('Found ' . \count($exclusions) . ' exclusions sets.');
dump('Found ' . \count($themes) . ' themes.');

// Get/init mapping data
$mapping = (\array_key_exists('mapping', $data) && \is_array($data['mapping'])) ? $data['mapping'] : [];
$mappingItems = (\array_key_exists('items', $mapping) && \is_array($mapping['items'])) ? $mapping['items'] : [];

foreach ($items as $item) {
    if (!\array_key_exists($item, $mappingItems)) {
        $mappingItems[$item] = [];
    }
    if (!\array_key_exists('is', $mappingItems[$item]) || !\is_array($mappingItems[$item])) {
        $mappingItems[$item]['is'] = [];
    }
    if (!\array_key_exists('isnot', $mappingItems[$item]) || !\is_array($mappingItems[$item])) {
        $mappingItems[$item]['isnot'] = [];
    }
    if (!\array_key_exists('na', $mappingItems[$item]) || !\is_array($mappingItems[$item])) {
        $mappingItems[$item]['na'] = [];
    }

    $mappingItems[$item] = clean_item_mappings($mappingItems[$item]);
}

// TODO: Should check/add unkown items from current mapping
\ksort($mappingItems);
$data['mapping'] = [
    'items' => $mappingItems,
];

function showCategories($categories)
{
    dump(\join("\n", $categories));
}

function showItems($items)
{
    dump(\join("\n", $items));
}

function showExclusions($exclusions)
{
    foreach ($exclusions as $exclusionSet) {
        dump(\join(', ', $exclusionSet));
    }
}

function showThemes($theme)
{
    foreach ($theme as $name => $categories) {
        dump($name . ': ' . \join(', ', $categories));
    }
}

function showMappings($mappingItems)
{
    $columnsWidths = [
        'items' => 0,
    ];

    $items = \array_keys($mappingItems);
    $categories = [];
    foreach ($mappingItems as $item => $mapping) {
        if ($columnsWidths['items'] < \mb_strlen($item)) {
            $columnsWidths['items'] = \mb_strlen($item);
        }

        foreach (\array_merge($mapping['is'], $mapping['isnot'], $mapping['na']) as $category) {
            if (!\in_array($category, $categories)) {
                $categories[] = $category;
                if (!\array_key_exists($category, $columnsWidths)) {
                    $columnsWidths[$category] = 0;
                }
                if ($columnsWidths[$category] < \mb_strlen($category)) {
                    $columnsWidths[$category] = \mb_strlen($category);
                }
            }
        }
    }

    $strIs = '✅';
    $strIsNot = '❌';
    $strNa = '⛔';

    // separator
    $line = [
        \mb_str_pad('', $columnsWidths['items'], '-', STR_PAD_BOTH),
    ];
    foreach ($categories as $category) {
        $line[] = \mb_str_pad('', $columnsWidths[$category], '-', STR_PAD_BOTH);
    }
    dump('--' . \join('---', $line) . '--');

    // header
    $line = [
        \mb_str_pad('', $columnsWidths['items'], ' ', STR_PAD_BOTH),
    ];
    foreach ($categories as $category) {
        $line[] = \mb_str_pad($category, $columnsWidths[$category], ' ', STR_PAD_BOTH);
    }
    dump('| ' . \join(' | ', $line) . ' |');

    // separator
    $line = [
        \mb_str_pad('', $columnsWidths['items'], '-', STR_PAD_BOTH),
    ];
    foreach ($categories as $category) {
        $line[] = \mb_str_pad('', $columnsWidths[$category], '-', STR_PAD_BOTH);
    }
    dump('|-' . \join('-|-', $line) . '-|');

    foreach ($items as $item) {
        $line = [
            \mb_str_pad($item, $columnsWidths['items'], ' ', STR_PAD_RIGHT),
        ];
        foreach ($categories as $category) {
            $value = '';
            if (\in_array($category, $mappingItems[$item]['is'])) {
                $value = $strIs;
            } elseif (\in_array($category, $mappingItems[$item]['isnot'])) {
                $value = $strIsNot;
            } elseif (\in_array($category, $mappingItems[$item]['na'])) {
                $value = $strNa;
            }
            $line[] = \mb_str_pad($value, $columnsWidths[$category], ' ', STR_PAD_BOTH);
        }
        dump('| ' . \join(' | ', $line) . ' |');
    }

    // separator
    $line = [
        \mb_str_pad('', $columnsWidths['items'], '-', STR_PAD_BOTH),
    ];
    foreach ($categories as $category) {
        $line[] = \mb_str_pad('', $columnsWidths[$category], '-', STR_PAD_BOTH);
    }
    dump('--' . \join('---', $line) . '--');
}

function editMappings($mappingItems, $categories, $items, $exclusions)
{
    // Set missing associations
    $exitEditMappings = false;
    $missing = find_missing_associations($mappingItems, $categories, $items);
    while ((\count($missing) !== 0) && ($exitEditMappings === false)) {
        dump('');
        dump('Missing associations: ' . \count($missing));
        dump('');
        $picked = $missing[mt_rand(0, \count($missing) - 1)];

        $item = $picked['item'];
        $category = $picked['category'];

        $question = 'Is "' . $item . '" can be categorised as "' . $category . '"?';

        $ex = find_exclusions($exclusions, $category);
        if (\count($ex) !== 0) {
            $question .= ' (and apply accordingly to "' . join('" and "', $ex) . '")';
        }

        dump($question);
        $answer = ask('1: yes ; 2: no ; 3: n/a ; 0: exit');

        switch ($answer) {
            case '0':
                $exitEditMappings = true;
                break;
            case '1':
                dump(' -> "' . $item . '" is "' . $category . '"');
                $mappingItems[$item]['is'][] = $category;
                // apply "is not" to each other
                foreach ($ex as $exclusion) {
                    dump(' -> "' . $item . '" is not "' . $exclusion . '"');
                    $mappingItems[$item]['isnot'][] = $exclusion;
                }
                break;
            case '2':
                dump(' -> "' . $item . '" is not "' . $category . '"');
                $mappingItems[$item]['isnot'][] = $category;
                // apply "is" only if one exclusion
                if (\count($ex) === 1) {
                    foreach ($ex as $exclusion) {
                        dump(' -> "' . $item . '" is "' . $exclusion . '"');
                        $mappingItems[$item]['is'][] = $exclusion;
                    }
                }
                break;
            case '3':
                dump(' -> "' . $item . '" does not apply as "is or is not" "' . $category . '"');
                $mappingItems[$item]['na'][] = $category;
                foreach ($ex as $excludedCategory) {
                    dump(' -> "' . $item . '" does not apply as "is or is not" "' . $excludedCategory . '"');
                    $mappingItems[$item]['na'][] = $excludedCategory;
                }
                break;

            default:
                dump('wut? skipping...');
                break;
        }

        $mappingItems[$item] = clean_item_mappings($mappingItems[$item]);

        $missing = find_missing_associations($mappingItems, $categories, $items);
    }

    return $mappingItems;
}

// Main loop
$exitMainLoop = false;
while ($exitMainLoop === false) {
    dump('');

    $missing = find_missing_associations($mappingItems, $categories, $items);

    $menu = [
        '0: save and exit',
        '',
        '1: show categories (' . \count($categories) . ' found)',
        '2: show items (' . \count($items) . ' found)',
        '3: show exclusions (' . \count($exclusions) . ' found)',
        '4: show themes (' . \count($themes) . ' found)',
        '5: show mappings (' . \count($mappingItems) . ' found)',
        '',
        '6: complete mappings (' . \count($missing) . ' missing)',
    ];
    $answer = ask(\join("\n", $menu));

    switch ($answer) {
        case '0':
            $exitMainLoop = true;
            break;
        case '1':
            showCategories($categories);
            break;
        case '2':
            showItems($items);
            break;
        case '3':
            showExclusions($exclusions);
            break;
        case '4':
            showThemes($themes);
            break;
        case '5':
            showMappings($mappingItems);
            break;
        case '6':
            $data['mapping']['items'] = editMappings($mappingItems, $categories, $items, $exclusions);
            break;
        default:
            break;
    }
}

write_data($data, $jsonDataFile);

echo "ok, done." . PHP_EOL;