/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "PgmImage.h"
#include <cassert>

using namespace std;

PgmImage::PgmImage(std::string filename) :
m_w(0),m_h(0),m_colors(255),m_format(PGM_BINARY_GRAYMAP),m_over_allocation(256)
{
    if ( !ReadPGM(filename) )
        return;
}

PgmImage::PgmImage(int w, int h, int format) :
m_colors(255),m_w(w),m_h(h),m_format(format),m_over_allocation(256)
{
    SetFormat(format);
}

PgmImage::PgmImage(unsigned char *data, int w, int h) :
m_colors(255),m_w(w),m_h(h),m_format(PGM_BINARY_GRAYMAP),m_over_allocation(256)
{
    SetData(data);
}

PgmImage::PgmImage(std::vector<unsigned char> &data, int w, int h) :
m_colors(255),m_w(w),m_h(h),m_format(PGM_BINARY_GRAYMAP),m_over_allocation(256)
{
    if ( data.size() == w*h )
        SetData(&data[0]);
    else
        //throw (std::exception("Size of data is not w*h."));
        throw (std::exception());
}

PgmImage::PgmImage(const PgmImage &im) :
m_colors(255),m_w(0),m_h(0),m_format(PGM_BINARY_GRAYMAP),m_over_allocation(256)
{
    DeepCopy(im, *this);
}

PgmImage& PgmImage::operator= (const PgmImage &im)
{
    if (this == &im) return *this;
    DeepCopy(im, *this);
    return *this;
}

void PgmImage::DeepCopy(const PgmImage& src, PgmImage& dst)
{
    dst.m_data = src.m_data;

    // PGM data
    dst.m_w = src.m_w;
    dst.m_h = src.m_h;
    dst.m_format = src.m_format;
    dst.m_colors = src.m_colors;

    dst.m_comment = src.m_comment;
    SetupRowPointers();
}

PgmImage::~PgmImage()
{

}

void PgmImage::SetFormat(int format)
{
    m_format = format;

    switch (format)
    {
    case PGM_BINARY_GRAYMAP:
        m_data.resize(m_w*m_h+m_over_allocation);
        break;
    case PGM_BINARY_PIXMAP:
        m_data.resize(m_w*m_h*3+m_over_allocation);
        break;
    default:
        return;
        break;
    }
    SetupRowPointers();
}

void PgmImage::SetData(const unsigned char * data)
{
    m_data.resize(m_w*m_h+m_over_allocation);
    memcpy(&m_data[0],data,m_w*m_h);
    SetupRowPointers();
}

bool PgmImage::ReadPGM(const std::string filename)
{
    ifstream in(filename.c_str(),std::ios::in | std::ios::binary);
    if ( !in.is_open() )
        return false;

    // read the header:
    string format_header,size_header,colors_header;

    getline(in,format_header);
    stringstream s;
    s << format_header;

    s >> format_header >> m_w >> m_h >> m_colors;
    s.clear();

    if ( m_w == 0 )
    {
        while ( in.peek() == '#' )
            getline(in,m_comment);

        getline(in,size_header);

        while ( in.peek() == '#' )
            getline(in,m_comment);

            m_colors = 0;

        // parse header
        s << size_header;
        s >> m_w >> m_h >> m_colors;
        s.clear();

        if ( m_colors == 0 )
        {
            getline(in,colors_header);
            s << colors_header;
            s >> m_colors;
        }
    }

    if ( format_header == "P5" )
        m_format = PGM_BINARY_GRAYMAP;
    else if (format_header == "P6" )
        m_format = PGM_BINARY_PIXMAP;
    else
        m_format = PGM_FORMAT_INVALID;

    switch(m_format)
    {
    case(PGM_BINARY_GRAYMAP):
        m_data.resize(m_w*m_h+m_over_allocation);
        in.read((char *)(&m_data[0]),m_data.size());
        break;
    case(PGM_BINARY_PIXMAP):
        m_data.resize(m_w*m_h*3+m_over_allocation);
        in.read((char *)(&m_data[0]),m_data.size());
        break;
    default:
        return false;
        break;
    }
    in.close();

    SetupRowPointers();

    return true;
}

bool PgmImage::WritePGM(const std::string filename, const std::string comment)
{
    string format_header;

    switch(m_format)
    {
    case PGM_BINARY_GRAYMAP:
        format_header = "P5\n";
        break;
    case PGM_BINARY_PIXMAP:
        format_header = "P6\n";
        break;
    default:
        return false;
        break;
    }

    ofstream out(filename.c_str(),std::ios::out |ios::binary);
    out << format_header << "# " << comment << '\n' << m_w << " " << m_h << '\n' << m_colors << '\n';

    out.write((char *)(&m_data[0]), m_data.size());

    out.close();

    return true;
}

void PgmImage::SetupRowPointers()
{
    int i;
    m_rows.resize(m_h);

    switch (m_format)
    {
    case PGM_BINARY_GRAYMAP:
        for(i=0;i<m_h;i++)
        {
            m_rows[i]=&m_data[m_w*i];
        }
        break;
    case PGM_BINARY_PIXMAP:
        for(i=0;i<m_h;i++)
        {
            m_rows[i]=&m_data[(m_w*3)*i];
        }
        break;
    }
}

void PgmImage::ConvertToGray()
{
    if ( m_format != PGM_BINARY_PIXMAP ) return;

    // Y = 0.3*R + 0.59*G + 0.11*B;
    for ( int i = 0; i < m_w*m_h; ++i )
        m_data[i] = (unsigned char)(0.3*m_data[3*i]+0.59*m_data[3*i+1]+0.11*m_data[3*i+2]);

    m_data.resize(m_w*m_h+m_over_allocation);
    m_format = PGM_BINARY_GRAYMAP;

    SetupRowPointers();
}

std::ostream& operator<< (std::ostream& o, const PgmImage& im)
{
    o << "PGM Image Info:\n";
    o << "Size: " << im.m_w << " x " << im.m_h << "\n";
    o << "Comment: " << im.m_comment << "\n";
    switch (im.m_format)
    {
    case PgmImage::PGM_BINARY_PIXMAP:
        o << "Format: RGB binary pixmap";
        break;
    case PgmImage::PGM_BINARY_GRAYMAP:
        o << "Format: PPM binary graymap";
        break;
    default:
        o << "Format: Invalid";
        break;
    }
    o << endl;
    return o;
}