@extends ('layouts.patient')
<?php
// include the Diff class
?>

@section('inner-content')
    <?php
    /** @var \App\Models\Client $patient */

    $firstDateEverSeenByAnyone = null;
    $firstDateEverSeenByMcpTypePro = null;
    $firstDateEverSeenByCurrentMcp = null;
    $firstDateSeenByHcpMap = [];
    $lastDateSeenByHcpMap = [];
    $loopLastDateSeenByHcpMap = [];
    $hcpNoteCountMap = [];
    $loopLastDateSeenByMcpTypePro = null;
    $loopLastContentByHcp = [];
    $daysInMcpProgramTotal = null;
    $hcpIdToProMap = [];
    $totalNotes = 0;
    $totalNotesWithBillingClosed = 0;
    $totalNotesWithClaimingClosed = 0;
    foreach($patient->notesAscending as $note){
        if($note->is_cancelled || !$note->hcpPro) continue;
        $hcpIdToProMap[$note->hcp_pro_id] = $note->hcpPro;
        $d = $note->effective_dateest;
        $firstDateSeenByHcpMap[$note->hcp_pro_id] = isset($firstDateSeenByHcpMap[$note->hcp_pro_id]) ? $firstDateSeenByHcpMap[$note->hcp_pro_id] : $d;
        $lastDateSeenByHcpMap[$note->hcp_pro_id] = $d;
        $noteCount = $hcpNoteCountMap[$note->hcp_pro_id] = isset($hcpNoteCountMap[$note->hcp_pro_id]) ? $hcpNoteCountMap[$note->hcp_pro_id]  + 1 : 1;
        $firstDateSeenByThisHcp = $firstDateSeenByHcpMap[$note->hcp_pro_id];
        $firstDateEverSeenByAnyone = $firstDateEverSeenByAnyone ? $firstDateEverSeenByAnyone : $d;
        if($note->hcpPro->is_enrolled_as_mcp){
            $firstDateEverSeenByMcpTypePro = $firstDateEverSeenByMcpTypePro ? $firstDateEverSeenByMcpTypePro : $d;
            if($note->client->mcp && $note->hcpPro->id == $note->client->mcp->id){
                $firstDateEverSeenByCurrentMcp = $firstDateEverSeenByMcpTypePro;
            }
        }
        $daysInProgram = (strtotime($d) - strtotime($firstDateEverSeenByAnyone)) / (60 * 60 * 24);
        $daysInMcpProgram = $firstDateEverSeenByMcpTypePro ? (strtotime($d) - strtotime($firstDateEverSeenByMcpTypePro)) / (60 * 60 * 24) : null;
        $daysWithHcp = (strtotime($d) - strtotime($firstDateSeenByThisHcp)) / (60 * 60 * 24);
        $lastVisitWithHcp = isset($loopLastDateSeenByHcpMap[$note->hcp_pro_id]) ? $loopLastDateSeenByHcpMap[$note->hcp_pro_id] : null;
        $daysSinceLastNoteByThisHcp = $lastVisitWithHcp ? (strtotime($d) - strtotime($lastVisitWithHcp)) / (60 * 60 * 24) : null;
        $daysSinceLastMcpTypeVisit = $note->hcpPro->is_enrolled_as_mcp && $loopLastDateSeenByMcpTypePro ? (strtotime($d) - strtotime($loopLastDateSeenByMcpTypePro)) / (60 * 60 * 24) : null;
        $loopLastDateSeenByHcpMap[$note->hcpPro->id] = $d;
        if($note->hcpPro->is_enrolled_as_mcp){
            $loopLastDateSeenByMcpTypePro = $d;
        }
        $daysInMcpProgramTotal = (strtotime(date('Y-m-d')) - strtotime($firstDateSeenByThisHcp)) / (60 * 60 * 24);

        $totalNotes++;
        if($note->is_bill_closed) $totalNotesWithBillingClosed++;
        if($note->is_claim_closed) $totalNotesWithClaimingClosed++;
    }
    ?>
    <div id="claims-resolver">
    <h4 class="font-weight-bold m-0 mb-3">Claims Resolver</h4>
    <h2><strong>Numbers:</strong></h2>
    <table class="table table-sm table-striped">
        <tr>
            <td class="w-25">First date ever seen by anyone:</td>
            <td>{{ friendly_date_time($firstDateEverSeenByAnyone, false) }}</td>
        </tr>
        <tr>
            <td>First date with medical professional:</td>
            <td>{{ friendly_date_time($firstDateEverSeenByMcpTypePro, false) }}</td>
        </tr>
        <tr>
            <td>First date ever seen by current MCP:</td>
            <td>{{ friendly_date_time($firstDateEverSeenByCurrentMcp, false) }}</td>
        </tr>
        <tr>
            <td>HCP notes signed:</td>
            <td>{{$hcpSignedNotesCount}}</td>
        </tr>
        <tr>
            <td>Days in MCP program:</td>
            <td>{{ $daysInMcpProgramTotal }}</td>
        </tr>
        <tr>
            <td>Total Notes:</td>
            <td>{{ $totalNotes }}</td>
        </tr>
        <tr>
            <td>Total Notes With Billing Closed:</td>
            <td>
                <div class="d-flex">
                    <span class="fill-percent-content">{{ $totalNotesWithBillingClosed }} / {{$totalNotes}}</span>
                    <div class="d-inline-flex flex-grow-1 ml-3">
                        @if($totalNotes > 0)
                        <span class="fill-percent-value">{{round($totalNotesWithBillingClosed * 100 / $totalNotes, 1)}}%</span>
                        <div class="flex-grow-1 position-relative fill-bar">
                            <div class="bg-info position-absolute" style="left: 0; top: 0; height: 100%; width: {{$totalNotesWithBillingClosed * 100 / $totalNotes}}%"></div>
                        </div>
                        @else 
                         No notes
                        @endif
                    </div>
                </div>
            </td>
        </tr>
        <tr>
            <td>Total Notes With Claiming Closed:</td>
            <td>
                <div class="d-flex align-items-center">
                   @if($totalNotes > 0)
                    <span class="fill-percent-content">{{ $totalNotesWithClaimingClosed }} / {{$totalNotes}}</span>
                    <div class="d-inline-flex flex-grow-1 ml-3">
                        <span class="fill-percent-value">{{round($totalNotesWithClaimingClosed * 100 / $totalNotes, 1)}}%</span>
                        <div class="flex-grow-1 position-relative fill-bar">
                            <div class="bg-info position-absolute" style="left: 0; top: 0; height: 100%; width: {{$totalNotesWithClaimingClosed * 100 / $totalNotes}}%"></div>
                        </div>
                    </div>
                    @else 
                    <span>--</span>
                    @endif 
                </div>
            </td>
        </tr>
    </table>
    <h2><strong>All HCPs:</strong></h2>
    <table class="table table-sm table-condensed table-striped">
        <tr>
            <th>Pro</th>
            <th>First Visit Date</th>
            <th>Last Visit Date</th>
            <th>Days Between First & Last Visit</th>
            <th>Note Count</th>
            <th>Days / Visit (avg.)</th>
        </tr>
        @foreach($hcpIdToProMap as $k => $v)
            <?php
            $daysBetweenFirstAndLastVisit = (strtotime($lastDateSeenByHcpMap[$k]) - strtotime($firstDateSeenByHcpMap[$k])) / (60 * 60 * 24);
            ?>
            <tr>
                <td>{{ $v->displayName() }}</td>
                <td>{{ friendly_date_time($firstDateSeenByHcpMap[$k], false) }}</td>
                <td>{{ friendly_date_time($lastDateSeenByHcpMap[$k], false) }}</td>
                <td>
                    {{ $daysBetweenFirstAndLastVisit }}
                </td>
                <td>{{ $hcpNoteCountMap[$k] }}</td>
                <td> {{ ($hcpNoteCountMap[$k] - 1) ? round($daysBetweenFirstAndLastVisit / ($hcpNoteCountMap[$k] - 1), 2) : null }} </td>
            </tr>
        @endforeach
    </table>
    <h2>
        <strong>All Notes:</strong>
    </h2>
    <style>
        .diff td{
            vertical-align : top;
            white-space    : pre;
            white-space    : pre-wrap;
            font-family    : monospace;
        }


        .diff td{
            padding:0 0.667em;
            vertical-align:top;
            white-space:pre;
            white-space:pre-wrap;
            font-family:Consolas,'Courier New',Courier,monospace;
            font-size:0.75em;
            line-height:1.333;
        }

        .diff span{
            display:block;
            min-height:1.333em;
            margin-top:-1px;
            padding:0 3px;
        }

        * html .diff span{
            height:1.333em;
        }

        .diff span:first-child{
            margin-top:0;
        }

        .diffDeleted span{
            border:1px solid rgb(255,192,192);
            background:rgb(255,224,224);
        }

        .diffInserted span{
            border:1px solid rgb(192,255,192);
            background:rgb(224,255,224);
        }

        #toStringOutput{
            margin:0 2em 2em;
        }

        td.cr-check {
            background-color: lightgreen;
        }

        td.cr-close {
            background-color: lightcoral;
        }

    </style>
    <div style="overflow: auto">
        <table class="table table-bordered table-condensed" id="notes-table">
        <thead>
        <tr>
            <th>#</th>
            <th>HCP</th>
            <th>Date</th>
{{--            <th>Days in Program</th>--}}
            <th>Days in MCP Program</th>
{{--            <th>Days with HCP</th>--}}
            <th>Note Count</th>
            <th>Days Since Last Note by HCP</th>
{{--            <th>Days Since Last MCP Type Visit</th>--}}
{{--            <th>Loop check</th>--}}
            <th>Signed By HCP?</th>
            <th>Billing Closed?</th>
            <th>Claiming Closed?</th>
{{--            <th>Content</th>--}}
            <th>Bills</th>
            <th>Claims</th>
            <th>
                Difference
            </th>
        </tr>
        </thead>
        <tbody style="overflow-y: auto; max-height: 500px;">
        <?php
            $firstDateEverSeenByAnyone = null;
            $firstDateEverSeenByMcpTypePro = null;
            $firstDateSeenByHcpMap = [];
            $loopLastDateSeenByHcpMap = [];
            $hcpNoteCountMap = [];
            $loopLastDateSeenByMcpTypePro = null;
            $loopLastContentByHcp = [];
            $rowIndex = 0;
            foreach($patient->notesAscending as $note){
                if($note->is_cancelled || !$note->hcpPro) continue;
                $rowIndex++;

                $d = $note->effective_dateest;
                $firstDateSeenByHcpMap[$note->hcp_pro_id] = isset($firstDateSeenByHcpMap[$note->hcp_pro_id]) ? $firstDateSeenByHcpMap[$note->hcp_pro_id] : $d;
                $noteCount = $hcpNoteCountMap[$note->hcp_pro_id] = isset($hcpNoteCountMap[$note->hcp_pro_id]) ? $hcpNoteCountMap[$note->hcp_pro_id]  + 1 : 1;

                $firstDateSeenByThisHcp = $firstDateSeenByHcpMap[$note->hcp_pro_id];

                $firstDateEverSeenByAnyone = $firstDateEverSeenByAnyone ? $firstDateEverSeenByAnyone : $d;
                if($note->hcpPro->is_enrolled_as_mcp){
                    $firstDateEverSeenByMcpTypePro = $firstDateEverSeenByMcpTypePro ? $firstDateEverSeenByMcpTypePro : $d;
                }

                $daysInProgram = (strtotime($d) - strtotime($firstDateEverSeenByAnyone)) / (60 * 60 * 24);
                $daysInMcpProgram = $firstDateEverSeenByMcpTypePro ? (strtotime($d) - strtotime($firstDateEverSeenByMcpTypePro)) / (60 * 60 * 24) : null;

                $daysWithHcp = (strtotime($d) - strtotime($firstDateSeenByThisHcp)) / (60 * 60 * 24);

                $lastVisitWithHcp = isset($loopLastDateSeenByHcpMap[$note->hcp_pro_id]) ? $loopLastDateSeenByHcpMap[$note->hcp_pro_id] : null;
                $daysSinceLastNoteByThisHcp = $lastVisitWithHcp ? (strtotime($d) - strtotime($lastVisitWithHcp)) / (60 * 60 * 24) : null;

                $daysSinceLastMcpTypeVisit = $note->hcpPro->is_enrolled_as_mcp && $loopLastDateSeenByMcpTypePro ? (strtotime($d) - strtotime($loopLastDateSeenByMcpTypePro)) / (60 * 60 * 24) : null;

                // content

                $content = null;
                ob_start();
                foreach($note->sections as $section){
                    echo $section->summary_html;
                    echo '<hr>';
                }
                $content = ob_get_clean();
                $lastContent = isset($loopLastContentByHcp[$note->hcpPro->id]) ? $loopLastContentByHcp[$note->hcpPro->id] : "-";

                $contentText = Html2Text::convert($content);
                $lastContentText = Html2Text::convert($lastContent);

        ?>
        <tr>
            <td>{{$rowIndex}}</td>
            <td>
                <strong>{{ $note->hcpPro->displayName() }}</strong>
            </td>
            <td>
                <a native target="_blank"
                   open-in-stag-popup
                   update-parent
                   mc-initer="note-single"
                   title="Note Details"
                   href="/patients/view/{{ $note->client->uid }}/notes/view/{{ $note->uid }}">
                    {{ friendly_date_time($d, false) }}
                </a>
            </td>
{{--            <td>--}}
{{--                {{ $daysInProgram }}--}}
{{--            </td>--}}
            <td>
                {{ $daysInMcpProgram }}
            </td>
{{--            <td>--}}
{{--                {{ $daysWithHcp }}--}}
{{--            </td>--}}
            <td>
                {{ $noteCount }}
            </td>
            <td>
                {{$daysSinceLastNoteByThisHcp}}
            </td>
{{--            <td>--}}
{{--                {{ $daysSinceLastMcpTypeVisit }}--}}
{{--            </td>--}}
{{--            <td>--}}
{{--                {{ $loopLastDateSeenByMcpTypePro }}--}}
{{--            </td>--}}

            <td class="cr-{{ $note->is_signed_by_hcp ? 'check' : 'close' }}">
                <i class="fa fa-{{ $note->is_signed_by_hcp ? 'check' : 'times-circle' }}"></i>
                HCP {{ $note->is_signed_by_hcp ? '' : 'Not' }} Signed
            </td>
            <td class="cr-{{ $note->is_bill_closed ? 'check' : 'close' }}">
                <i class="fa fa-{{ $note->is_bill_closed ? 'check' : 'times-circle' }}"></i>
                Billing {{ $note->is_bill_closed ? '' : 'Not' }} Closed
            </td>
            <td class="cr-{{ $note->is_claim_closed ? 'check' : 'close' }}">
                <i class="fa fa-{{ $note->is_claim_closed ? 'check' : 'times-circle' }}"></i>
                Claiming {{ $note->is_claim_closed ? '' : 'Not' }} Closed
            </td>
{{--            <td><div style="max-height: 200px; overflow-y: auto;">{!! $content !!}</div></td>--}}

            <td class="p-0">
                <div>
                    <table class="table table-sm m-0 border-0">
                    @foreach($note->bills as $bill)
                        @if(!$bill->is_cancelled)
                        <tr>
                            <td>{{$bill->code}}</td>
                            <td style="width: 60px">
                                {{str_contains($bill->code, 'Treatment Services') ? floor((float) $bill->number_of_units * 60) . ' mins.' : $bill->number_of_units}}
                            </td>
                        </tr>
                        @endif
                    @endforeach
                    </table>
                </div>
            </td>
            <td class="p-0">
                <div>
                    <table class="table table-sm m-0 border-0">
                        @foreach($note->claims as $claim)
                            <?php if($claim->is_cancelled) continue; ?>
                            <tr>
                                <td style="background-color: lightblue">
                                    CL#{{$claim->id}}
                                </td>
                            </tr>
                            <tr>
                                <td class="p-0">
                                    <table class="table table-sm table-striped m-0">
                                    @foreach($claim->lines as $line)
                                            <tr>
                                                <td style="width: 80px">{{ $line->cpt }}</td>
                                                <td style="width: 30px">{{ $line->units }}</td>
                                                <td class="p-0">
                                                    <table class="table table-sm m-0 border-0">
                                                    @foreach($line->claimLineIcds as $icd)
                                                            <tr>
                                                                <td>{{ $icd->code }}</td>
                                                                <td>{{ $icd->description }}</td>
                                                            </tr>
                                                    @endforeach
                                                    @if(!$line->claimLineIcds || !count($line->claimLineIcds))
                                                            <tr>
                                                                <td colspan="2">
                                                                    <b>
                                                                        <i class="fa fa-exclamation-triangle"></i>
                                                                        No ICDs
                                                                    </b>
                                                                </td>
                                                            </tr>
                                                    @endif
                                                    </table>
                                                </td>
                                            </tr>
                                    @endforeach
                                    </table>
                                </td>
                            </tr>
                        @endforeach
                    </table>
                </div>
            </td>
            <td>
                <div moe large relative no-mask>
                    <a href="#" start show>Show Diff</a>
                    <div url="/dummy" right>
                        <div style="max-height: 250px; overflow-y: auto;">
                            <?php echo Diff::toTable(Diff::compare($lastContentText, $contentText)) ?>
                        </div>
                    </div>
                </div>
            </td>
        </tr>
        <?php
                $loopLastDateSeenByHcpMap[$note->hcpPro->id] = $d;
                if($note->hcpPro->is_enrolled_as_mcp){
                    $loopLastDateSeenByMcpTypePro = $d;
                }
                $loopLastContentByHcp[$note->hcpPro->id] = $content;
            }
        ?>

        </tbody>
    </table>
    </div>
    </div>
    <script>
        (function() {
            function init() {
                $('#notes-table').DataTable({
                    paging: false,
                    scrollY: 500,
                    scrollX: true
                });
            }
            addMCInitializer('claims-resolver', init, '#claims-resolver');
        }).call(window);
    </script>

@endsection

<?php

/*

class.Diff.php

A class containing a diff implementation

Created by Kate Morley - http://iamkate.com/ - and released under the terms of
the CC0 1.0 Universal legal code:

http://creativecommons.org/publicdomain/zero/1.0/legalcode

*/

// A class containing functions for computing diffs and formatting the output.
class Diff{

    // define the constants
    const UNMODIFIED = 0;
    const DELETED    = 1;
    const INSERTED   = 2;

    /* Returns the diff for two strings. The return value is an array, each of
     * whose values is an array containing two values: a line (or character, if
     * $compareCharacters is true), and one of the constants DIFF::UNMODIFIED (the
     * line or character is in both strings), DIFF::DELETED (the line or character
     * is only in the first string), and DIFF::INSERTED (the line or character is
     * only in the second string). The parameters are:
     *
     * $string1           - the first string
     * $string2           - the second string
     * $compareCharacters - true to compare characters, and false to compare
     *                      lines; this optional parameter defaults to false
     */
    public static function compare(
        $string1, $string2, $compareCharacters = false){

        // initialise the sequences and comparison start and end positions
        $start = 0;
        if ($compareCharacters){
            $sequence1 = $string1;
            $sequence2 = $string2;
            $end1 = strlen($string1) - 1;
            $end2 = strlen($string2) - 1;
        }else{
            $sequence1 = preg_split('/\R/', $string1);
            $sequence2 = preg_split('/\R/', $string2);
            $end1 = count($sequence1) - 1;
            $end2 = count($sequence2) - 1;
        }

        // skip any common prefix
        while ($start <= $end1 && $start <= $end2
            && $sequence1[$start] == $sequence2[$start]){
            $start ++;
        }

        // skip any common suffix
        while ($end1 >= $start && $end2 >= $start
            && $sequence1[$end1] == $sequence2[$end2]){
            $end1 --;
            $end2 --;
        }

        // compute the table of longest common subsequence lengths
        $table = self::computeTable($sequence1, $sequence2, $start, $end1, $end2);

        // generate the partial diff
        $partialDiff =
            self::generatePartialDiff($table, $sequence1, $sequence2, $start);

        // generate the full diff
        $diff = array();
        for ($index = 0; $index < $start; $index ++){
            $diff[] = array($sequence1[$index], self::UNMODIFIED);
        }
        while (count($partialDiff) > 0) $diff[] = array_pop($partialDiff);
        for ($index = $end1 + 1;
             $index < ($compareCharacters ? strlen($sequence1) : count($sequence1));
             $index ++){
            $diff[] = array($sequence1[$index], self::UNMODIFIED);
        }

        // return the diff
        return $diff;

    }

    /* Returns the diff for two files. The parameters are:
     *
     * $file1             - the path to the first file
     * $file2             - the path to the second file
     * $compareCharacters - true to compare characters, and false to compare
     *                      lines; this optional parameter defaults to false
     */
    public static function compareFiles(
        $file1, $file2, $compareCharacters = false){

        // return the diff of the files
        return self::compare(
            file_get_contents($file1),
            file_get_contents($file2),
            $compareCharacters);

    }

    /* Returns the table of longest common subsequence lengths for the specified
     * sequences. The parameters are:
     *
     * $sequence1 - the first sequence
     * $sequence2 - the second sequence
     * $start     - the starting index
     * $end1      - the ending index for the first sequence
     * $end2      - the ending index for the second sequence
     */
    private static function computeTable(
        $sequence1, $sequence2, $start, $end1, $end2){

        // determine the lengths to be compared
        $length1 = $end1 - $start + 1;
        $length2 = $end2 - $start + 1;

        // initialise the table
        $table = array(array_fill(0, $length2 + 1, 0));

        // loop over the rows
        for ($index1 = 1; $index1 <= $length1; $index1 ++){

            // create the new row
            $table[$index1] = array(0);

            // loop over the columns
            for ($index2 = 1; $index2 <= $length2; $index2 ++){

                // store the longest common subsequence length
                if ($sequence1[$index1 + $start - 1]
                    == $sequence2[$index2 + $start - 1]){
                    $table[$index1][$index2] = $table[$index1 - 1][$index2 - 1] + 1;
                }else{
                    $table[$index1][$index2] =
                        max($table[$index1 - 1][$index2], $table[$index1][$index2 - 1]);
                }

            }
        }

        // return the table
        return $table;

    }

    /* Returns the partial diff for the specificed sequences, in reverse order.
     * The parameters are:
     *
     * $table     - the table returned by the computeTable function
     * $sequence1 - the first sequence
     * $sequence2 - the second sequence
     * $start     - the starting index
     */
    private static function generatePartialDiff(
        $table, $sequence1, $sequence2, $start){

        //  initialise the diff
        $diff = array();

        // initialise the indices
        $index1 = count($table) - 1;
        $index2 = count($table[0]) - 1;

        // loop until there are no items remaining in either sequence
        while ($index1 > 0 || $index2 > 0){

            // check what has happened to the items at these indices
            if ($index1 > 0 && $index2 > 0
                && $sequence1[$index1 + $start - 1]
                == $sequence2[$index2 + $start - 1]){

                // update the diff and the indices
                $diff[] = array($sequence1[$index1 + $start - 1], self::UNMODIFIED);
                $index1 --;
                $index2 --;

            }elseif ($index2 > 0
                && $table[$index1][$index2] == $table[$index1][$index2 - 1]){

                // update the diff and the indices
                $diff[] = array($sequence2[$index2 + $start - 1], self::INSERTED);
                $index2 --;

            }else{

                // update the diff and the indices
                $diff[] = array($sequence1[$index1 + $start - 1], self::DELETED);
                $index1 --;

            }

        }

        // return the diff
        return $diff;

    }

    /* Returns a diff as a string, where unmodified lines are prefixed by '  ',
     * deletions are prefixed by '- ', and insertions are prefixed by '+ '. The
     * parameters are:
     *
     * $diff      - the diff array
     * $separator - the separator between lines; this optional parameter defaults
     *              to "\n"
     */
    public static function toString($diff, $separator = "\n"){

        // initialise the string
        $string = '';

        // loop over the lines in the diff
        foreach ($diff as $line){

            // extend the string with the line
            switch ($line[1]){
                case self::UNMODIFIED : $string .= '  ' . $line[0];break;
                case self::DELETED    : $string .= '- ' . $line[0];break;
                case self::INSERTED   : $string .= '+ ' . $line[0];break;
            }

            // extend the string with the separator
            $string .= $separator;

        }

        // return the string
        return $string;

    }

    /* Returns a diff as an HTML string, where unmodified lines are contained
     * within 'span' elements, deletions are contained within 'del' elements, and
     * insertions are contained within 'ins' elements. The parameters are:
     *
     * $diff      - the diff array
     * $separator - the separator between lines; this optional parameter defaults
     *              to '<br>'
     */
    public static function toHTML($diff, $separator = '<br>'){

        // initialise the HTML
        $html = '';

        // loop over the lines in the diff
        foreach ($diff as $line){

            // extend the HTML with the line
            switch ($line[1]){
                case self::UNMODIFIED : $element = 'span'; break;
                case self::DELETED    : $element = 'del';  break;
                case self::INSERTED   : $element = 'ins';  break;
            }
            $html .=
                '<' . $element . '>'
                . htmlspecialchars($line[0])
                . '</' . $element . '>';

            // extend the HTML with the separator
            $html .= $separator;

        }

        // return the HTML
        return $html;

    }

    /* Returns a diff as an HTML table. The parameters are:
     *
     * $diff        - the diff array
     * $indentation - indentation to add to every line of the generated HTML; this
     *                optional parameter defaults to ''
     * $separator   - the separator between lines; this optional parameter
     *                defaults to '<br>'
     */
    public static function toTable($diff, $indentation = '', $separator = '<br>'){

        // initialise the HTML
        $html = $indentation . "<table class=\"diff\">\n";

        // loop over the lines in the diff
        $index = 0;
        while ($index < count($diff)){

            // determine the line type
            switch ($diff[$index][1]){

                // display the content on the left and right
                case self::UNMODIFIED:
                    $leftCell =
                        self::getCellContent(
                            $diff, $indentation, $separator, $index, self::UNMODIFIED);
                    $rightCell = $leftCell;
                    break;

                // display the deleted on the left and inserted content on the right
                case self::DELETED:
                    $leftCell =
                        self::getCellContent(
                            $diff, $indentation, $separator, $index, self::DELETED);
                    $rightCell =
                        self::getCellContent(
                            $diff, $indentation, $separator, $index, self::INSERTED);
                    break;

                // display the inserted content on the right
                case self::INSERTED:
                    $leftCell = '';
                    $rightCell =
                        self::getCellContent(
                            $diff, $indentation, $separator, $index, self::INSERTED);
                    break;

            }

            // extend the HTML with the new row
            $html .=
                $indentation
                . "  <tr>\n"
                . $indentation
                . '    <td class="diff'
                . ($leftCell == $rightCell
                    ? 'Unmodified'
                    : ($leftCell == '' ? 'Blank' : 'Deleted'))
                . '">'
                . $leftCell
                . "</td>\n"
                . $indentation
                . '    <td class="diff'
                . ($leftCell == $rightCell
                    ? 'Unmodified'
                    : ($rightCell == '' ? 'Blank' : 'Inserted'))
                . '">'
                . $rightCell
                . "</td>\n"
                . $indentation
                . "  </tr>\n";

        }

        // return the HTML
        return $html . $indentation . "</table>\n";

    }

    /* Returns the content of the cell, for use in the toTable function. The
     * parameters are:
     *
     * $diff        - the diff array
     * $indentation - indentation to add to every line of the generated HTML
     * $separator   - the separator between lines
     * $index       - the current index, passes by reference
     * $type        - the type of line
     */
    private static function getCellContent(
        $diff, $indentation, $separator, &$index, $type){

        // initialise the HTML
        $html = '';

        // loop over the matching lines, adding them to the HTML
        while ($index < count($diff) && $diff[$index][1] == $type){
            $html .=
                '<span>'
                . htmlspecialchars($diff[$index][0])
                . '</span>'
                . $separator;
            $index ++;
        }

        // return the HTML
        return $html;

    }

}

class Html2Text {

    public static function defaultOptions() {
        return array(
            'ignore_errors' => false,
            'drop_links'    => false,
        );
    }

    /**
     * Tries to convert the given HTML into a plain text format - best suited for
     * e-mail display, etc.
     *
     * <p>In particular, it tries to maintain the following features:
     * <ul>
     *   <li>Links are maintained, with the 'href' copied over
     *   <li>Information in the &lt;head&gt; is lost
     * </ul>
     *
     * @param string $html the input HTML
     * @param boolean $ignore_error Ignore xml parsing errors
     * @return string the HTML converted, as best as possible, to text
     * @throws Html2TextException if the HTML could not be loaded as a {@link \DOMDocument}
     */
    public static function convert($html, $options = array()) {

        if ($options === false || $options === true) {
            // Using old style (< 1.0) of passing in options
            $options = array('ignore_errors' => $options);
        }

        $options = array_merge(static::defaultOptions(), $options);

        // check all options are valid
        foreach ($options as $key => $value) {
            if (!in_array($key, array_keys(static::defaultOptions()))) {
                throw new \InvalidArgumentException("Unknown html2text option '$key'");
            }
        }

        $is_office_document = static::isOfficeDocument($html);

        if ($is_office_document) {
            // remove office namespace
            $html = str_replace(array("<o:p>", "</o:p>"), "", $html);
        }

        $html = static::fixNewlines($html);
        if (mb_detect_encoding($html, "UTF-8", true)) {
            $html = mb_convert_encoding($html, "HTML-ENTITIES", "UTF-8");
        }

        //$doc = static::getDocument($html, $options['ignore_errors']);
        $doc = static::getDocument($html);

        $output = static::iterateOverNode($doc, null, false, $is_office_document, $options);

        // process output for whitespace/newlines
        $output = static::processWhitespaceNewlines($output);

        return $output;
    }

    /**
     * Unify newlines; in particular, \r\n becomes \n, and
     * then \r becomes \n. This means that all newlines (Unix, Windows, Mac)
     * all become \ns.
     *
     * @param string $text text with any number of \r, \r\n and \n combinations
     * @return string the fixed text
     */
    static function fixNewlines($text) {
        // replace \r\n to \n
        $text = str_replace("\r\n", "\n", $text);
        // remove \rs
        $text = str_replace("\r", "\n", $text);

        return $text;
    }

    static function nbspCodes() {
        return array(
            "\xc2\xa0",
            "\u00a0",
        );
    }

    static function zwnjCodes() {
        return array(
            "\xe2\x80\x8c",
            "\u200c",
        );
    }

    /**
     * Remove leading or trailing spaces and excess empty lines from provided multiline text
     *
     * @param string $text multiline text any number of leading or trailing spaces or excess lines
     * @return string the fixed text
     */
    static function processWhitespaceNewlines($text) {

        // remove excess spaces around tabs
        $text = preg_replace("/ *\t */im", "\t", $text);

        // remove leading whitespace
        $text = ltrim($text);

        // remove leading spaces on each line
        $text = preg_replace("/\n[ \t]*/im", "\n", $text);

        // convert non-breaking spaces to regular spaces to prevent output issues,
        // do it here so they do NOT get removed with other leading spaces, as they
        // are sometimes used for indentation
        $text = static::renderText($text);

        // remove trailing whitespace
        $text = rtrim($text);

        // remove trailing spaces on each line
        $text = preg_replace("/[ \t]*\n/im", "\n", $text);

        // unarmor pre blocks
        $text = static::fixNewLines($text);

        // remove unnecessary empty lines
        $text = preg_replace("/\n\n\n*/im", "\n\n", $text);

        return $text;
    }

    /**
     * Parse HTML into a DOMDocument
     *
     * @param string $html the input HTML
     * @param boolean $ignore_error Ignore xml parsing errors
     * @return \DOMDocument the parsed document tree
     */
    static function getDocument($html, $ignore_error = true) {

        $doc = new \DOMDocument();

        $html = trim($html);

        if (!$html) {
            // DOMDocument doesn't support empty value and throws an error
            // Return empty document instead
            return $doc;
        }

        if ($html[0] !== '<') {
            // If HTML does not begin with a tag, we put a body tag around it.
            // If we do not do this, PHP will insert a paragraph tag around
            // the first block of text for some reason which can mess up
            // the newlines. See pre.html test for an example.
            $html = '<body>' . $html . '</body>';
        }

        if ($ignore_error) {
            $doc->strictErrorChecking = false;
            $doc->recover = true;
            $doc->xmlStandalone = true;
            $old_internal_errors = libxml_use_internal_errors(true);
            $load_result = $doc->loadHTML($html, LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NONET | LIBXML_PARSEHUGE);
            libxml_use_internal_errors($old_internal_errors);
        }
        else {
            $load_result = $doc->loadHTML($html);
        }

        if (!$load_result) {
            throw new Html2TextException("Could not load HTML - badly formed?", $html);
        }

        return $doc;
    }

    /**
     * Can we guess that this HTML is generated by Microsoft Office?
     */
    static function isOfficeDocument($html) {
        return strpos($html, "urn:schemas-microsoft-com:office") !== false;
    }

    /**
     * Replace any special characters with simple text versions, to prevent output issues:
     * - Convert non-breaking spaces to regular spaces; and
     * - Convert zero-width non-joiners to '' (nothing).
     *
     * This is to match our goal of rendering documents as they would be rendered
     * by a browser.
     */
    static function renderText($text) {
        $text = str_replace(static::nbspCodes(), " ", $text);
        $text = str_replace(static::zwnjCodes(), "", $text);
        return $text;
    }

    static function isWhitespace($text) {
        return strlen(trim(static::renderText($text), "\n\r\t ")) === 0;
    }

    static function nextChildName($node) {
        // get the next child
        $nextNode = $node->nextSibling;
        while ($nextNode != null) {
            if ($nextNode instanceof \DOMText) {
                if (!static::isWhitespace($nextNode->wholeText)) {
                    break;
                }
            }

            if ($nextNode instanceof \DOMElement) {
                break;
            }

            $nextNode = $nextNode->nextSibling;
        }

        $nextName = null;
        if (($nextNode instanceof \DOMElement || $nextNode instanceof \DOMText) && $nextNode != null) {
            $nextName = strtolower($nextNode->nodeName);
        }

        return $nextName;
    }

    static function iterateOverNode($node, $prevName = null, $in_pre = false, $is_office_document = false, $options) {
        if ($node instanceof \DOMText) {
            // Replace whitespace characters with a space (equivilant to \s)
            if ($in_pre) {
                $text = "\n" . trim(static::renderText($node->wholeText), "\n\r\t ") . "\n";

                // Remove trailing whitespace only
                $text = preg_replace("/[ \t]*\n/im", "\n", $text);

                // armor newlines with \r.
                return str_replace("\n", "\r", $text);

            } else {
                $text = static::renderText($node->wholeText);
                $text = preg_replace("/[\\t\\n\\f\\r ]+/im", " ", $text);

                if (!static::isWhitespace($text) && ($prevName == 'p' || $prevName == 'div')) {
                    return "\n" . $text;
                }
                return $text;
            }
        }

        if ($node instanceof \DOMDocumentType || $node instanceof \DOMProcessingInstruction) {
            // ignore
            return "";
        }

        $name = strtolower($node->nodeName);
        $nextName = static::nextChildName($node);

        // start whitespace
        switch ($name) {
            case "hr":
                $prefix = '';
                if ($prevName != null) {
                    $prefix = "\n";
                }
                return $prefix . "---------------------------------------------------------------\n";

            case "style":
            case "head":
            case "title":
            case "meta":
            case "script":
                // ignore these tags
                return "";

            case "h1":
            case "h2":
            case "h3":
            case "h4":
            case "h5":
            case "h6":
            case "ol":
            case "ul":
            case "pre":
                // add two newlines
                $output = "\n\n";
                break;

            case "td":
            case "th":
                // add tab char to separate table fields
                $output = "\t";
                break;

            case "p":
                // Microsoft exchange emails often include HTML which, when passed through
                // html2text, results in lots of double line returns everywhere.
                //
                // To fix this, for any p element with a className of `MsoNormal` (the standard
                // classname in any Microsoft export or outlook for a paragraph that behaves
                // like a line return) we skip the first line returns and set the name to br.
                if ($is_office_document && $node->getAttribute('class') == 'MsoNormal') {
                    $output = "";
                    $name = 'br';
                    break;
                }

                // add two lines
                $output = "\n\n";
                break;

            case "tr":
                // add one line
                $output = "\n";
                break;

            case "div":
                $output = "";
                if ($prevName !== null) {
                    // add one line
                    $output .= "\n";
                }
                break;

            case "li":
                $output = "- ";
                break;

            default:
                // print out contents of unknown tags
                $output = "";
                break;
        }

        // debug
        //$output .= "[$name,$nextName]";

        if (isset($node->childNodes)) {

            $n = $node->childNodes->item(0);
            $previousSiblingNames = array();
            $previousSiblingName = null;

            $parts = array();
            $trailing_whitespace = 0;

            while ($n != null) {

                $text = static::iterateOverNode($n, $previousSiblingName, $in_pre || $name == 'pre', $is_office_document, $options);

                // Pass current node name to next child, as previousSibling does not appear to get populated
                if ($n instanceof \DOMDocumentType
                    || $n instanceof \DOMProcessingInstruction
                    || ($n instanceof \DOMText && static::isWhitespace($text))) {
                    // Keep current previousSiblingName, these are invisible
                    $trailing_whitespace++;
                }
                else {
                    $previousSiblingName = strtolower($n->nodeName);
                    $previousSiblingNames[] = $previousSiblingName;
                    $trailing_whitespace = 0;
                }

                $node->removeChild($n);
                $n = $node->childNodes->item(0);

                $parts[] = $text;
            }

            // Remove trailing whitespace, important for the br check below
            while ($trailing_whitespace-- > 0) {
                array_pop($parts);
            }

            // suppress last br tag inside a node list if follows text
            $last_name = array_pop($previousSiblingNames);
            if ($last_name === 'br') {
                $last_name = array_pop($previousSiblingNames);
                if ($last_name === '#text') {
                    array_pop($parts);
                }
            }

            $output .= implode('', $parts);
        }

        // end whitespace
        switch ($name) {
            case "h1":
            case "h2":
            case "h3":
            case "h4":
            case "h5":
            case "h6":
            case "pre":
            case "p":
                // add two lines
                $output .= "\n\n";
                break;

            case "br":
                // add one line
                $output .= "\n";
                break;

            case "div":
                break;

            case "a":
                // links are returned in [text](link) format
                $href = $node->getAttribute("href");

                $output = trim($output);

                // remove double [[ ]] s from linking images
                if (substr($output, 0, 1) == "[" && substr($output, -1) == "]") {
                    $output = substr($output, 1, strlen($output) - 2);

                    // for linking images, the title of the <a> overrides the title of the <img>
                    if ($node->getAttribute("title")) {
                        $output = $node->getAttribute("title");
                    }
                }

                // if there is no link text, but a title attr
                if (!$output && $node->getAttribute("title")) {
                    $output = $node->getAttribute("title");
                }

                if ($href == null) {
                    // it doesn't link anywhere
                    if ($node->getAttribute("name") != null) {
                        if ($options['drop_links']) {
                            $output = "$output";
                        } else {
                            $output = "[$output]";
                        }
                    }
                } else {
                    if ($href == $output || $href == "mailto:$output" || $href == "http://$output" || $href == "https://$output") {
                        // link to the same address: just use link
                        $output = "$output";
                    } else {
                        // replace it
                        if ($output) {
                            if ($options['drop_links']) {
                                $output = "$output";
                            } else {
                                $output = "[$output]($href)";
                            }
                        } else {
                            // empty string
                            $output = "$href";
                        }
                    }
                }

                // does the next node require additional whitespace?
                switch ($nextName) {
                    case "h1": case "h2": case "h3": case "h4": case "h5": case "h6":
                    $output .= "\n";
                    break;
                }
                break;

            case "img":
                if ($node->getAttribute("title")) {
                    $output = "[" . $node->getAttribute("title") . "]";
                } elseif ($node->getAttribute("alt")) {
                    $output = "[" . $node->getAttribute("alt") . "]";
                } else {
                    $output = "";
                }
                break;

            case "li":
                $output .= "\n";
                break;

            case "blockquote":
                // process quoted text for whitespace/newlines
                $output = static::processWhitespaceNewlines($output);

                // add leading newline
                $output = "\n" . $output;

                // prepend '> ' at the beginning of all lines
                $output = preg_replace("/\n/im", "\n> ", $output);

                // replace leading '> >' with '>>'
                $output = preg_replace("/\n> >/im", "\n>>", $output);

                // add another leading newline and trailing newlines
                $output = "\n" . $output . "\n\n";
                break;
            default:
                // do nothing
        }

        return $output;
    }
}

?>




