#include <default.h>
#include <utils.h>
#include <cpuinfo.h>
#include <formats.h>
#include <infotypes.h>
#ifdef __NetBSD__
#include <sys/inttypes.h>
#endif

#include <string.h>
#ifndef WIN32
#include <unistd.h>
#include <sys/time.h>
#else
#include <windows.h>
#endif
#include <time.h>
#include <pthread.h>
#include <sys/types.h>
#ifdef	HAVE_KSTAT
#include <kstat.h>
#endif
#include <string>
#include <cctype>
#include <cstdio>
#include <iostream>

using namespace std;

AttributeInfo::AttributeInfo(const char* n, const char* a, const char** o)
    : name(n), about(a), kind(Select)
{
//    cout<<"AttributeInfo("<<n<<") at "<<(int)this<<endl;
    while(*o)
    {
	options.push_back(*o);
	o++;
    }
}

AttributeInfo::AttributeInfo(const char* n, const char* a, Kind k, int minval, int maxval)
    : name(n), about(a), kind(k), i_min(minval), i_max(maxval)
{
//    cout<<"AttributeInfo("<<n<<") at "<<(int)this<<endl;
}

AttributeInfo::AttributeInfo(const char* n)
    : name(n), about(n), kind(Integer), i_min(0), i_max(-1)
{
//    cout<<"AttributeInfo("<<n<<") at "<<(int)this<<endl;
}
AttributeInfo::AttributeInfo(const std::string n)
    : name(n), about(n), kind(Integer), i_min(0), i_max(-1)
{
//    cout<<"AttributeInfo("<<n<<") at "<<(int)this<<endl;
}

AttributeInfo::AttributeInfo()
{
}
AttributeInfo::~AttributeInfo()
{
//    cout<<"~AttributeInfo("<<name<<") at "<<(int)this<<endl;
}

CodecInfo::CodecInfo(const int* array, const char* info, const char* path, const char* a,
		     Kind _kind, Media _media, Direction _direction, const GUID* id,
		     const std::vector<AttributeInfo>& ei,
		     const std::vector<AttributeInfo>& di)
    : fourcc(array[0]), text(info), about(a), kind(_kind), media(_media),
    direction(_direction), decoder_info(di), encoder_info(ei),
    handle(0), dll(path)
{
//    cout<<"CodecInfo("<<info<<") at "<<(int)this<<endl;
    if (id)
	guid = *id;

    if (!*array) // uncompressed codec
    {
	fourcc_array.push_back(0);
	return;
    }

    while (*array)
    {
	fourcc_array.push_back(*array);
	array++;
    }
}

CodecInfo::CodecInfo()
{
}

CodecInfo::~CodecInfo()
{
//    cout<<"~CodecInfo("<<text<<") at "<<(int)this<<endl;
}

const CodecInfo* CodecInfo::match(unsigned long codec, CodecInfo::Media cimedia, const CodecInfo* start)
{
    vector<CodecInfo>::const_iterator it = video_codecs.begin();
    if ( start )
	it = vector<CodecInfo>::const_iterator(start);

    for(; it!=video_codecs.end(); it++)
    {
	if ((start && (&(*it) == start)) || (it->media != cimedia))
	   continue;

	vector<fourcc_t>::const_iterator iv;
	for (iv = it->fourcc_array.begin(); iv != it->fourcc_array.end(); iv++)
	    if (codec == (*iv))
		return &(*it);
    }
    return 0;
}

#ifdef WIN32
#define rdtsc __asm _emit 0fh __asm _emit 031h
#define cpuid __asm _emit 0fh __asm _emit 0a2h

#define strncasecmp strnicmp
#endif

static void do_cpuid(unsigned int *regs)
{
#ifndef WIN32
	unsigned int ax;
	ax=1;
	__asm__ __volatile__(
	"pushl %%ebx; pushl %%ecx; pushl %%edx;"
	".byte  0x0f, 0xa2;"
	"movl   %%eax, (%2);"
	"movl   %%ebx, 4(%2);"
	"movl   %%ecx, 8(%2);"
	"movl   %%edx, 12(%2);"
	"popl %%edx; popl %%ecx; popl %%ebx;"
	: "=a" (ax)
	:  "0" (ax), "S" (regs)
	);
#else
	__asm
	{
		push eax
		push ebx
		push ecx
		push edx
		push esi
		mov esi, regs
		mov eax, 1
		cpuid
		mov dword ptr [esi], eax
		add esi, 4
		mov dword ptr [esi], ebx
		add esi, 4
		mov dword ptr [esi], ecx
		add esi, 4
		mov dword ptr [esi], edx
		pop esi
		pop edx
		pop ecx
		pop ebx
		pop eax
	}
#endif
	return;
}

unsigned int CPU_Info::localcount_tsc()
{
    int a;
#ifndef WIN32
    __asm__ __volatile__("rdtsc\n\t"

    :"=a"(a)
    :
    :"edx");
#else
    __asm
    {
    	rdtsc
    	mov a, eax
    }
#endif
    return a;
}
int64_t CPU_Info::longcount_tsc()
{
    unsigned int i;
    unsigned int j;
#ifndef WIN32
    __asm__ __volatile__("rdtsc\n\t":"=a"(i), "=d"(j):);
#else
    __asm
    {
	rdtsc
	mov i, eax
	mov j, edx
    }
#endif
    return ((int64_t)j<<32)+(int64_t)i;
}

unsigned int CPU_Info::localcount_notsc()
{
#ifndef WIN32
    struct timeval tv;
    unsigned limit=~0U;
    limit/=1000000;
    gettimeofday(&tv, 0);
    return limit*tv.tv_usec;
#else
	return time(0);
#endif
}

int64_t CPU_Info::longcount_notsc()
{
#ifndef WIN32
    struct timeval tv;
    unsigned limit=~0U;
    limit/=1000000;
    gettimeofday(&tv, 0);
    uint64_t result;
    result=tv.tv_sec;
    result<<=32;
    result+=limit*tv.tv_usec;
    return result;
#else
	return time(0);
#endif
}

double CPU_Info::old_freq()
{
#if	HAVE_KSTAT
    /*
     * try to extact the CPU speed from the solaris kernel's kstat data
     */
    kstat_ctl_t   *kc;
    kstat_t       *ksp;
    kstat_named_t *kdata;
    int            mhz = 0;

    kc = kstat_open();
    if (kc != NULL)
    {
	ksp = kstat_lookup(kc, "cpu_info", 0, "cpu_info0");

	/* kstat found and name/value pairs? */
	if (ksp != NULL && ksp->ks_type == KSTAT_TYPE_NAMED)
	{
	    /* read the kstat data from the kernel */
	    if (kstat_read(kc, ksp, NULL) != -1)
	    {
		/*
		 * lookup desired "clock_MHz" entry, check the expected
		 * data type
		 */
		kdata = (kstat_named_t *)kstat_data_lookup(ksp, "clock_MHz");
		if (kdata != NULL && kdata->data_type == KSTAT_DATA_INT32)
		    mhz = kdata->value.i32;
	    }
	}
	kstat_close(kc);
    }

    if (mhz > 0)
	return mhz * 1000.;
#endif	/* HAVE_KSTAT */

    int i=time(NULL);
    int64_t x,y;
    while(i==time(NULL));
    x=longcount();
    i++;
    while(i==time(NULL));
    y=longcount();
    return (double)(y-x)/1000.;
}

CPU_Info::CPU_Info() : freq(-1), have_tsc(0), have_mmx(0)
{
    char line[200];
    char model[200]="unknown";
    char flags[500]="";
    char* s, *value;

    memory_lock_init();
    FILE *f = fopen("/proc/cpuinfo", "r");
    if (!f)
    {
	unsigned int regs[4];
	do_cpuid(regs);
	have_tsc=regs[3] & 0x00000010;
	have_mmx=regs[3] & 0x00800000;

	if(have_tsc)
	{
	    longcount=longcount_tsc;
	    localcount=localcount_tsc;
	}
	else
	{
	    longcount=longcount_notsc;
	    localcount=localcount_notsc;
	}

	freq=old_freq();
	return;
    }

    while (fgets(line,200,f) != NULL)
    {

	/* NOTE: the ':' is the only character we can rely on */
        value = strchr(line,':');
	if (!value)
	    continue;
	/* terminate the valuename */
	*value++ = '\0';
	/* skip any leading spaces */
	while (*value == ' ')
	    value++;
        s = strchr(value, '\n');
	if (s)
	    *s = '\0';

	if (!strncasecmp(line, "cpu MHz",strlen("cpu MHz")))
	{
	    freq=atof(value);
	    freq*=1000;
	}
	if (!strncasecmp(line, "model name", strlen("model name")))
	    strncpy(model, value, sizeof(model)-1);
	if (!strncasecmp(line, "flags", strlen("flags")) ||
	    !strncasecmp(line, "features", strlen("features")))
	    strncpy(flags, value, sizeof(flags)-1);
	continue;
    }
    fclose(f);

    cout<<"Available CPU flags: "<<flags<<endl;
    have_tsc = (strstr(flags, "tsc") != 0);
    have_mmx = (strstr(flags, "mmx") != 0);


    if (have_tsc)
    {
	longcount=longcount_tsc;
	localcount=localcount_tsc;
    }
    else
    {
	longcount=longcount_notsc;
	localcount=localcount_notsc;
    }

    if (freq < 0)
	freq = old_freq();
    if (have_tsc)
	cout<<freq/1000.<<" MHz "<<model<<" processor detected"<<endl;
}
